mcpbox 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,12 +5,13 @@
5
5
  </picture>
6
6
  </p>
7
7
 
8
- **MCPBox** is a lightweight gateway that exposes local stdio-based MCP (Model Context Protocol) servers via Streamable HTTP, enabling Claude and other AI agents to connect from anywhere.
8
+ **MCPBox** is a lightweight gateway that exposes local stdio-based [MCP](https://modelcontextprotocol.io) servers via Streamable HTTP, enabling Claude and other AI agents to connect from anywhere.
9
9
 
10
- - Runs multiple MCP stdio servers behind a single HTTP endpoint
11
- - Exposes Tools, Resources & Prompts
10
+ - Runs multiple servers behind a single HTTP endpoint
11
+ - Supports Tools, Resources & Prompts
12
12
  - Namespaces with `servername__` prefix to avoid collisions
13
- - OAuth or API key authentication
13
+ - Per-server tool filtering to limit AI access and reduce context usage
14
+ - OAuth 2.1, API key, or no auth
14
15
 
15
16
  <picture>
16
17
  <source media="(prefers-color-scheme: dark)" srcset="assets/diagram-dark.excalidraw.png">
@@ -27,28 +28,22 @@ Create `mcpbox.json`:
27
28
  "memory": {
28
29
  "command": "npx",
29
30
  "args": ["-y", "@modelcontextprotocol/server-memory"]
31
+ },
32
+ "sequential-thinking": {
33
+ "command": "npx",
34
+ "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"]
30
35
  }
31
36
  }
32
37
  }
33
38
  ```
34
39
 
35
- Run with:
36
-
37
- **npx**
40
+ Run:
38
41
 
39
42
  ```bash
40
43
  npx mcpbox
41
44
  ```
42
- *MCP server commands (e.g., `uvx`, `docker`) must be available where the box runs.*
43
-
44
- **Docker**
45
-
46
- ```bash
47
- docker run -v ./mcpbox.json:/config/config.json -p 8080:8080 ghcr.io/kandobyte/mcpbox
48
- ```
49
- *The Docker image includes Node.js and Python, supporting MCP servers launched via `npx` and `uvx`.*
50
45
 
51
- The box starts on http://localhost:8080. Connect an agent by adding this to your MCP client config:
46
+ Add to your MCP client config:
52
47
 
53
48
  ```json
54
49
  {
@@ -61,103 +56,6 @@ The box starts on http://localhost:8080. Connect an agent by adding this to your
61
56
  }
