Repository navigation
Expand file tree
/
Copy pathmcpProtocol.ts
More file actions
68 lines (60 loc) · 2.46 KB
/
Copy pathmcpProtocol.ts
File metadata and controls
68 lines (60 loc) · 2.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import { createMcpHandler, Server, type Implementation, type Tool } from '@modelcontextprotocol/server';
import YAML from 'yaml';
const UNCACHED = { ttlMs: 0, cacheScope: 'private' } as const;
export interface McpServerPresentation {
serverInfo: Implementation;
instructions: string;
}
export function createMcpServerPresentation(
brandName: string,
adminPanelUrl: string,
guidance = '',
readOnly = false,
): McpServerPresentation {
const adminPanel = `AdminForth admin panel for "${brandName}" at ${adminPanelUrl}`;
return {
serverInfo: {
name: 'adminforth-mcp',
title: `${brandName} Admin Panel`,
version: '1.0.0',
description: `${adminPanel}.`,
websiteUrl: adminPanelUrl,
},
instructions: [
`This is the ${adminPanel}. Use its tools to ${readOnly ? 'read' : 'read and update'} data allowed for the authenticated user.`,
guidance,
].filter(Boolean).join('\n\n'),
};
}
export interface McpProtocolContext extends McpServerPresentation {
listTools: () => Tool[];
callTool: (
name: string,
arguments_: Record<string, unknown> | undefined,
) => Promise<{ output: unknown; isError: boolean }>;
}
// The JSON round trip drops functions and undefined values that handler responses may carry, which YAML cannot serialize.
function serializeToolOutput(output: unknown): string {
return typeof output === 'string' ? output : YAML.stringify(JSON.parse(JSON.stringify(output ?? null)));
}
function createServer({ serverInfo, instructions, listTools, callTool }: McpProtocolContext): Server {
const server = new Server(serverInfo, {
capabilities: { tools: { listChanged: false } },
instructions,
cacheHints: { 'tools/list': UNCACHED, 'server/discover': UNCACHED },
});
server.setRequestHandler('tools/list', () => ({ tools: listTools() }));
server.setRequestHandler('tools/call', async ({ params }) => {
const { output, isError } = await callTool(params.name, params.arguments);
return { content: [{ type: 'text', text: serializeToolOutput(output) }], isError };
});
return server;
}
export class McpProtocol {
private readonly contexts = new WeakMap<Request, McpProtocolContext>();
private readonly handler = createMcpHandler(({ requestInfo }) => createServer(this.contexts.get(requestInfo!)!));
handle(request: Request, parsedBody: unknown, context: McpProtocolContext): Promise<Response> {
this.contexts.set(request, context);
return this.handler.fetch(request, { parsedBody });
}
}