Go all in on stdio and gate all the tools behind the launch tool (#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
This commit is contained in:
Tyler James Leonhardt
2025-08-29 14:41:01 -07:00
committed by GitHub
parent b2fb4accc4
commit f1a5fb082b
25 changed files with 372 additions and 518 deletions

View File

@@ -2,16 +2,15 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { z, ZodRawShape, AnyZodObject, ZodTypeAny } from 'zod';
import { Server, ServerOptions } from '@modelcontextprotocol/sdk/server/index.js';
import { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
import { Implementation, ListToolsRequestSchema, CallToolRequestSchema, ListToolsResult, Tool, CallToolResult, McpError, ErrorCode, ToolAnnotations, ServerRequest, ServerNotification, CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
import { Implementation, ListToolsRequestSchema, CallToolRequestSchema, ListToolsResult, Tool, CallToolResult, McpError, ErrorCode, CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
import { getServer as getAutomationServer } from './automation';
import { getServer as getPlaywrightServer } from './playwright';
import { getApplication } from './application';
import { ApplicationService } from './application';
import { createInMemoryTransportPair } from './inMemoryTransport';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { Application } from '../../automation';
interface SubServer {
client: Client;
@@ -19,43 +18,61 @@ interface SubServer {
}
export async function getServer(): Promise<Server> {
const app = await getApplication();
const automationServer = await getAutomationServer(app);
const playwrightServer = await getPlaywrightServer(app);
// Create in-memory transport pairs for internal communication
const appService = new ApplicationService();
const automationServer = await getAutomationServer(appService);
const [automationServerTransport, automationClientTransport] = createInMemoryTransportPair();
const [playwrightServerTransport, playwrightClientTransport] = createInMemoryTransportPair();
const automationClient = new Client({
name: 'Automation Client',
version: '1.0.0'
});
const playwrightClient = new Client({
name: 'Playwright Client',
version: '1.0.0'
});
const automationClient = new Client({ name: 'Automation Client', version: '1.0.0' });
await automationServer.connect(automationServerTransport);
await automationClient.connect(automationClientTransport);
await playwrightServer.connect(playwrightServerTransport);
await playwrightClient.connect(playwrightClientTransport);
await playwrightClient.notification({
method: 'notifications/initialized',
});
return new MultiplexServer(
[
// Prefixes could change in the future... be careful.
{ client: playwrightClient, prefix: 'browser_' },
{ client: automationClient, prefix: 'vscode_automation_' }
],
const multiplexServer = new MultiplexServer(
[{ client: automationClient, prefix: 'vscode_automation_' }],
{
name: 'VS Code Automation + Playwright Server',
version: '1.0.0',
title: 'Contains tools that can interact with a local build of VS Code. Used for verifying UI behavior.'
}
).server;
);
const closables: { close(): Promise<void> }[] = [];
const createPlaywrightServer = async (app: Application) => {
const playwrightServer = await getPlaywrightServer(app);
const [playwrightServerTransport, playwrightClientTransport] = createInMemoryTransportPair();
const playwrightClient = new Client({ name: 'Playwright Client', version: '1.0.0' });
await playwrightServer.connect(playwrightServerTransport);
await playwrightClient.connect(playwrightClientTransport);
await playwrightClient.notification({ method: 'notifications/initialized' });
// Prefixes could change in the future... be careful.
const playwrightSubServer = { client: playwrightClient, prefix: 'browser_' };
multiplexServer.addSubServer(playwrightSubServer);
multiplexServer.sendToolListChanged();
closables.push(
playwrightClient,
playwrightServer,
playwrightServerTransport,
playwrightClientTransport,
{
async close() {
multiplexServer.removeSubServer(playwrightSubServer);
multiplexServer.sendToolListChanged();
}
}
);
};
const disposePlaywrightServer = async () => {
while (closables.length) {
closables.pop()?.close();
}
};
appService.onApplicationChange(async app => {
if (app) {
await createPlaywrightServer(app);
} else {
await disposePlaywrightServer();
}
});
return multiplexServer.server;
}
/**
@@ -164,46 +181,21 @@ export class MultiplexServer {
this.server.sendToolListChanged();
}
}
addSubServer(subServer: SubServer) {
this.subServers.push(subServer);
this.sendToolListChanged();
}
removeSubServer(subServer: SubServer) {
const index = this.subServers.indexOf(subServer);
if (index >= 0) {
const removed = this.subServers.splice(index);
if (removed) {
this.sendToolListChanged();
}
} else {
throw new Error('SubServer not found.');
}
}
}
/**
* Callback for a tool handler registered with Server.tool().
*
* Parameters will include tool arguments, if applicable, as well as other request handler context.
*
* The callback should return:
* - `structuredContent` if the tool has an outputSchema defined
* - `content` if the tool does not have an outputSchema
* - Both fields are optional but typically one should be provided
*/
export type ToolCallback<Args extends undefined | ZodRawShape = undefined> =
Args extends ZodRawShape
? (
args: z.objectOutputType<Args, ZodTypeAny>,
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
) => CallToolResult | Promise<CallToolResult>
: (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult>;
export type RegisteredTool = {
title?: string;
description?: string;
inputSchema?: AnyZodObject;
outputSchema?: AnyZodObject;
annotations?: ToolAnnotations;
callback: ToolCallback<undefined | ZodRawShape>;
enabled: boolean;
enable(): void;
disable(): void;
update<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>(
updates: {
name?: string | null;
title?: string;
description?: string;
paramsSchema?: InputArgs;
outputSchema?: OutputArgs;
annotations?: ToolAnnotations;
callback?: ToolCallback<InputArgs>;
enabled?: boolean;
}): void;
remove(): void;
};