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 +13 -115
- package/dist/config/loader.js +9 -1
- package/dist/config/schema.d.ts +8 -0
- package/dist/config/schema.js +12 -0
- package/dist/mcp/handler.d.ts +22 -0
- package/dist/mcp/handler.js +148 -0
- package/dist/mcp/manager.d.ts +5 -12
- package/dist/mcp/manager.js +70 -57
- package/dist/server.js +4 -56
- package/package.json +8 -5
- package/dist/mcp/handlers.d.ts +0 -433
- package/dist/mcp/handlers.js +0 -144
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
|
|
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
|
|
11
|
-
-
|
|
10
|
+
- Runs multiple servers behind a single HTTP endpoint
|
|
11
|
+
- Supports Tools, Resources & Prompts
|
|
12
12
|
- Namespaces with `servername__` prefix to avoid collisions
|
|
13
|
-
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
package/dist/config/loader.js
CHANGED
|
@@ -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, (
|
|
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;
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -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>;
|
package/dist/config/schema.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/mcp/manager.d.ts
CHANGED
|
@@ -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(
|
|
37
|
-
readResource(
|
|
38
|
-
getPrompt(
|
|
39
|
-
complete(
|
|
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
|
}
|
package/dist/mcp/manager.js
CHANGED
|
@@ -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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
122
|
-
logger.debug({ mcp: config.name }, "
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
139
|
-
logger.debug({ mcp: config.name }, "
|
|
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(
|
|
221
|
-
const mcpName = this.toolToMcp.get(
|
|
230
|
+
async callTool(params) {
|
|
231
|
+
const mcpName = this.toolToMcp.get(params.name);
|
|
222
232
|
if (!mcpName) {
|
|
223
|
-
logger.warn(`Unknown tool called: ${
|
|
224
|
-
throw new Error(`Unknown tool: ${
|
|
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: ${
|
|
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,
|
|
234
|
-
:
|
|
235
|
-
logger.info({
|
|
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:
|
|
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: ${
|
|
255
|
+
}, `Tool result: ${params.name}`);
|
|
246
256
|
return result;
|
|
247
257
|
}
|
|
248
|
-
async readResource(
|
|
249
|
-
const mcpName = this.resourceToMcp.get(
|
|
258
|
+
async readResource(params) {
|
|
259
|
+
const mcpName = this.resourceToMcp.get(params.uri);
|
|
250
260
|
if (!mcpName) {
|
|
251
|
-
logger.warn(`Unknown resource: ${
|
|
252
|
-
throw new Error(`Unknown resource: ${
|
|
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: ${
|
|
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,
|
|
262
|
-
:
|
|
263
|
-
logger.info(`Resource read: ${
|
|
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: ${
|
|
277
|
+
logger.info({ duration: `${duration}ms` }, `Resource result: ${params.uri}`);
|
|
268
278
|
return result;
|
|
269
279
|
}
|
|
270
|
-
async getPrompt(
|
|
271
|
-
const mcpName = this.promptToMcp.get(
|
|
280
|
+
async getPrompt(params) {
|
|
281
|
+
const mcpName = this.promptToMcp.get(params.name);
|
|
272
282
|
if (!mcpName) {
|
|
273
|
-
logger.warn(`Unknown prompt: ${
|
|
274
|
-
throw new Error(`Unknown prompt: ${
|
|
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: ${
|
|
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,
|
|
284
|
-
:
|
|
285
|
-
logger.info({
|
|
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:
|
|
299
|
+
arguments: params.arguments,
|
|
290
300
|
});
|
|
291
301
|
const duration = Date.now() - startTime;
|
|
292
|
-
logger.info({ duration: `${duration}ms` }, `Prompt result: ${
|
|
302
|
+
logger.info({ duration: `${duration}ms` }, `Prompt result: ${params.name}`);
|
|
293
303
|
return result;
|
|
294
304
|
}
|
|
295
|
-
async complete(
|
|
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({
|
|
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 {
|
|
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
|
-
|
|
213
|
-
protectedRoutes.post("/
|
|
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.
|
|
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": "^
|
|
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
|
}
|
package/dist/mcp/handlers.d.ts
DELETED
|
@@ -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">;
|
package/dist/mcp/handlers.js
DELETED
|
@@ -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
|
-
}
|