chore: tool definition without zod (#873)
This commit is contained in:
@@ -22,6 +22,7 @@ import { Response } from './response.js';
|
|||||||
import { SessionLog } from './sessionLog.js';
|
import { SessionLog } from './sessionLog.js';
|
||||||
import { filteredTools } from './tools.js';
|
import { filteredTools } from './tools.js';
|
||||||
import { packageJSON } from './package.js';
|
import { packageJSON } from './package.js';
|
||||||
|
import { toToolDefinition } from './tools/tool.js';
|
||||||
|
|
||||||
import type { Tool } from './tools/tool.js';
|
import type { Tool } from './tools/tool.js';
|
||||||
import type { BrowserContextFactory } from './browserContextFactory.js';
|
import type { BrowserContextFactory } from './browserContextFactory.js';
|
||||||
@@ -65,15 +66,15 @@ export class BrowserServerBackend implements ServerBackend {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tools(): mcpServer.ToolSchema<any>[] {
|
tools(): mcpServer.ToolDefinition[] {
|
||||||
return this._tools.map(tool => tool.schema);
|
return this._tools.map(tool => toToolDefinition(tool.schema));
|
||||||
}
|
}
|
||||||
|
|
||||||
async callTool(schema: mcpServer.ToolSchema<any>, rawArguments: any) {
|
async callTool(name: string, rawArguments: any) {
|
||||||
|
const tool = this._tools.find(tool => tool.schema.name === name)!;
|
||||||
|
const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {});
|
||||||
const context = this._context!;
|
const context = this._context!;
|
||||||
const parsedArguments = schema.inputSchema.parse(rawArguments || {});
|
const response = new Response(context, name, parsedArguments);
|
||||||
const response = new Response(context, schema.name, parsedArguments);
|
|
||||||
const tool = this._tools.find(tool => tool.schema.name === schema.name)!;
|
|
||||||
context.setRunningTool(true);
|
context.setRunningTool(true);
|
||||||
try {
|
try {
|
||||||
await tool.handle(context, parsedArguments, response);
|
await tool.handle(context, parsedArguments, response);
|
||||||
|
|||||||
@@ -2,3 +2,4 @@
|
|||||||
../
|
../
|
||||||
../loop/
|
../loop/
|
||||||
../mcp/
|
../mcp/
|
||||||
|
../tools/
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { packageJSON } from '../package.js';
|
|||||||
import { Context } from './context.js';
|
import { Context } from './context.js';
|
||||||
import { perform } from './perform.js';
|
import { perform } from './perform.js';
|
||||||
import { snapshot } from './snapshot.js';
|
import { snapshot } from './snapshot.js';
|
||||||
|
import { toToolDefinition } from '../tools/tool.js';
|
||||||
|
|
||||||
import type { FullConfig } from '../config.js';
|
import type { FullConfig } from '../config.js';
|
||||||
import type { ServerBackend } from '../mcp/server.js';
|
import type { ServerBackend } from '../mcp/server.js';
|
||||||
@@ -48,13 +49,13 @@ class LoopToolsServerBackend implements ServerBackend {
|
|||||||
this._context = await Context.create(this._config);
|
this._context = await Context.create(this._config);
|
||||||
}
|
}
|
||||||
|
|
||||||
tools(): mcpServer.ToolSchema<any>[] {
|
tools(): mcpServer.ToolDefinition[] {
|
||||||
return this._tools.map(tool => tool.schema);
|
return this._tools.map(tool => toToolDefinition(tool.schema));
|
||||||
}
|
}
|
||||||
|
|
||||||
async callTool(schema: mcpServer.ToolSchema<any>, rawArguments: any): Promise<mcpServer.ToolResponse> {
|
async callTool(name: string, rawArguments: any): Promise<mcpServer.ToolResponse> {
|
||||||
const tool = this._tools.find(tool => tool.schema.name === schema.name)!;
|
const tool = this._tools.find(tool => tool.schema.name === name)!;
|
||||||
const parsedArguments = schema.inputSchema.parse(rawArguments || {});
|
const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {});
|
||||||
return await tool.handle(this._context!, parsedArguments);
|
return await tool.handle(this._context!, parsedArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,11 @@
|
|||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
import type * as mcpServer from '../mcp/server.js';
|
import type * as mcpServer from '../mcp/server.js';
|
||||||
import type { Context } from './context.js';
|
import type { Context } from './context.js';
|
||||||
|
import type { ToolSchema } from '../tools/tool.js';
|
||||||
|
|
||||||
|
|
||||||
export type Tool<Input extends z.Schema = z.Schema> = {
|
export type Tool<Input extends z.Schema = z.Schema> = {
|
||||||
schema: mcpServer.ToolSchema<Input>;
|
schema: ToolSchema<Input>;
|
||||||
handle: (context: Context, params: z.output<Input>) => Promise<mcpServer.ToolResponse>;
|
handle: (context: Context, params: z.output<Input>) => Promise<mcpServer.ToolResponse>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,3 +2,6 @@
|
|||||||
../log.js
|
../log.js
|
||||||
../manualPromise.js
|
../manualPromise.js
|
||||||
../httpServer.js
|
../httpServer.js
|
||||||
|
|
||||||
|
[proxyBackend.ts]
|
||||||
|
../package.js
|
||||||
@@ -15,12 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { ServerBackend, ToolResponse, ToolSchema } from './server.js';
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||||
import { defineTool, Tool } from '../tools/tool.js';
|
|
||||||
import { packageJSON } from '../package.js';
|
|
||||||
import { logUnhandledError } from '../log.js';
|
import { logUnhandledError } from '../log.js';
|
||||||
|
import { packageJSON } from '../package.js';
|
||||||
|
import { ToolDefinition, ServerBackend, ToolResponse } from './server.js';
|
||||||
|
|
||||||
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||||
|
|
||||||
@@ -40,8 +40,8 @@ export class ProxyBackend implements ServerBackend {
|
|||||||
|
|
||||||
private _clientFactories: ClientFactoryList;
|
private _clientFactories: ClientFactoryList;
|
||||||
private _currentClient: Client | undefined;
|
private _currentClient: Client | undefined;
|
||||||
private _contextSwitchTool: Tool<any>;
|
private _contextSwitchTool: ToolDefinition;
|
||||||
private _tools: ToolSchema<any>[] = [];
|
private _tools: ToolDefinition[] = [];
|
||||||
private _server: Server | undefined;
|
private _server: Server | undefined;
|
||||||
|
|
||||||
constructor(clientFactories: ClientFactoryList) {
|
constructor(clientFactories: ClientFactoryList) {
|
||||||
@@ -54,20 +54,20 @@ export class ProxyBackend implements ServerBackend {
|
|||||||
await this._setCurrentClient(this._clientFactories[0]);
|
await this._setCurrentClient(this._clientFactories[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
tools(): ToolSchema<any>[] {
|
tools(): ToolDefinition[] {
|
||||||
if (this._clientFactories.length === 1)
|
if (this._clientFactories.length === 1)
|
||||||
return this._tools;
|
return this._tools;
|
||||||
return [
|
return [
|
||||||
...this._tools,
|
...this._tools,
|
||||||
this._contextSwitchTool.schema,
|
this._contextSwitchTool,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async callTool(schema: ToolSchema<any>, rawArguments: any): Promise<ToolResponse> {
|
async callTool(name: string, rawArguments: any): Promise<ToolResponse> {
|
||||||
if (schema.name === this._contextSwitchTool.schema.name)
|
if (name === this._contextSwitchTool.name)
|
||||||
return this._callContextSwitchTool(rawArguments);
|
return this._callContextSwitchTool(rawArguments);
|
||||||
const result = await this._currentClient!.callTool({
|
const result = await this._currentClient!.callTool({
|
||||||
name: schema.name,
|
name,
|
||||||
arguments: rawArguments,
|
arguments: rawArguments,
|
||||||
});
|
});
|
||||||
return result as unknown as ToolResponse;
|
return result as unknown as ToolResponse;
|
||||||
@@ -95,39 +95,28 @@ export class ProxyBackend implements ServerBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _defineContextSwitchTool(): Tool<any> {
|
private _defineContextSwitchTool(): ToolDefinition {
|
||||||
return defineTool({
|
return {
|
||||||
capability: 'core',
|
name: 'browser_connect',
|
||||||
|
description: [
|
||||||
schema: {
|
'Connect to a browser using one of the available methods:',
|
||||||
name: 'browser_connect',
|
...this._clientFactories.map(factory => `- "${factory.name}": ${factory.description}`),
|
||||||
|
].join('\n'),
|
||||||
|
inputSchema: zodToJsonSchema(z.object({
|
||||||
|
name: z.enum(this._clientFactories.map(factory => factory.name) as [string, ...string[]]).default(this._clientFactories[0].name).describe('The method to use to connect to the browser'),
|
||||||
|
}), { strictUnions: true }) as ToolDefinition['inputSchema'],
|
||||||
|
annotations: {
|
||||||
title: 'Connect to a browser context',
|
title: 'Connect to a browser context',
|
||||||
description: [
|
readOnlyHint: true,
|
||||||
'Connect to a browser using one of the available methods:',
|
openWorldHint: false,
|
||||||
...this._clientFactories.map(factory => `- "${factory.name}": ${factory.description}`),
|
|
||||||
].join('\n'),
|
|
||||||
inputSchema: z.object({
|
|
||||||
name: z.enum(this._clientFactories.map(factory => factory.name) as [string, ...string[]]).default(this._clientFactories[0].name).describe('The method to use to connect to the browser'),
|
|
||||||
}),
|
|
||||||
type: 'readOnly',
|
|
||||||
},
|
},
|
||||||
|
};
|
||||||
async handle() {
|
|
||||||
throw new Error('Unreachable');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _setCurrentClient(factory: ClientFactory) {
|
private async _setCurrentClient(factory: ClientFactory) {
|
||||||
await this._currentClient?.close();
|
await this._currentClient?.close();
|
||||||
this._currentClient = await factory.create(this._server!);
|
this._currentClient = await factory.create(this._server!);
|
||||||
const tools = await this._currentClient.listTools();
|
const tools = await this._currentClient.listTools();
|
||||||
this._tools = tools.tools.map(tool => ({
|
this._tools = tools.tools;
|
||||||
name: tool.name,
|
|
||||||
title: tool.title ?? '',
|
|
||||||
description: tool.description ?? '',
|
|
||||||
inputSchema: tool.inputSchema ?? z.object({}),
|
|
||||||
type: tool.annotations?.readOnlyHint ? 'readOnly' as const : 'destructive' as const,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||||
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||||
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
||||||
import { ManualPromise } from '../manualPromise.js';
|
import { ManualPromise } from '../manualPromise.js';
|
||||||
import { logUnhandledError } from '../log.js';
|
import { logUnhandledError } from '../log.js';
|
||||||
|
|
||||||
import type { ImageContent, TextContent } from '@modelcontextprotocol/sdk/types.js';
|
import type { ImageContent, TextContent, Tool } from '@modelcontextprotocol/sdk/types.js';
|
||||||
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||||
export type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
export type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||||
|
|
||||||
@@ -36,22 +34,14 @@ export type ToolResponse = {
|
|||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ToolSchema<Input extends z.Schema> = {
|
export type ToolDefinition = Tool;
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
inputSchema: Input;
|
|
||||||
type: 'readOnly' | 'destructive';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ToolHandler = (toolName: string, params: any) => Promise<ToolResponse>;
|
|
||||||
|
|
||||||
export interface ServerBackend {
|
export interface ServerBackend {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
initialize?(server: Server): Promise<void>;
|
initialize?(server: Server): Promise<void>;
|
||||||
tools(): ToolSchema<any>[];
|
tools(): ToolDefinition[];
|
||||||
callTool(schema: ToolSchema<any>, rawArguments: any): Promise<ToolResponse>;
|
callTool(name: string, rawArguments: any): Promise<ToolResponse>;
|
||||||
serverClosed?(): void;
|
serverClosed?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,17 +63,7 @@ export function createServer(backend: ServerBackend, runHeartbeat: boolean): Ser
|
|||||||
|
|
||||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
const tools = backend.tools();
|
const tools = backend.tools();
|
||||||
return { tools: tools.map(tool => ({
|
return { tools };
|
||||||
name: tool.name,
|
|
||||||
description: tool.description,
|
|
||||||
inputSchema: tool.inputSchema instanceof z.ZodType ? zodToJsonSchema(tool.inputSchema) : tool.inputSchema,
|
|
||||||
annotations: {
|
|
||||||
title: tool.title,
|
|
||||||
readOnlyHint: tool.type === 'readOnly',
|
|
||||||
destructiveHint: tool.type === 'destructive',
|
|
||||||
openWorldHint: true,
|
|
||||||
},
|
|
||||||
})) };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let heartbeatRunning = false;
|
let heartbeatRunning = false;
|
||||||
@@ -100,12 +80,12 @@ export function createServer(backend: ServerBackend, runHeartbeat: boolean): Ser
|
|||||||
isError: true,
|
isError: true,
|
||||||
});
|
});
|
||||||
const tools = backend.tools();
|
const tools = backend.tools();
|
||||||
const tool = tools.find(tool => tool.name === request.params.name) as ToolSchema<any>;
|
const tool = tools.find(tool => tool.name === request.params.name);
|
||||||
if (!tool)
|
if (!tool)
|
||||||
return errorResult(`Error: Tool "${request.params.name}" not found`);
|
return errorResult(`Error: Tool "${request.params.name}" not found`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await backend.callTool(tool, request.params.arguments || {});
|
return await backend.callTool(tool.name, request.params.arguments || {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResult(String(error));
|
return errorResult(String(error));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,15 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||||
|
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
import type { Context } from '../context.js';
|
import type { Context } from '../context.js';
|
||||||
import type * as playwright from 'playwright';
|
import type * as playwright from 'playwright';
|
||||||
import type { ToolCapability } from '../../config.js';
|
import type { ToolCapability } from '../../config.js';
|
||||||
import type { Tab } from '../tab.js';
|
import type { Tab } from '../tab.js';
|
||||||
import type { Response } from '../response.js';
|
import type { Response } from '../response.js';
|
||||||
import type { ToolSchema } from '../mcp/server.js';
|
import type { ToolDefinition } from '../mcp/server.js';
|
||||||
|
|
||||||
export type FileUploadModalState = {
|
export type FileUploadModalState = {
|
||||||
type: 'fileChooser';
|
type: 'fileChooser';
|
||||||
@@ -36,6 +38,28 @@ export type DialogModalState = {
|
|||||||
|
|
||||||
export type ModalState = FileUploadModalState | DialogModalState;
|
export type ModalState = FileUploadModalState | DialogModalState;
|
||||||
|
|
||||||
|
export type ToolSchema<Input extends z.Schema> = {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
inputSchema: Input;
|
||||||
|
type: 'readOnly' | 'destructive';
|
||||||
|
};
|
||||||
|
|
||||||
|
export function toToolDefinition(tool: ToolSchema<any>): ToolDefinition {
|
||||||
|
return {
|
||||||
|
name: tool.name,
|
||||||
|
description: tool.description,
|
||||||
|
inputSchema: zodToJsonSchema(tool.inputSchema, { strictUnions: true }) as ToolDefinition['inputSchema'],
|
||||||
|
annotations: {
|
||||||
|
title: tool.title,
|
||||||
|
readOnlyHint: tool.type === 'readOnly',
|
||||||
|
destructiveHint: tool.type === 'destructive',
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Tool<Input extends z.Schema = z.Schema> = {
|
export type Tool<Input extends z.Schema = z.Schema> = {
|
||||||
capability: ToolCapability;
|
capability: ToolCapability;
|
||||||
schema: ToolSchema<Input>;
|
schema: ToolSchema<Input>;
|
||||||
|
|||||||
Reference in New Issue
Block a user