summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json12
-rw-r--r--packages/cli/src/config/config.test.ts17
-rw-r--r--packages/cli/src/config/config.ts9
-rw-r--r--packages/vscode-ide-companion/src/ide-server.ts65
4 files changed, 76 insertions, 27 deletions
diff --git a/package-lock.json b/package-lock.json
index 5c1b7c0a..99269519 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5699,6 +5699,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-port": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz",
+ "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts
index 4042bf93..6aab4487 100644
--- a/packages/cli/src/config/config.test.ts
+++ b/packages/cli/src/config/config.test.ts
@@ -840,6 +840,7 @@ describe('loadCliConfig ideMode', () => {
// Explicitly delete TERM_PROGRAM and SANDBOX before each test
delete process.env.TERM_PROGRAM;
delete process.env.SANDBOX;
+ delete process.env.GEMINI_CLI_IDE_SERVER_PORT;
});
afterEach(() => {
@@ -876,6 +877,7 @@ describe('loadCliConfig ideMode', () => {
process.argv = ['node', 'script.js', '--ide-mode'];
const argv = await parseArguments();
process.env.TERM_PROGRAM = 'vscode';
+ process.env.GEMINI_CLI_IDE_SERVER_PORT = '3000';
const settings: Settings = {};
const config = await loadCliConfig(settings, [], 'test-session', argv);
expect(config.getIdeMode()).toBe(true);
@@ -885,6 +887,7 @@ describe('loadCliConfig ideMode', () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments();
process.env.TERM_PROGRAM = 'vscode';
+ process.env.GEMINI_CLI_IDE_SERVER_PORT = '3000';
const settings: Settings = { ideMode: true };
const config = await loadCliConfig(settings, [], 'test-session', argv);
expect(config.getIdeMode()).toBe(true);
@@ -894,6 +897,7 @@ describe('loadCliConfig ideMode', () => {
process.argv = ['node', 'script.js', '--ide-mode'];
const argv = await parseArguments();
process.env.TERM_PROGRAM = 'vscode';
+ process.env.GEMINI_CLI_IDE_SERVER_PORT = '3000';
const settings: Settings = { ideMode: false };
const config = await loadCliConfig(settings, [], 'test-session', argv);
expect(config.getIdeMode()).toBe(true);
@@ -932,6 +936,7 @@ describe('loadCliConfig ideMode', () => {
process.argv = ['node', 'script.js', '--ide-mode'];
const argv = await parseArguments();
process.env.TERM_PROGRAM = 'vscode';
+ process.env.GEMINI_CLI_IDE_SERVER_PORT = '3000';
const settings: Settings = {};
const config = await loadCliConfig(settings, [], 'test-session', argv);
expect(config.getIdeMode()).toBe(true);
@@ -941,4 +946,16 @@ describe('loadCliConfig ideMode', () => {
expect(mcpServers['_ide_server'].description).toBe('IDE connection');
expect(mcpServers['_ide_server'].trust).toBe(false);
});
+
+ it('should throw an error if ideMode is true and no port is set', async () => {
+ process.argv = ['node', 'script.js', '--ide-mode'];
+ const argv = await parseArguments();
+ process.env.TERM_PROGRAM = 'vscode';
+ const settings: Settings = {};
+ await expect(
+ loadCliConfig(settings, [], 'test-session', argv),
+ ).rejects.toThrow(
+ "Could not run in ide mode, make sure you're running in vs code integrated terminal. Try running in a fresh terminal.",
+ );
+ });
});
diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts
index bf76fa4c..69708a61 100644
--- a/packages/cli/src/config/config.ts
+++ b/packages/cli/src/config/config.ts
@@ -306,13 +306,20 @@ export async function loadCliConfig(
}
if (ideMode) {
+ const companionPort = process.env.GEMINI_CLI_IDE_SERVER_PORT;
+ if (!companionPort) {
+ throw new Error(
+ "Could not run in ide mode, make sure you're running in vs code integrated terminal. Try running in a fresh terminal.",
+ );
+ }
+ const httpUrl = `http://localhost:${companionPort}/mcp`;
mcpServers[IDE_SERVER_NAME] = new MCPServerConfig(
undefined, // command
undefined, // args
undefined, // env
undefined, // cwd
undefined, // url
- 'http://localhost:3000/mcp', // httpUrl
+ httpUrl, // httpUrl
undefined, // headers
undefined, // tcp
undefined, // timeout
diff --git a/packages/vscode-ide-companion/src/ide-server.ts b/packages/vscode-ide-companion/src/ide-server.ts
index 43c55e7d..160cb54a 100644
--- a/packages/vscode-ide-companion/src/ide-server.ts
+++ b/packages/vscode-ide-companion/src/ide-server.ts
@@ -14,21 +14,31 @@ import {
type JSONRPCNotification,
} from '@modelcontextprotocol/sdk/types.js';
+import { Server } from 'node:http';
+
+function sendActiveFileChangedNotification(
+ transport: StreamableHTTPServerTransport,
+) {
+ const editor = vscode.window.activeTextEditor;
+ const filePath = editor ? editor.document.uri.fsPath : '';
+ const notification: JSONRPCNotification = {
+ jsonrpc: '2.0',
+ method: 'ide/activeFileChanged',
+ params: { filePath },
+ };
+ transport.send(notification);
+}
+
export async function startIDEServer(context: vscode.ExtensionContext) {
const app = express();
app.use(express.json());
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
+ const sessionsWithInitialNotification = new Set<string>();
- const disposable = vscode.window.onDidChangeActiveTextEditor((editor) => {
- const filePath = editor ? editor.document.uri.fsPath : null;
- const notification: JSONRPCNotification = {
- jsonrpc: '2.0',
- method: 'ide/activeFileChanged',
- params: { filePath },
- };
+ const disposable = vscode.window.onDidChangeActiveTextEditor((_editor) => {
for (const transport of Object.values(transports)) {
- transport.send(notification);
+ sendActiveFileChangedNotification(transport);
}
});
context.subscriptions.push(disposable);
@@ -44,19 +54,12 @@ export async function startIDEServer(context: vscode.ExtensionContext) {
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (newSessionId) => {
transports[newSessionId] = transport;
- const editor = vscode.window.activeTextEditor;
- const filePath = editor ? editor.document.uri.fsPath : null;
- const notification: JSONRPCNotification = {
- jsonrpc: '2.0',
- method: 'ide/activeFileChanged',
- params: { filePath },
- };
- transport.send(notification);
},
});
transport.onclose = () => {
if (transport.sessionId) {
+ sessionsWithInitialNotification.delete(transport.sessionId);
delete transports[transport.sessionId];
}
};
@@ -101,6 +104,7 @@ export async function startIDEServer(context: vscode.ExtensionContext) {
}
const transport = transports[sessionId];
+
try {
await transport.handleRequest(req, res);
} catch (error) {
@@ -109,20 +113,31 @@ export async function startIDEServer(context: vscode.ExtensionContext) {
res.status(400).send('Bad Request');
}
}
+
+ if (!sessionsWithInitialNotification.has(sessionId)) {
+ sendActiveFileChangedNotification(transport);
+ sessionsWithInitialNotification.add(sessionId);
+ }
};
app.get('/mcp', handleSessionRequest);
- // TODO(#3918): Generate dynamically and write to env variable
- const PORT = 3000;
- app.listen(PORT, (error?: Error) => {
- if (error) {
- console.error('Failed to start server:', error);
+ const server = app.listen(0, () => {
+ const address = (server as Server).address();
+ if (address && typeof address !== 'string') {
+ const port = address.port;
+ context.environmentVariableCollection.replace(
+ 'GEMINI_CLI_IDE_SERVER_PORT',
+ port.toString(),
+ );
+ console.log(`MCP Streamable HTTP Server listening on port ${port}`);
+ } else {
+ const port = 0;
+ console.error('Failed to start server:', 'Unknown error');
vscode.window.showErrorMessage(
- `Companion server failed to start on port ${PORT}: ${error.message}`,
+ `Companion server failed to start on port ${port}: Unknown error`,
);
}
- console.log(`MCP Streamable HTTP Server listening on port ${PORT}`);
});
}
@@ -144,9 +159,7 @@ const createMcpServer = () => {
async () => {
try {
const activeEditor = vscode.window.activeTextEditor;
- const filePath = activeEditor
- ? activeEditor.document.uri.fsPath
- : undefined;
+ const filePath = activeEditor ? activeEditor.document.uri.fsPath : '';
if (filePath) {
return {
content: [{ type: 'text', text: `Active file: ${filePath}` }],