mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 12:04:04 +01:00
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:
committed by
GitHub
parent
b2fb4accc4
commit
f1a5fb082b
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user