62
57
  ```
63
58
 
64
- For remote access with authentication, see [Deployment](#deployment) and [Connect Your AI](#connect-your-ai).
65
-
66
- ## Configuration
67
-
68
- See [`mcpbox.example.jsonc`](mcpbox.example.jsonc) for all options. All string values support `${VAR_NAME}` environment variable substitution.
69
-
70
- **[Authentication](docs/authentication.md)** — none (default), API key, or OAuth.
71
-
72
- ## Deployment
73
-
74
- To expose MCPBox remotely, put it behind a TLS-terminating reverse proxy.
75
-
76
- Before deploying with OAuth:
77
- - [ ] Use sqlite storage for persistence across restarts
78
- - [ ] Set issuer to your public URL
79
- - [ ] Use bcrypt hashes for local passwords
80
-
81
- > [!NOTE]
82
- > MCPBox is single-instance only — don't run multiple instances behind a load balancer.
83
-
84
- ### Quick remote access
85
-
86
- Use [cloudflared](https://github.com/cloudflare/cloudflared) to expose a local instance (no account required):
87
-
88
- ```bash
89
- cloudflared tunnel --url http://localhost:8080
90
- ```
91
-
92
- Then update your config with the generated public URL:
93
-
94
- ```json
95
- {
96
- "auth": {
97
- "type": "oauth",
98
- "issuer": "https://<tunnel-id>.trycloudflare.com",
99
- "identityProviders": [
100
- { "type": "local", "users": [{ "username": "admin", "password": "${MCPBOX_PASSWORD}" }] }
101
- ],
102
- "dynamicRegistration": true
103
- },
104
- "storage": {
105
- "type": "sqlite",
106
- "path": "/data/mcpbox.db"
107
- },
108
- "mcpServers": { ... }
109
- }
110
- ```
111
-
112
- Run with a persistent data volume:
113
-
114
- ```bash
115
- docker run -v ./mcpbox.json:/config/config.json -v ./data:/data -p 8080:8080 ghcr.io/kandobyte/mcpbox
116
- ```
117
-
118
- ## Connect Your AI
119
-
120
- ### Claude Web & Mobile
59
+ ## Documentation
121
60
 
122
- Settings Connectors Add Custom Connector enter your URL → Connect
123
-
124
- Requires `dynamicRegistration: true` in your config.
125
-
126
- ### Claude Code
127
-
128
- ```bash
129
- claude mcp add --transport http mcpbox https://your-mcpbox-url.com
130
- ```
131
-
132
- Requires `dynamicRegistration: true` in your config.
133
-
134
- ### Other MCP clients
135
-
136
- **With dynamic registration (OAuth)** — just provide the URL:
137
-
138
- ```json
139
- {
140
- "mcpServers": {
141
- "mcpbox": {
142
- "type": "http",
143
- "url": "https://your-mcpbox-url.com"
144
- }
145
- }
146
- }
147
- ```
148
-
149
- **With API key:**
150
-
151
- ```json
152
- {
153
- "mcpServers": {
154
- "mcpbox": {
155
- "type": "http",
156
- "url": "https://your-mcpbox-url.com",
157
- "headers": {
158
- "Authorization": "Bearer YOUR_API_KEY"
159
- }
160
- }
161
- }
162
- }
163
- ```
61
+ See the [documentation](https://kandobyte.github.io/mcpbox/quick-start) for configuration, authentication, deployment, and connecting clients.
@@ -2,7 +2,13 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { RawConfigSchema, } from "./schema.js";
3
3
  function substituteEnvVars(obj) {
4
4
  if (typeof obj === "string") {
5
- return obj.replace(/\$\{(\w+)\}/g, (_, name) => process.env[name] ?? "");
5
+ return obj.replace(/\$\{(\w+)\}/g, (match, name) => {
6
+ const value = process.env[name];
7
+ if (value === undefined) {
8
+ throw new Error(`Environment variable ${name} is not set (referenced as ${match})`);
9
+ }
10
+ return value;
11
+ });
6
12
  }
7
13
  if (Array.isArray(obj)) {
8
14
  return obj.map(substituteEnvVars);
@@ -25,6 +31,8 @@ function parseMcpServers(mcpServers) {
25
31
  args: entry.args,
26
32
  env: entry.env,
27
33
  tools: entry.tools,
34
+ resources: entry.resources,
35
+ prompts: entry.prompts,
28
36
  });
29
37
  }
30
38
  return mcps;
@@ -8,6 +8,8 @@ export declare const McpServerEntrySchema: z.ZodObject<{
8
8
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
9
9
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
10
10
  tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
11
+ resources: z.ZodOptional<z.ZodBoolean>;
12
+ prompts: z.ZodOptional<z.ZodBoolean>;
11
13
  }, z.core.$strict>;
12
14
  /**
13
15
  * OAuth user credentials
@@ -204,6 +206,8 @@ export declare const RawConfigSchema: z.ZodObject<{
204
206
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
205
207
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
206
208
  tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
209
+ resources: z.ZodOptional<z.ZodBoolean>;
210
+ prompts: z.ZodOptional<z.ZodBoolean>;
207
211
  }, z.core.$strict>>>;
208
212
  }, z.core.$strict>;
209
213
  /**
@@ -216,6 +220,8 @@ export declare const McpConfigSchema: z.ZodObject<{
216
220
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
217
221
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
218
222
  tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
223
+ resources: z.ZodOptional<z.ZodBoolean>;
224
+ prompts: z.ZodOptional<z.ZodBoolean>;
219
225
  }, z.core.$strip>;
220
226
  /**
221
227
  * Processed config (after loader adds defaults and resolves mcpServers)
@@ -282,6 +288,8 @@ export declare const ConfigSchema: z.ZodObject<{
282
288
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
283
289
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
284
290
  tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
291
+ resources: z.ZodOptional<z.ZodBoolean>;
292
+ prompts: z.ZodOptional<z.ZodBoolean>;
285
293
  }, z.core.$strip>>;
286
294
  }, z.core.$strip>;
287
295
  export type McpServerEntry = z.infer<typeof McpServerEntrySchema>;
@@ -17,6 +17,8 @@ export const McpServerEntrySchema = z
17
17
  args: z.array(z.string()).optional(),
18
18
  env: z.record(z.string(), z.string()).optional(),
19
19
  tools: z.array(z.string()).optional(),
20
+ resources: z.boolean().optional(),
21
+ prompts: z.boolean().optional(),
20
22
  })
21
23
  .strict();
22
24
  /**
@@ -129,6 +131,14 @@ export const AuthConfigSchema = z.discriminatedUnion("type", [
129
131
  return oauth.identityProviders && oauth.identityProviders.length > 0;
130
132
  }, {
131
133
  message: "dynamic registration requires identity providers to be configured for user login",
134
+ })
135
+ .refine((oauth) => {
136
+ if (!oauth.clients)
137
+ return true;
138
+ const ids = oauth.clients.map((c) => c.clientId);
139
+ return new Set(ids).size === ids.length;
140
+ }, {
141
+ message: "Duplicate client IDs are not allowed",
132
142
  }),
133
143
  ]);
134
144
  /**
@@ -193,6 +203,8 @@ export const McpConfigSchema = z.object({
193
203
  args: z.array(z.string()).optional(),
194
204
  env: z.record(z.string(), z.string()).optional(),
195
205
  tools: z.array(z.string()).optional(),
206
+ resources: z.boolean().optional(),
207
+ prompts: z.boolean().optional(),
196
208
  });
197
209
  /**
198
210
  * Processed config (after loader adds defaults and resolves mcpServers)
@@ -0,0 +1,22 @@
1
+ import { ErrorCode } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { Context } from "hono";
3
+ import type { McpManager } from "./manager.js";
4
+ export declare function createMcpHandler(mcpManager: McpManager): (c: Context) => Promise<(Response & import("hono").TypedResponse<{
5
+ jsonrpc: "2.0";
6
+ id: string | number;
7
+ error: {
8
+ code: number;
9
+ message: string;
10
+ };
11
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
12
+ jsonrpc: "2.0";
13
+ id: string | number;
14
+ result: import("hono/utils/types").JSONValue;
15
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
16
+ jsonrpc: string;
17
+ error: {
18
+ code: ErrorCode;
19
+ message: string;
20
+ };
21
+ id: null;
22
+ }, 400, "json">) | (Response & import("hono").TypedResponse<null, 202, "body">)>;
@@ -0,0 +1,148 @@
1
+ import { CallToolRequestSchema, CompleteRequestSchema, ErrorCode, GetPromptRequestSchema, InitializeRequestSchema, isJSONRPCNotification, JSONRPCRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, PingRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
2
+ import { logger } from "../logger.js";
3
+ import { NAME, VERSION } from "../version.js";
4
+ function jsonrpcResult(id, result) {
5
+ return { jsonrpc: "2.0", id, result };
6
+ }
7
+ function jsonrpcError(id, code, message) {
8
+ return { jsonrpc: "2.0", id, error: { code, message } };
9
+ }
10
+ export function createMcpHandler(mcpManager) {
11
+ return async (c) => {
12
+ let body;
13
+ try {
14
+ body = await c.req.json();
15
+ }
16
+ catch (e) {
17
+ logger.warn({ error: e instanceof Error ? e.message : String(e) }, "MCP parse error");
18
+ return c.json({
19
+ jsonrpc: "2.0",
20
+ error: { code: ErrorCode.ParseError, message: "Parse error" },
21
+ id: null,
22
+ }, 400);
23
+ }
24
+ if (isJSONRPCNotification(body)) {
25
+ const method = body.method;
26
+ logger.debug({ method }, "MCP notification");
27
+ return c.body(null, 202);
28
+ }
29
+ const envelope = JSONRPCRequestSchema.safeParse(body);
30
+ if (!envelope.success) {
31
+ return c.json({
32
+ jsonrpc: "2.0",
33
+ error: {
34
+ code: ErrorCode.InvalidRequest,
35
+ message: "Invalid request",
36
+ },
37
+ id: null,
38
+ }, 400);
39
+ }
40
+ const request = envelope.data;
41
+ logger.debug({ method: request.method, id: request.id }, "MCP request");
42
+ return handleRequest(c, request, mcpManager);
43
+ };
44
+ }
45
+ async function handleRequest(c, request, mcpManager) {
46
+ const { id, method } = request;
47
+ if (method === "initialize") {
48
+ const parsed = InitializeRequestSchema.safeParse(request);
49
+ if (!parsed.success) {
50
+ return c.json(jsonrpcError(id, ErrorCode.InvalidParams, "Invalid params"));
51
+ }
52
+ const result = {
53
+ protocolVersion: "2025-11-25",
54
+ capabilities: {
55
+ tools: { listChanged: true },
56
+ resources: { listChanged: true },
57
+ prompts: { listChanged: true },
58
+ completions: {},
59
+ },
60
+ serverInfo: {
61
+ name: NAME,
62
+ version: VERSION,
63
+ },
64
+ };
65
+ return c.json(jsonrpcResult(id, result));
66
+ }
67
+ if (method === "ping") {
68
+ const parsed = PingRequestSchema.safeParse(request);
69
+ if (!parsed.success) {
70
+ return c.json(jsonrpcError(id, ErrorCode.InvalidParams, "Invalid params"));
71
+ }
72
+ return c.json(jsonrpcResult(id, {}));
73
+ }
74
+ if (method === "tools/list") {
75
+ const parsed = ListToolsRequestSchema.safeParse(request);
76
+ if (!parsed.success) {
77
+ return c.json(jsonrpcError(id, ErrorCode.InvalidParams, "Invalid params"));
78
+ }
79
+ return c.json(jsonrpcResult(id, { tools: mcpManager.listTools() }));
80
+ }
81
+ if (method === "tools/call") {
82
+ const parsed = CallToolRequestSchema.safeParse(request);
83
+ if (!parsed.success) {
84
+ return c.json(jsonrpcError(id, ErrorCode.InvalidParams, "Invalid params"));
85
+ }
86
+ try {
87
+ const result = await mcpManager.callTool(parsed.data.params);
88
+ return c.json(jsonrpcResult(id, result));
89
+ }
90
+ catch (error) {
91
+ return c.json(jsonrpcError(id, ErrorCode.InternalError, error instanceof Error ? error.message : "Internal error"));
92
+ }
93
+ }
94
+ if (method === "resources/list") {
95
+ const parsed = ListResourcesRequestSchema.safeParse(request);
96
+ if (!parsed.success) {
97
+ return c.json(jsonrpcError(id, ErrorCode.InvalidParams, "Invalid params"));
98
+ }
99
+ return c.json(jsonrpcResult(id, { resources: mcpManager.listResources() }));
100
+ }
101
+ if (method === "resources/read") {
102
+ const parsed = ReadResourceRequestSchema.safeParse(request);
103
+ if (!parsed.success) {
104
+ return c.json(jsonrpcError(id, ErrorCode.InvalidParams, "Invalid params"));
105
+ }
106
+ try {
107
+ const result = await mcpManager.readResource(parsed.data.params);
108
+ return c.json(jsonrpcResult(id, result));
109
+ }
110
+ catch (error) {
111
+ return c.json(jsonrpcError(id, ErrorCode.InternalError, error instanceof Error ? error.message : "Internal error"));
112
+ }
113
+ }
114
+ if (method === "prompts/list") {
115
+ const parsed = ListPromptsRequestSchema.safeParse(request);
116
+ if (!parsed.success) {
117
+ return c.json(jsonrpcError(id, ErrorCode.InvalidParams, "Invalid params"));
118
+ }
119
+ return c.json(jsonrpcResult(id, { prompts: mcpManager.listPrompts() }));
120
+ }
121
+ if (method === "prompts/get") {
122
+ const parsed = GetPromptRequestSchema.safeParse(request);
123
+ if (!parsed.success) {
124
+ return c.json(jsonrpcError(id, ErrorCode.InvalidParams, "Invalid params"));
125
+ }
126
+ try {
127
+ const result = await mcpManager.getPrompt(parsed.data.params);
128
+ return c.json(jsonrpcResult(id, result));
129
+ }
130
+ catch (error) {
131
+ return c.json(jsonrpcError(id, ErrorCode.InternalError, error instanceof Error ? error.message : "Internal error"));
132
+ }
133
+ }
134
+ if (method === "completion/complete") {
135
+ const parsed = CompleteRequestSchema.safeParse(request);
136
+ if (!parsed.success) {
137
+ return c.json(jsonrpcError(id, ErrorCode.InvalidParams, "Invalid params"));
138
+ }
139
+ try {
140
+ const result = await mcpManager.complete(parsed.data.params);
141
+ return c.json(jsonrpcResult(id, result));
142
+ }
143
+ catch (error) {
144
+ return c.json(jsonrpcError(id, ErrorCode.InternalError, error instanceof Error ? error.message : "Internal error"));
145
+ }
146
+ }
147
+ return c.json(jsonrpcError(id, ErrorCode.MethodNotFound, `Method not found: ${method}`));
148
+ }
@@ -1,6 +1,6 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
- import type { CallToolResult, CompleteResult, GetPromptResult, Prompt, ReadResourceResult, Resource, Tool } from "@modelcontextprotocol/sdk/types.js";
3
+ import type { CallToolRequestParams, CallToolResult, CompleteRequestParams, CompleteResult, GetPromptRequestParams, GetPromptResult, Prompt, ReadResourceRequestParams, ReadResourceResult, Resource, Tool } from "@modelcontextprotocol/sdk/types.js";
4
4
  import type { LogConfig, McpConfig } from "../config/types.js";
5
5
  export interface ManagedMcp {
6
6
  name: string;
@@ -33,16 +33,9 @@ export declare class McpManager {
33
33
  prompts: number;
34
34
  }>;
35
35
  }>;
36
- callTool(toolName: string, args: Record<string, unknown>): Promise<CallToolResult>;
37
- readResource(resourceUri: string): Promise<ReadResourceResult>;
38
- getPrompt(promptName: string, args?: Record<string, string>): Promise<GetPromptResult>;
39
- complete(ref: {
40
- type: string;
41
- name?: string;
42
- uri?: string;
43
- }, argument: {
44
- name: string;
45
- value: string;
46
- }): Promise<CompleteResult>;
36
+ callTool(params: CallToolRequestParams): Promise<CallToolResult>;
37
+ readResource(params: ReadResourceRequestParams): Promise<ReadResourceResult>;
38
+ getPrompt(params: GetPromptRequestParams): Promise<GetPromptResult>;
39
+ complete(params: CompleteRequestParams): Promise<CompleteResult>;
47
40
  private resolveCompletionRef;
48
41
  }
@@ -106,37 +106,47 @@ export class McpManager {
106
106
  }
107
107
  // Get resources from this MCP (namespace them if multiple servers)
108
108
  let resources = [];
109
- try {
110
- const { resources: rawResources } = await client.listResources();
111
- resources = this.useNamespacing
112
- ? rawResources.map((resource) => ({
113
- ...resource,
114
- uri: namespaceName(config.name, resource.uri),
115
- }))
116
- : rawResources;
117
- for (const resource of resources) {
118
- this.resourceToMcp.set(resource.uri, config.name);
109
+ if (config.resources !== false) {
110
+ try {
111
+ const { resources: rawResources } = await client.listResources();
112
+ resources = this.useNamespacing
113
+ ? rawResources.map((resource) => ({
114
+ ...resource,
115
+ uri: namespaceName(config.name, resource.uri),
116
+ }))
117
+ : rawResources;
118
+ for (const resource of resources) {
119
+ this.resourceToMcp.set(resource.uri, config.name);
120
+ }
121
+ }
122
+ catch {
123
+ logger.debug({ mcp: config.name }, "Server doesn't support resources");
119
124
  }
120
125
  }
121
- catch {
122
- logger.debug({ mcp: config.name }, "Server doesn't support resources");
126
+ else {
127
+ logger.debug({ mcp: config.name }, "Resources disabled by config");
123
128
  }
124
129
  // Get prompts from this MCP (namespace them if multiple servers)
125
130
  let prompts = [];
126
- try {
127
- const { prompts: rawPrompts } = await client.listPrompts();
128
- prompts = this.useNamespacing
129
- ? rawPrompts.map((prompt) => ({
130
- ...prompt,
131
- name: namespaceName(config.name, prompt.name),
132
- }))
133
- : rawPrompts;
134
- for (const prompt of prompts) {
135
- this.promptToMcp.set(prompt.name, config.name);
131
+ if (config.prompts !== false) {
132
+ try {
133
+ const { prompts: rawPrompts } = await client.listPrompts();
134
+ prompts = this.useNamespacing
135
+ ? rawPrompts.map((prompt) => ({
136
+ ...prompt,
137
+ name: namespaceName(config.name, prompt.name),
138
+ }))
139
+ : rawPrompts;
140
+ for (const prompt of prompts) {
141
+ this.promptToMcp.set(prompt.name, config.name);
142
+ }
143
+ }
144
+ catch {
145
+ logger.debug({ mcp: config.name }, "Server doesn't support prompts");
136
146
  }
137
147
  }
138
- catch {
139
- logger.debug({ mcp: config.name }, "Server doesn't support prompts");
148
+ else {
149
+ logger.debug({ mcp: config.name }, "Prompts disabled by config");
140
150
  }
141
151
  this.mcps.set(config.name, {
142
152
  name: config.name,
@@ -217,91 +227,94 @@ export class McpManager {
217
227
  }
218
228
  return { servers };
219
229
  }
220
- async callTool(toolName, args) {
221
- const mcpName = this.toolToMcp.get(toolName);
230
+ async callTool(params) {
231
+ const mcpName = this.toolToMcp.get(params.name);
222
232
  if (!mcpName) {
223
- logger.warn(`Unknown tool called: ${toolName}`);
224
- throw new Error(`Unknown tool: ${toolName}`);
233
+ logger.warn(`Unknown tool called: ${params.name}`);
234
+ throw new Error(`Unknown tool: ${params.name}`);
225
235
  }
226
236
  const mcp = this.mcps.get(mcpName);
227
237
  if (!mcp) {
228
- logger.error({ mcpName }, `MCP not found for tool: ${toolName}`);
238
+ logger.error({ mcpName }, `MCP not found for tool: ${params.name}`);
229
239
  throw new Error(`MCP not found: ${mcpName}`);
230
240
  }
231
241
  // Strip namespace prefix to get original tool name
232
242
  const originalName = this.useNamespacing
233
- ? stripNamespace(mcpName, toolName)
234
- : toolName;
235
- logger.info({ args }, `Tool call: ${toolName}`);
243
+ ? stripNamespace(mcpName, params.name)
244
+ : params.name;
245
+ logger.info({ arguments: params.arguments }, `Tool call: ${params.name}`);
236
246
  const startTime = Date.now();
237
247
  const result = await mcp.client.callTool({
238
248
  name: originalName,
239
- arguments: args,
249
+ arguments: params.arguments,
240
250
  });
241
251
  const duration = Date.now() - startTime;
242
252
  logger.info({
243
253
  duration: `${duration}ms`,
244
254
  isError: result.isError ?? false,
245
- }, `Tool result: ${toolName}`);
255
+ }, `Tool result: ${params.name}`);
246
256
  return result;
247
257
  }
248
- async readResource(resourceUri) {
249
- const mcpName = this.resourceToMcp.get(resourceUri);
258
+ async readResource(params) {
259
+ const mcpName = this.resourceToMcp.get(params.uri);
250
260
  if (!mcpName) {
251
- logger.warn(`Unknown resource: ${resourceUri}`);
252
- throw new Error(`Unknown resource: ${resourceUri}`);
261
+ logger.warn(`Unknown resource: ${params.uri}`);
262
+ throw new Error(`Unknown resource: ${params.uri}`);
253
263
  }
254
264
  const mcp = this.mcps.get(mcpName);
255
265
  if (!mcp) {
256
- logger.error({ mcpName }, `MCP not found for resource: ${resourceUri}`);
266
+ logger.error({ mcpName }, `MCP not found for resource: ${params.uri}`);
257
267
  throw new Error(`MCP not found: ${mcpName}`);
258
268
  }
259
269
  // Strip namespace prefix to get original URI
260
270
  const originalUri = this.useNamespacing
261
- ? stripNamespace(mcpName, resourceUri)
262
- : resourceUri;
263
- logger.info(`Resource read: ${resourceUri}`);
271
+ ? stripNamespace(mcpName, params.uri)
272
+ : params.uri;
273
+ logger.info(`Resource read: ${params.uri}`);
264
274
  const startTime = Date.now();
265
275
  const result = await mcp.client.readResource({ uri: originalUri });
266
276
  const duration = Date.now() - startTime;
267
- logger.info({ duration: `${duration}ms` }, `Resource result: ${resourceUri}`);
277
+ logger.info({ duration: `${duration}ms` }, `Resource result: ${params.uri}`);
268
278
  return result;
269
279
  }
270
- async getPrompt(promptName, args) {
271
- const mcpName = this.promptToMcp.get(promptName);
280
+ async getPrompt(params) {
281
+ const mcpName = this.promptToMcp.get(params.name);
272
282
  if (!mcpName) {
273
- logger.warn(`Unknown prompt: ${promptName}`);
274
- throw new Error(`Unknown prompt: ${promptName}`);
283
+ logger.warn(`Unknown prompt: ${params.name}`);
284
+ throw new Error(`Unknown prompt: ${params.name}`);
275
285
  }
276
286
  const mcp = this.mcps.get(mcpName);
277
287
  if (!mcp) {
278
- logger.error({ mcpName }, `MCP not found for prompt: ${promptName}`);
288
+ logger.error({ mcpName }, `MCP not found for prompt: ${params.name}`);
279
289
  throw new Error(`MCP not found: ${mcpName}`);
280
290
  }
281
291
  // Strip namespace prefix to get original name
282
292
  const originalName = this.useNamespacing
283
- ? stripNamespace(mcpName, promptName)
284
- : promptName;
285
- logger.info({ args }, `Prompt get: ${promptName}`);
293
+ ? stripNamespace(mcpName, params.name)
294
+ : params.name;
295
+ logger.info({ arguments: params.arguments }, `Prompt get: ${params.name}`);
286
296
  const startTime = Date.now();
287
297
  const result = await mcp.client.getPrompt({
288
298
  name: originalName,
289
- arguments: args,
299
+ arguments: params.arguments,
290
300
  });
291
301
  const duration = Date.now() - startTime;
292
- logger.info({ duration: `${duration}ms` }, `Prompt result: ${promptName}`);
302
+ logger.info({ duration: `${duration}ms` }, `Prompt result: ${params.name}`);
293
303
  return result;
294
304
  }
295
- async complete(ref, argument) {
296
- const { mcpName, originalRef } = this.resolveCompletionRef(ref);
305
+ async complete(params) {
306
+ const { mcpName, originalRef } = this.resolveCompletionRef(params.ref);
297
307
  const mcp = this.mcps.get(mcpName);
298
308
  if (!mcp) {
299
309
  logger.error({ mcpName }, "MCP not found for completion");
300
310
  throw new Error(`MCP not found: ${mcpName}`);
301
311
  }
302
- logger.info({ ref, argument }, "Completion request");
312
+ logger.info({ ref: params.ref, argument: params.argument }, "Completion request");
303
313
  const startTime = Date.now();
304
- const result = await mcp.client.complete({ ref: originalRef, argument });
314
+ const result = await mcp.client.complete({
315
+ ref: originalRef,
316
+ argument: params.argument,
317
+ });
305
318
  const duration = Date.now() - startTime;
306
319
  logger.info({ duration: `${duration}ms` }, "Completion result");
307
320
  return result;
package/dist/server.js CHANGED
@@ -7,7 +7,7 @@ import { OAuthServer } from "./auth/oauth.js";
7
7
  import { GitHubIdentityProvider } from "./auth/providers/github.js";
8
8
  import { LocalIdentityProvider } from "./auth/providers/local.js";
9
9
  import { logger } from "./logger.js";
10
- import { handleCompletionComplete, handleInitialize, handleInitialized, handleMethodNotFound, handlePing, handlePromptsGet, handlePromptsList, handleResourcesList, handleResourcesRead, handleToolsCall, handleToolsList, } from "./mcp/handlers.js";
10
+ import { createMcpHandler } from "./mcp/handler.js";
11
11
  import { McpManager } from "./mcp/manager.js";
12
12
  import { MemoryStore } from "./storage/memory.js";
13
13
  import { SqliteStore } from "./storage/sqlite.js";
@@ -150,67 +150,15 @@ export async function createServer(config) {
150
150
  }
151
151
  return next();
152
152
  });
153
- // MCP handler
154
- const handleMcp = async (c) => {
155
- let message;
156
- try {
157
- message = await c.req.json();
158
- }
159
- catch (e) {
160
- logger.warn({ error: e instanceof Error ? e.message : String(e) }, "MCP parse error");
161
- return c.json({
162
- jsonrpc: "2.0",
163
- error: { code: -32700, message: "Parse error" },
164
- id: null,
165
- }, 400);
166
- }
167
- const method = "method" in message ? message.method : undefined;
168
- const id = "id" in message ? message.id : undefined;
169
- logger.debug({ method, id }, "MCP request");
170
- if (method === "initialize") {
171
- return handleInitialize(c, message);
172
- }
173
- if (method === "notifications/initialized") {
174
- return handleInitialized(c);
175
- }
176
- if (method === "tools/list") {
177
- return handleToolsList(c, message, mcpManager);
178
- }
179
- if (method === "tools/call") {
180
- const params = message.params;
181
- return handleToolsCall(c, message, mcpManager, params);
182
- }
183
- if (method === "resources/list") {
184
- return handleResourcesList(c, message, mcpManager);
185
- }
186
- if (method === "resources/read") {
187
- const params = message.params;
188
- return handleResourcesRead(c, message, mcpManager, params);
189
- }
190
- if (method === "prompts/list") {
191
- return handlePromptsList(c, message, mcpManager);
192
- }
193
- if (method === "prompts/get") {
194
- const params = message.params;
195
- return handlePromptsGet(c, message, mcpManager, params);
196
- }
197
- if (method === "ping") {
198
- return handlePing(c, message);
199
- }
200
- if (method === "completion/complete") {
201
- const params = message.params;
202
- return handleCompletionComplete(c, message, mcpManager, params);
203
- }
204
- return handleMethodNotFound(c, message, method ?? "unknown");
205
- };
206
153
  // Status endpoint (protected)
207
154
  protectedRoutes.get("/status", async (c) => {
208
155
  const health = await mcpManager.checkHealth();
209
156
  return c.json({ servers: health.servers });
210
157
  });
211
158
  // MCP endpoints (protected)
212
- protectedRoutes.post("/", handleMcp);
213
- protectedRoutes.post("/mcp", handleMcp);
159
+ const mcp = createMcpHandler(mcpManager);
160
+ protectedRoutes.post("/", mcp);
161
+ protectedRoutes.post("/mcp", mcp);
214
162
  // Mount protected routes
215
163
  app.route("/", protectedRoutes);
216
164
  // 404 for other routes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcpbox",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "A lightweight gateway that exposes local stdio-based MCP servers via Streamable HTTP",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -18,7 +18,10 @@
18
18
  "test:conformance:oauth": "node --disable-warning=ExperimentalWarning --import tsx --test --test-concurrency=1 'test/conformance-oauth/**/*.test.ts'",
19
19
  "test:coverage": "node --experimental-test-coverage --disable-warning=ExperimentalWarning --import tsx --test --test-concurrency=1 'test/unit/**/*.test.ts' 'test/integration/**/*.test.ts' 'test/conformance-oauth/**/*.test.ts'",
20
20
  "format": "biome check --write .",
21
- "check": "biome check ."
21
+ "check": "biome check .",
22
+ "docs:dev": "vitepress dev docs",
23
+ "docs:build": "vitepress build docs",
24
+ "docs:preview": "vitepress preview docs"
22
25
  },
23
26
  "keywords": [
24
27
  "mcp",
@@ -51,7 +54,7 @@
51
54
  "dependencies": {
52
55
  "@hono/node-server": "^1.19.9",
53
56
  "@modelcontextprotocol/sdk": "^1.25.3",
54
- "bcryptjs": "^2.4.3",
57
+ "bcryptjs": "^3.0.0",
55
58
  "hono": "^4.11.7",
56
59
  "pino": "^10.3.0",
57
60
  "pino-pretty": "^13.1.3",
@@ -59,9 +62,9 @@
59
62
  },
60
63
  "devDependencies": {
61
64
  "@biomejs/biome": "^2.3.14",
62
- "@types/bcryptjs": "^2.4.6",
63
65
  "@types/node": "^25.1.0",
64
66
  "tsx": "^4.21.0",
65
- "typescript": "^5.9.3"
67
+ "typescript": "^5.9.3",
68
+ "vitepress": "^1.6.4"
66
69
  }
67
70
  }
@@ -1,433 +0,0 @@
1
- import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
2
- import type { Context } from "hono";
3
- import type { McpManager } from "./manager.js";
4
- export declare function handleInitialize(c: Context, message: JSONRPCMessage): Response & import("hono").TypedResponse<{
5
- jsonrpc: string;
6
- id: string | number | undefined;
7
- result: {
8
- protocolVersion: string;
9
- capabilities: {
10
- tools: {
11
- listChanged: true;
12
- };
13
- resources: {
14
- listChanged: true;
15
- };
16
- prompts: {
17
- listChanged: true;
18
- };
19
- completions: {};
20
- };
21
- serverInfo: {
22
- name: string;
23
- version: string;
24
- };
25
- };
26
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">;
27
- export declare function handleInitialized(c: Context): Response & import("hono").TypedResponse<null, 202, "body">;
28
- export declare function handleToolsList(c: Context, message: JSONRPCMessage, mcpManager: McpManager): Response & import("hono").TypedResponse<{
29
- jsonrpc: string;
30
- id: string | number | undefined;
31
- result: {
32
- tools: {
33
- inputSchema: {
34
- [x: string]: import("hono/utils/types").JSONValue;
35
- type: "object";
36
- properties?: {
37
- [x: string]: never;
38
- } | undefined;
39
- required?: string[] | undefined;
40
- };
41
- name: string;
42
- description?: string | undefined;
43
- outputSchema?: {
44
- [x: string]: import("hono/utils/types").JSONValue;
45
- type: "object";
46
- properties?: {
47
- [x: string]: never;
48
- } | undefined;
49
- required?: string[] | undefined;
50
- } | undefined;
51
- annotations?: {
52
- title?: string | undefined;
53
- readOnlyHint?: boolean | undefined;
54
- destructiveHint?: boolean | undefined;
55
- idempotentHint?: boolean | undefined;
56
- openWorldHint?: boolean | undefined;
57
- } | undefined;
58
- execution?: {
59
- taskSupport?: "optional" | "required" | "forbidden" | undefined;
60
- } | undefined;
61
- _meta?: {
62
- [x: string]: import("hono/utils/types").JSONValue;
63
- } | undefined;
64
- icons?: {
65
- src: string;
66
- mimeType?: string | undefined;
67
- sizes?: string[] | undefined;
68
- theme?: "light" | "dark" | undefined;
69
- }[] | undefined;
70
- title?: string | undefined;
71
- }[];
72
- };
73
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">;
74
- export declare function handleToolsCall(c: Context, message: JSONRPCMessage, mcpManager: McpManager, params: {
75
- name: string;
76
- arguments?: Record<string, unknown>;
77
- }): Promise<(Response & import("hono").TypedResponse<{
78
- jsonrpc: string;
79
- id: string | number | undefined;
80
- result: {
81
- [x: string]: import("hono/utils/types").JSONValue;
82
- content: ({
83
- type: "text";
84
- text: string;
85
- annotations?: {
86
- audience?: ("user" | "assistant")[] | undefined;
87
- priority?: number | undefined;
88
- lastModified?: string | undefined;
89
- } | undefined;
90
- _meta?: {
91
- [x: string]: import("hono/utils/types").JSONValue;
92
- } | undefined;
93
- } | {
94
- type: "image";
95
- data: string;
96
- mimeType: string;
97
- annotations?: {
98
- audience?: ("user" | "assistant")[] | undefined;
99
- priority?: number | undefined;
100
- lastModified?: string | undefined;
101
- } | undefined;
102
- _meta?: {
103
- [x: string]: import("hono/utils/types").JSONValue;
104
- } | undefined;
105
- } | {
106
- type: "audio";
107
- data: string;
108
- mimeType: string;
109
- annotations?: {
110
- audience?: ("user" | "assistant")[] | undefined;
111
- priority?: number | undefined;
112
- lastModified?: string | undefined;
113
- } | undefined;
114
- _meta?: {
115
- [x: string]: import("hono/utils/types").JSONValue;
116
- } | undefined;
117
- } | {
118
- uri: string;
119
- name: string;
120
- type: "resource_link";
121
- description?: string | undefined;
122
- mimeType?: string | undefined;
123
- annotations?: {
124
- audience?: ("user" | "assistant")[] | undefined;
125
- priority?: number | undefined;
126
- lastModified?: string | undefined;
127
- } | undefined;
128
- _meta?: {
129
- [x: string]: import("hono/utils/types").JSONValue;
130
- } | undefined;
131
- icons?: {
132
- src: string;
133
- mimeType?: string | undefined;
134
- sizes?: string[] | undefined;
135
- theme?: "light" | "dark" | undefined;
136
- }[] | undefined;
137
- title?: string | undefined;
138
- } | {
139
- type: "resource";
140
- resource: {
141
- uri: string;
142
- text: string;
143
- mimeType?: string | undefined;
144
- _meta?: {
145
- [x: string]: import("hono/utils/types").JSONValue;
146
- } | undefined;
147
- } | {
148
- uri: string;
149
- blob: string;
150
- mimeType?: string | undefined;
151
- _meta?: {
152
- [x: string]: import("hono/utils/types").JSONValue;
153
- } | undefined;
154
- };
155
- annotations?: {
156
- audience?: ("user" | "assistant")[] | undefined;
157
- priority?: number | undefined;
158
- lastModified?: string | undefined;
159
- } | undefined;
160
- _meta?: {
161
- [x: string]: import("hono/utils/types").JSONValue;
162
- } | undefined;
163
- })[];
164
- _meta?: {
165
- [x: string]: import("hono/utils/types").JSONValue;
166
- progressToken?: string | number | undefined;
167
- "io.modelcontextprotocol/related-task"?: {
168
- taskId: string;
169
- } | undefined;
170
- } | undefined;
171
- structuredContent?: {
172
- [x: string]: import("hono/utils/types").JSONValue;
173
- } | undefined;
174
- isError?: boolean | undefined;
175
- };
176
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
177
- jsonrpc: string;
178
- id: string | number | undefined;
179
- error: {
180
- code: number;
181
- message: string;
182
- };
183
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
184
- export declare function handlePing(c: Context, message: JSONRPCMessage): Response & import("hono").TypedResponse<{
185
- jsonrpc: string;
186
- id: string | number | undefined;
187
- result: {};
188
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">;
189
- export declare function handleResourcesList(c: Context, message: JSONRPCMessage, mcpManager: McpManager): Response & import("hono").TypedResponse<{
190
- jsonrpc: string;
191
- id: string | number | undefined;
192
- result: {
193
- resources: {
194
- uri: string;
195
- name: string;
196
- description?: string | undefined;
197
- mimeType?: string | undefined;
198
- annotations?: {
199
- audience?: ("user" | "assistant")[] | undefined;
200
- priority?: number | undefined;
201
- lastModified?: string | undefined;
202
- } | undefined;
203
- _meta?: {
204
- [x: string]: import("hono/utils/types").JSONValue;
205
- } | undefined;
206
- icons?: {
207
- src: string;
208
- mimeType?: string | undefined;
209
- sizes?: string[] | undefined;
210
- theme?: "light" | "dark" | undefined;
211
- }[] | undefined;
212
- title?: string | undefined;
213
- }[];
214
- };
215
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">;
216
- export declare function handleResourcesRead(c: Context, message: JSONRPCMessage, mcpManager: McpManager, params: {
217
- uri: string;
218
- }): Promise<(Response & import("hono").TypedResponse<{
219
- jsonrpc: string;
220
- id: string | number | undefined;
221
- result: {
222
- [x: string]: import("hono/utils/types").JSONValue;
223
- contents: ({
224
- uri: string;
225
- text: string;
226
- mimeType?: string | undefined;
227
- _meta?: {
228
- [x: string]: import("hono/utils/types").JSONValue;
229
- } | undefined;
230
- } | {
231
- uri: string;
232
- blob: string;
233
- mimeType?: string | undefined;
234
- _meta?: {
235
- [x: string]: import("hono/utils/types").JSONValue;
236
- } | undefined;
237
- })[];
238
- _meta?: {
239
- [x: string]: import("hono/utils/types").JSONValue;
240
- progressToken?: string | number | undefined;
241
- "io.modelcontextprotocol/related-task"?: {
242
- taskId: string;
243
- } | undefined;
244
- } | undefined;
245
- };
246
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
247
- jsonrpc: string;
248
- id: string | number | undefined;
249
- error: {
250
- code: number;
251
- message: string;
252
- };
253
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
254
- export declare function handlePromptsList(c: Context, message: JSONRPCMessage, mcpManager: McpManager): Response & import("hono").TypedResponse<{
255
- jsonrpc: string;
256
- id: string | number | undefined;
257
- result: {
258
- prompts: {
259
- name: string;
260
- description?: string | undefined;
261
- arguments?: {
262
- name: string;
263
- description?: string | undefined;
264
- required?: boolean | undefined;
265
- }[] | undefined;
266
- _meta?: {
267
- [x: string]: import("hono/utils/types").JSONValue;
268
- } | undefined;
269
- icons?: {
270
- src: string;
271
- mimeType?: string | undefined;
272
- sizes?: string[] | undefined;
273
- theme?: "light" | "dark" | undefined;
274
- }[] | undefined;
275
- title?: string | undefined;
276
- }[];
277
- };
278
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">;
279
- export declare function handlePromptsGet(c: Context, message: JSONRPCMessage, mcpManager: McpManager, params: {
280
- name: string;
281
- arguments?: Record<string, string>;
282
- }): Promise<(Response & import("hono").TypedResponse<{
283
- jsonrpc: string;
284
- id: string | number | undefined;
285
- result: {
286
- [x: string]: import("hono/utils/types").JSONValue;
287
- messages: {
288
- role: "user" | "assistant";
289
- content: {
290
- type: "text";
291
- text: string;
292
- annotations?: {
293
- audience?: ("user" | "assistant")[] | undefined;
294
- priority?: number | undefined;
295
- lastModified?: string | undefined;
296
- } | undefined;
297
- _meta?: {
298
- [x: string]: import("hono/utils/types").JSONValue;
299
- } | undefined;
300
- } | {
301
- type: "image";
302
- data: string;
303
- mimeType: string;
304
- annotations?: {
305
- audience?: ("user" | "assistant")[] | undefined;
306
- priority?: number | undefined;
307
- lastModified?: string | undefined;
308
- } | undefined;
309
- _meta?: {
310
- [x: string]: import("hono/utils/types").JSONValue;
311
- } | undefined;
312
- } | {
313
- type: "audio";
314
- data: string;
315
- mimeType: string;
316
- annotations?: {
317
- audience?: ("user" | "assistant")[] | undefined;
318
- priority?: number | undefined;
319
- lastModified?: string | undefined;
320
- } | undefined;
321
- _meta?: {
322
- [x: string]: import("hono/utils/types").JSONValue;
323
- } | undefined;
324
- } | {
325
- uri: string;
326
- name: string;
327
- type: "resource_link";
328
- description?: string | undefined;
329
- mimeType?: string | undefined;
330
- annotations?: {
331
- audience?: ("user" | "assistant")[] | undefined;
332
- priority?: number | undefined;
333
- lastModified?: string | undefined;
334
- } | undefined;
335
- _meta?: {
336
- [x: string]: import("hono/utils/types").JSONValue;
337
- } | undefined;
338
- icons?: {
339
- src: string;
340
- mimeType?: string | undefined;
341
- sizes?: string[] | undefined;
342
- theme?: "light" | "dark" | undefined;
343
- }[] | undefined;
344
- title?: string | undefined;
345
- } | {
346
- type: "resource";
347
- resource: {
348
- uri: string;
349
- text: string;
350
- mimeType?: string | undefined;
351
- _meta?: {
352
- [x: string]: import("hono/utils/types").JSONValue;
353
- } | undefined;
354
- } | {
355
- uri: string;
356
- blob: string;
357
- mimeType?: string | undefined;
358
- _meta?: {
359
- [x: string]: import("hono/utils/types").JSONValue;
360
- } | undefined;
361
- };
362
- annotations?: {
363
- audience?: ("user" | "assistant")[] | undefined;
364
- priority?: number | undefined;
365
- lastModified?: string | undefined;
366
- } | undefined;
367
- _meta?: {
368
- [x: string]: import("hono/utils/types").JSONValue;
369
- } | undefined;
370
- };
371
- }[];
372
- _meta?: {
373
- [x: string]: import("hono/utils/types").JSONValue;
374
- progressToken?: string | number | undefined;
375
- "io.modelcontextprotocol/related-task"?: {
376
- taskId: string;
377
- } | undefined;
378
- } | undefined;
379
- description?: string | undefined;
380
- };
381
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
382
- jsonrpc: string;
383
- id: string | number | undefined;
384
- error: {
385
- code: number;
386
- message: string;
387
- };
388
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
389
- export declare function handleCompletionComplete(c: Context, message: JSONRPCMessage, mcpManager: McpManager, params: {
390
- ref: {
391
- type: string;
392
- name?: string;
393
- uri?: string;
394
- };
395
- argument: {
396
- name: string;
397
- value: string;
398
- };
399
- }): Promise<(Response & import("hono").TypedResponse<{
400
- jsonrpc: string;
401
- id: string | number | undefined;
402
- result: {
403
- [x: string]: import("hono/utils/types").JSONValue;
404
- completion: {
405
- [x: string]: import("hono/utils/types").JSONValue;
406
- values: string[];
407
- total?: number | undefined;
408
- hasMore?: boolean | undefined;
409
- };
410
- _meta?: {
411
- [x: string]: import("hono/utils/types").JSONValue;
412
- progressToken?: string | number | undefined;
413
- "io.modelcontextprotocol/related-task"?: {
414
- taskId: string;
415
- } | undefined;
416
- } | undefined;
417
- };
418
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
419
- jsonrpc: string;
420
- id: string | number | undefined;
421
- error: {
422
- code: number;
423
- message: string;
424
- };
425
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
426
- export declare function handleMethodNotFound(c: Context, message: JSONRPCMessage, method: string): Response & import("hono").TypedResponse<{
427
- jsonrpc: string;
428
- id: string | number | null;
429
- error: {
430
- code: number;
431
- message: string;
432
- };
433
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">;
@@ -1,144 +0,0 @@
1
- import { NAME, VERSION } from "../version.js";
2
- function getMessageId(message) {
3
- return "id" in message ? message.id : undefined;
4
- }
5
- export function handleInitialize(c, message) {
6
- return c.json({
7
- jsonrpc: "2.0",
8
- id: getMessageId(message),
9
- result: {
10
- protocolVersion: "2025-11-25",
11
- capabilities: {
12
- tools: { listChanged: true },
13
- resources: { listChanged: true },
14
- prompts: { listChanged: true },
15
- completions: {},
16
- },
17
- serverInfo: {
18
- name: NAME,
19
- version: VERSION,
20
- },
21
- },
22
- });
23
- }
24
- export function handleInitialized(c) {
25
- return c.body(null, 202);
26
- }
27
- export function handleToolsList(c, message, mcpManager) {
28
- const tools = mcpManager.listTools();
29
- return c.json({
30
- jsonrpc: "2.0",
31
- id: getMessageId(message),
32
- result: { tools },
33
- });
34
- }
35
- export async function handleToolsCall(c, message, mcpManager, params) {
36
- try {
37
- const result = await mcpManager.callTool(params.name, params.arguments ?? {});
38
- return c.json({
39
- jsonrpc: "2.0",
40
- id: getMessageId(message),
41
- result,
42
- });
43
- }
44
- catch (error) {
45
- return c.json({
46
- jsonrpc: "2.0",
47
- id: getMessageId(message),
48
- error: {
49
- code: -32603,
50
- message: error instanceof Error ? error.message : "Internal error",
51
- },
52
- });
53
- }
54
- }
55
- export function handlePing(c, message) {
56
- return c.json({
57
- jsonrpc: "2.0",
58
- id: getMessageId(message),
59
- result: {},
60
- });
61
- }
62
- export function handleResourcesList(c, message, mcpManager) {
63
- const resources = mcpManager.listResources();
64
- return c.json({
65
- jsonrpc: "2.0",
66
- id: getMessageId(message),
67
- result: { resources },
68
- });
69
- }
70
- export async function handleResourcesRead(c, message, mcpManager, params) {
71
- try {
72
- const result = await mcpManager.readResource(params.uri);
73
- return c.json({
74
- jsonrpc: "2.0",
75
- id: getMessageId(message),
76
- result,
77
- });
78
- }
79
- catch (error) {
80
- return c.json({
81
- jsonrpc: "2.0",
82
- id: getMessageId(message),
83
- error: {
84
- code: -32603,
85
- message: error instanceof Error ? error.message : "Internal error",
86
- },
87
- });
88
- }
89
- }
90
- export function handlePromptsList(c, message, mcpManager) {
91
- const prompts = mcpManager.listPrompts();
92
- return c.json({
93
- jsonrpc: "2.0",
94
- id: getMessageId(message),
95
- result: { prompts },
96
- });
97
- }
98
- export async function handlePromptsGet(c, message, mcpManager, params) {
99
- try {
100
- const result = await mcpManager.getPrompt(params.name, params.arguments);
101
- return c.json({
102
- jsonrpc: "2.0",
103
- id: getMessageId(message),
104
- result,
105
- });
106
- }
107
- catch (error) {
108
- return c.json({
109
- jsonrpc: "2.0",
110
- id: getMessageId(message),
111
- error: {
112
- code: -32603,
113
- message: error instanceof Error ? error.message : "Internal error",
114
- },
115
- });
116
- }
117
- }
118
- export async function handleCompletionComplete(c, message, mcpManager, params) {
119
- try {
120
- const result = await mcpManager.complete(params.ref, params.argument);
121
- return c.json({
122
- jsonrpc: "2.0",
123
- id: getMessageId(message),
124
- result,
125
- });
126
- }
127
- catch (error) {
128
- return c.json({
129
- jsonrpc: "2.0",
130
- id: getMessageId(message),
131
- error: {
132
- code: -32603,
133
- message: error instanceof Error ? error.message : "Internal error",
134
- },
135
- });
136
- }
137
- }
138
- export function handleMethodNotFound(c, message, method) {
139
- return c.json({
140
- jsonrpc: "2.0",
141
- id: getMessageId(message) ?? null,
142
- error: { code: -32601, message: `Method not found: ${method}` },
143
- });
144
- }