Encrypted, ephemeral, private memos on atproto

feat(mcp): implement sessions

graham.systems 6dd67986 de3bda94

verified
Changed files
+48 -9
packages
+47 -8
packages/mcp/index.ts
··· 22 const server = createServer(); 23 24 if (!args.http) { 25 - logger.info("starting in stdio server"); 26 27 const transport = new StdioServerTransport(); 28 await server.connect(transport); 29 } else { 30 - logger.info("starting in streamable HTTP server"); 31 32 - const transport = new StreamableHTTPServerTransport({ 33 - sessionIdGenerator: crypto.randomUUID, 34 - }); 35 36 Deno.serve( 37 { ··· 42 }, 43 onError(error) { 44 logger.error( 45 - "unexpected route error: {error?.message}", 46 - error as Record<string, unknown>, 47 ); 48 49 return new Response(null, { status: 500 }); ··· 61 return new Response(null, { status: 404 }); 62 } 63 64 const { req, res } = toReqRes(request); 65 66 - await server.connect(transport); 67 await transport.handleRequest(req, res); 68 69 return await toFetchResponse(res);
··· 22 const server = createServer(); 23 24 if (!args.http) { 25 + logger.info("starting in stdio mode"); 26 27 const transport = new StdioServerTransport(); 28 await server.connect(transport); 29 } else { 30 + logger.info("starting in streamable HTTP mode"); 31 32 + const sessions: Map<string, StreamableHTTPServerTransport> = new Map(); 33 34 Deno.serve( 35 { ··· 40 }, 41 onError(error) { 42 logger.error( 43 + "unexpected route error: {error}", 44 + { error }, 45 ); 46 47 return new Response(null, { status: 500 }); ··· 59 return new Response(null, { status: 404 }); 60 } 61 62 + const sessionId = request.headers.get("mcp-session-id"); 63 + let transport: StreamableHTTPServerTransport; 64 + 65 + if (sessionId && sessions.has(sessionId)) { 66 + logger.info("{method} resuming session {sessionId}", { 67 + sessionId, 68 + method: request.method, 69 + }); 70 + 71 + transport = sessions.get(sessionId)!; 72 + } else if ( 73 + request.method !== "POST" && !sessions.has(sessionId ?? "") 74 + ) { 75 + logger.error("{method} has invalid session {sessionId}", { 76 + sessionId, 77 + method: request.method, 78 + }); 79 + 80 + return Response.json({ error: "invalid or missing session" }, { 81 + status: 401, 82 + }); 83 + } else { 84 + const sessionId = crypto.randomUUID(); 85 + 86 + logger.info("opening new session {sessionId}", { sessionId }); 87 + 88 + transport = new StreamableHTTPServerTransport({ 89 + sessionIdGenerator: () => sessionId as string, 90 + }); 91 + 92 + transport.onclose = () => { 93 + logger.info("session {sessionId} closed, cleaning up", { 94 + sessionId, 95 + }); 96 + sessions.delete(sessionId); 97 + }; 98 + 99 + sessions.set(sessionId, transport); 100 + 101 + await server.connect(transport); 102 + } 103 + 104 const { req, res } = toReqRes(request); 105 106 await transport.handleRequest(req, res); 107 108 return await toFetchResponse(res);
+1 -1
packages/mcp/server.ts
··· 14 { 15 title: "Addition Tool", 16 description: "Add two numbers", 17 - inputSchema: { a: z.string(), b: z.string() }, 18 outputSchema: { result: z.number() }, 19 }, 20 ({ a, b }) => {
··· 14 { 15 title: "Addition Tool", 16 description: "Add two numbers", 17 + inputSchema: { a: z.number(), b: z.number() }, 18 outputSchema: { result: z.number() }, 19 }, 20 ({ a, b }) => {