Skip to content

Commit f1a5fb0

Browse files
Go all in on stdio and gate all the tools behind the launch tool (microsoft#264088)
* Removes HTTP logic * enable/disables automation tools based on if app is opened * kill and re-create the playwright server based on if app is opened
1 parent b2fb4ac commit f1a5fb0

25 files changed

+372
-518
lines changed

test/mcp/README.md

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,6 @@ That's it! It should automatically compile everything needed.
2727

2828
Then you can use `/playwright` to ask specific questions.
2929

30-
## Quick Start - HTTP
31-
32-
Getting started with the MCP server is simple - just run the pre-configured Code - OSS task:
33-
34-
1. Launch the MCP Server
35-
36-
In Code - OSS, open the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`) and run:
37-
38-
```
39-
Tasks: Run Task → Launch MCP Server
40-
```
41-
42-
2. Start the MCP Server
43-
44-
Open the Command Palette and run:
45-
```
46-
MCP: List Servers → vscode-playwright-mcp → Start Server
47-
```
48-
or open [mcp.json](../../.vscode/mcp.json) and start it from there.
49-
50-
That's it! Your AI assistant can now use browser automation capabilities through MCP.
51-
5230
## Arguments
5331

5432
Open the [mcp.json](../../.vscode/mcp.json) and modify the `args`:
@@ -149,11 +127,6 @@ test/mcp/
149127
- Check that port 33418 is not already in use
150128
- Verify all dependencies are installed with `npm install`
151129

152-
### Connection Issues
153-
- Confirm the server is running on `http://localhost:33418/mcp`
154-
- Check your AI assistant's MCP configuration
155-
- Look for CORS-related errors in browser console
156-
157130
### Browser Automation Issues
158131
- Ensure Code - OSS has been built and run at least once
159132
- Check the server logs for Playwright-related errors

test/mcp/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
"watch-automation": "cd ../automation && npm run watch",
1010
"watch-mcp": "node ../../node_modules/typescript/bin/tsc --watch --preserveWatchOutput",
1111
"watch": "npm-run-all -lp watch-automation watch-mcp",
12-
"start-http": "npm run -s compile && node ./out/main.js",
1312
"start-stdio": "npm run -s compile && node ./out/stdio.js"
1413
},
1514
"dependencies": {

test/mcp/src/application.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as playwright from 'playwright';
7-
import { getDevElectronPath, Quality, ConsoleLogger, FileLogger, Logger, MultiLogger, getBuildElectronPath, getBuildVersion, measureAndLog } from '../../automation';
7+
import { getDevElectronPath, Quality, ConsoleLogger, FileLogger, Logger, MultiLogger, getBuildElectronPath, getBuildVersion, measureAndLog, Application } from '../../automation';
88
import * as path from 'path';
99
import * as fs from 'fs';
1010
import * as os from 'os';
@@ -281,7 +281,6 @@ async function ensureStableCode(): Promise<void> {
281281
}
282282

283283
async function setup(): Promise<void> {
284-
logger.log('Test data path:', testDataPath);
285284
logger.log('Preparing smoketest setup...');
286285

287286
if (!opts.web && !opts.remote && opts.build) {
@@ -329,3 +328,55 @@ export async function getApplication() {
329328
});
330329
return application;
331330
}
331+
332+
export class ApplicationService {
333+
private _application: Application | undefined;
334+
private _closing: Promise<void> | undefined;
335+
private _listeners: ((app: Application | undefined) => void)[] = [];
336+
337+
onApplicationChange(listener: (app: Application | undefined) => void): void {
338+
this._listeners.push(listener);
339+
}
340+
341+
removeApplicationChangeListener(listener: (app: Application | undefined) => void): void {
342+
const index = this._listeners.indexOf(listener);
343+
if (index >= 0) {
344+
this._listeners.splice(index, 1);
345+
}
346+
}
347+
348+
get application(): Application | undefined {
349+
return this._application;
350+
}
351+
352+
async getOrCreateApplication(): Promise<Application> {
353+
if (this._closing) {
354+
await this._closing;
355+
}
356+
if (!this._application) {
357+
this._application = await getApplication();
358+
this._application.code.driver.browserContext.on('close', () => {
359+
this._closing = (async () => {
360+
if (this._application) {
361+
this._application.code.driver.browserContext.removeAllListeners();
362+
await this._application.stop();
363+
this._application = undefined;
364+
this._runAllListeners();
365+
}
366+
})();
367+
});
368+
this._runAllListeners();
369+
}
370+
return this._application;
371+
}
372+
373+
private _runAllListeners() {
374+
for (const listener of this._listeners) {
375+
try {
376+
listener(this._application);
377+
} catch (error) {
378+
console.error('Error occurred in application change listener:', error);
379+
}
380+
}
381+
}
382+
}

test/mcp/src/automation.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,47 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7-
import { getApplication } from './application';
7+
import { ApplicationService } from './application';
88
import { applyAllTools } from './automationTools/index.js';
9-
import { Application } from '../../automation';
109
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
1110

12-
export async function getServer(app?: Application): Promise<Server> {
11+
export async function getServer(appService: ApplicationService): Promise<Server> {
1312
const server = new McpServer({
1413
name: 'VS Code Automation Server',
1514
version: '1.0.0',
1615
title: 'An MCP Server that can interact with a local build of VS Code. Used for verifying UI behavior.'
1716
}, { capabilities: { logging: {} } });
1817

19-
const application = app ?? await getApplication();
18+
server.tool(
19+
'vscode_automation_start',
20+
'Start VS Code Build',
21+
{},
22+
async () => {
23+
const app = await appService.getOrCreateApplication();
24+
return {
25+
content: [{
26+
type: 'text' as const,
27+
text: app ? `VS Code started successfully` : `Failed to start VS Code`
28+
}]
29+
};
30+
}
31+
);
2032

2133
// Apply all VS Code automation tools using the modular structure
22-
applyAllTools(server, application);
34+
const registeredTools = applyAllTools(server, appService);
35+
const app = appService.application;
36+
if (app) {
37+
registeredTools.forEach(t => t.enable());
38+
} else {
39+
registeredTools.forEach(t => t.disable());
40+
}
2341

24-
application.code.driver.browserContext.on('close', async () => {
25-
await server.close();
42+
appService.onApplicationChange(app => {
43+
if (app) {
44+
registeredTools.forEach(t => t.enable());
45+
} else {
46+
registeredTools.forEach(t => t.disable());
47+
}
2648
});
2749

2850
return server.server;

test/mcp/src/automationTools/activityBar.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7-
import { Application } from '../../../automation';
6+
import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
7+
import { ApplicationService } from '../application';
88

99
/**
1010
* Activity Bar Tools
1111
*/
12-
export function applyActivityBarTools(server: McpServer, app: Application) {
12+
export function applyActivityBarTools(server: McpServer, appService: ApplicationService): RegisteredTool[] {
13+
const tools: RegisteredTool[] = [];
14+
1315
// Doesn't seem particularly useful
1416
// server.tool(
1517
// 'vscode_automation_activitybar_wait_for_position',
@@ -29,4 +31,6 @@ export function applyActivityBarTools(server: McpServer, app: Application) {
2931
// };
3032
// }
3133
// );
34+
35+
return tools;
3236
}

test/mcp/src/automationTools/core.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,15 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7-
import { Application } from '../../../automation';
6+
import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
7+
import { ApplicationService } from '../application';
88

99
/**
1010
* Core Application Management Tools
1111
*/
12-
export function applyCoreTools(server: McpServer, app: Application) {
13-
// A dummy start tool just so that the model has something to hold on to.
14-
server.tool(
15-
'vscode_automation_start',
16-
'Start VS Code Build',
17-
{},
18-
async () => {
19-
return {
20-
content: [{
21-
type: 'text' as const,
22-
text: `VS Code started successfully`
23-
}]
24-
};
25-
}
26-
);
12+
export function applyCoreTools(server: McpServer, appService: ApplicationService): RegisteredTool[] {
13+
const tools: RegisteredTool[] = [];
14+
2715
// Playwright keeps using this as a start... maybe it needs some massaging
2816
// server.tool(
2917
// 'vscode_automation_restart',
@@ -166,4 +154,6 @@ export function applyCoreTools(server: McpServer, app: Application) {
166154
// };
167155
// }
168156
// );
157+
158+
return tools;
169159
}

test/mcp/src/automationTools/debug.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7-
import { Application } from '../../../automation';
6+
import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
7+
import { ApplicationService } from '../application';
88
import { z } from 'zod';
99

1010
/**
1111
* Debug Tools
1212
*/
13-
export function applyDebugTools(server: McpServer, app: Application) {
14-
server.tool(
13+
export function applyDebugTools(server: McpServer, appService: ApplicationService): RegisteredTool[] {
14+
const tools: RegisteredTool[] = [];
15+
tools.push(server.tool(
1516
'vscode_automation_debug_open',
1617
'Open the debug viewlet',
1718
async () => {
19+
const app = await appService.getOrCreateApplication();
1820
await app.workbench.debug.openDebugViewlet();
1921
return {
2022
content: [{
@@ -23,16 +25,17 @@ export function applyDebugTools(server: McpServer, app: Application) {
2325
}]
2426
};
2527
}
26-
);
28+
));
2729

28-
server.tool(
30+
tools.push(server.tool(
2931
'vscode_automation_debug_set_breakpoint',
3032
'Set a breakpoint on a specific line',
3133
{
3234
lineNumber: z.number().describe('Line number to set breakpoint on')
3335
},
3436
async (args) => {
3537
const { lineNumber } = args;
38+
const app = await appService.getOrCreateApplication();
3639
await app.workbench.debug.setBreakpointOnLine(lineNumber);
3740
return {
3841
content: [{
@@ -41,12 +44,13 @@ export function applyDebugTools(server: McpServer, app: Application) {
4144
}]
4245
};
4346
}
44-
);
47+
));
4548

46-
server.tool(
49+
tools.push(server.tool(
4750
'vscode_automation_debug_start',
4851
'Start debugging',
4952
async () => {
53+
const app = await appService.getOrCreateApplication();
5054
const result = await app.workbench.debug.startDebugging();
5155
return {
5256
content: [{
@@ -55,7 +59,7 @@ export function applyDebugTools(server: McpServer, app: Application) {
5559
}]
5660
};
5761
}
58-
);
62+
));
5963

6064
// Playwright can probably figure this out
6165
// server.tool(
@@ -131,4 +135,6 @@ export function applyDebugTools(server: McpServer, app: Application) {
131135
// };
132136
// }
133137
// );
138+
139+
return tools;
134140
}

0 commit comments

Comments
 (0)