@luutuankiet/mcp-proxy-shim 1.0.0
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 +243 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +360 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# @luutuankiet/mcp-proxy-shim
|
|
2
|
+
|
|
3
|
+
**Stdio MCP shim for [mcpproxy-go](https://github.com/smart-mcp-proxy/mcpproxy-go)** — eliminates `args_json` string escaping overhead for LLM clients.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
mcpproxy-go's `/mcp/call` mode uses **generic dispatcher tools** (`call_tool_read`, `call_tool_write`, `call_tool_destructive`) that accept arguments as `args_json: string` — a pre-serialized JSON string. This is a sound design choice (one schema covers any upstream tool), but it creates real pain for LLM consumers:
|
|
8
|
+
|
|
9
|
+
### Before (what the LLM must produce)
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"name": "call_tool_read",
|
|
14
|
+
"arguments": {
|
|
15
|
+
"name": "myserver:read_files",
|
|
16
|
+
"args_json": "{\"files\":[{\"path\":\"src/index.ts\",\"head\":20}]}"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The LLM must escape every quote, every nested object, every bracket. For complex tool calls (file edits with match_text containing code), this becomes:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
"args_json": "{\"files\":[{\"path\":\"src/app.ts\",\"edits\":[{\"match_text\":\"function hello() {\\n return \\\"world\\\";\\n}\",\"new_string\":\"function hello() {\\n return \\\"universe\\\";\\n}\"}]}]}"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This is **~400 tokens of overhead per call**, and LLMs frequently produce malformed payloads (mismatched escaping, missing backslashes).
|
|
28
|
+
|
|
29
|
+
### After (with the shim)
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"name": "call_tool_read",
|
|
34
|
+
"arguments": {
|
|
35
|
+
"name": "myserver:read_files",
|
|
36
|
+
"args": {
|
|
37
|
+
"files": [{"path": "src/index.ts", "head": 20}]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Native JSON. No escaping. ~50 tokens. Zero malformed payloads.
|
|
44
|
+
|
|
45
|
+
### Impact at Scale
|
|
46
|
+
|
|
47
|
+
| Metric | Without shim | With shim | Savings |
|
|
48
|
+
|--------|-------------|-----------|---------|
|
|
49
|
+
| Tokens per call | ~400 | ~50 | **87%** |
|
|
50
|
+
| 30-call session overhead | ~12,000 tokens | ~1,500 tokens | **10,500 tokens saved** |
|
|
51
|
+
| Escaping bugs | Frequent | Zero | — |
|
|
52
|
+
| Edit operations (worst case) | ~500 tokens | ~200 tokens | **60%** |
|
|
53
|
+
|
|
54
|
+
## How It Works
|
|
55
|
+
|
|
56
|
+
```mermaid
|
|
57
|
+
sequenceDiagram
|
|
58
|
+
participant Client as MCP Client<br/>Claude Code / Cursor / etc
|
|
59
|
+
participant Shim as mcp-proxy-shim<br/>stdio
|
|
60
|
+
participant Proxy as mcpproxy-go<br/>StreamableHTTP
|
|
61
|
+
|
|
62
|
+
Note over Client,Proxy: Connection Setup
|
|
63
|
+
Client->>Shim: initialize (stdio)
|
|
64
|
+
Shim->>Proxy: initialize (HTTP)
|
|
65
|
+
Proxy-->>Shim: capabilities + session ID
|
|
66
|
+
Shim-->>Client: capabilities
|
|
67
|
+
|
|
68
|
+
Note over Client,Proxy: Tool Discovery
|
|
69
|
+
Client->>Shim: tools/list
|
|
70
|
+
Shim->>Proxy: tools/list
|
|
71
|
+
Proxy-->>Shim: tools with args_json: string
|
|
72
|
+
Note over Shim: Transform 3 schemas<br/>args_json:string → args:object<br/>All others: passthrough
|
|
73
|
+
Shim-->>Client: tools with args: object
|
|
74
|
+
|
|
75
|
+
Note over Client,Proxy: Tool Call (the magic)
|
|
76
|
+
Client->>Shim: call_tool_read<br/>args: {files: [{path: "..."}]}
|
|
77
|
+
Note over Shim: Serialize<br/>args_json = JSON.stringify(args)
|
|
78
|
+
Shim->>Proxy: call_tool_read<br/>args_json: '{"files":[...]}'
|
|
79
|
+
Proxy-->>Shim: file content
|
|
80
|
+
Shim-->>Client: file content (passthrough)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### What Gets Transformed
|
|
84
|
+
|
|
85
|
+
Only 3 tools are transformed. **Everything else passes through unchanged:**
|
|
86
|
+
|
|
87
|
+
| Tool | Schema change | All other fields |
|
|
88
|
+
|------|--------------|-----------------|
|
|
89
|
+
| `call_tool_read` | `args_json: string` → `args: object` | Unchanged |
|
|
90
|
+
| `call_tool_write` | `args_json: string` → `args: object` | Unchanged |
|
|
91
|
+
| `call_tool_destructive` | `args_json: string` → `args: object` | Unchanged |
|
|
92
|
+
| `retrieve_tools` | — | Passthrough |
|
|
93
|
+
| `upstream_servers` | — | Passthrough |
|
|
94
|
+
| `code_execution` | — | Passthrough |
|
|
95
|
+
| `read_cache` | — | Passthrough |
|
|
96
|
+
| All others | — | Passthrough |
|
|
97
|
+
|
|
98
|
+
## Quick Start
|
|
99
|
+
|
|
100
|
+
Add to your `.mcp.json` — no install needed, `npx` fetches on first run:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"mcpServers": {
|
|
105
|
+
"proxy": {
|
|
106
|
+
"type": "stdio",
|
|
107
|
+
"command": "npx",
|
|
108
|
+
"args": ["-y", "@luutuankiet/mcp-proxy-shim"],
|
|
109
|
+
"env": {
|
|
110
|
+
"MCP_URL": "https://your-proxy.example.com/mcp/?apikey=YOUR_KEY"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Or run directly from the CLI:
|
|
118
|
+
```bash
|
|
119
|
+
MCP_URL="https://your-proxy/mcp/?apikey=KEY" npx @luutuankiet/mcp-proxy-shim
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Why Not `/mcp/all`?
|
|
123
|
+
|
|
124
|
+
mcpproxy-go exposes two routing modes:
|
|
125
|
+
|
|
126
|
+
```mermaid
|
|
127
|
+
flowchart LR
|
|
128
|
+
subgraph "/mcp/all (direct mode)"
|
|
129
|
+
A1[Client] --> B1[myserver__read_files<br/>native schema]
|
|
130
|
+
A1 --> C1[myserver__edit_files<br/>native schema]
|
|
131
|
+
A1 --> D1[github__get_user<br/>native schema]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
subgraph "/mcp/call (retrieve_tools mode)"
|
|
135
|
+
A2[Client] --> B2[retrieve_tools<br/>BM25 search]
|
|
136
|
+
A2 --> C2[call_tool_read<br/>generic dispatcher]
|
|
137
|
+
A2 --> D2[upstream_servers<br/>add/remove/patch]
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**`/mcp/all`** gives each tool its native schema (no `args_json`), but **freezes the tool list at connect time**. Add a server? You must reconnect.
|
|
142
|
+
|
|
143
|
+
**`/mcp/call`** supports **dynamic server management** — add a YNAB server, a BigQuery connector, or a GitHub integration, and `retrieve_tools` discovers the new tools instantly. No reconnect.
|
|
144
|
+
|
|
145
|
+
We tested this live: added a YNAB financial tool mid-session → 43 new tools appeared immediately via `retrieve_tools`. The shim preserves this dynamic behavior while eliminating escaping overhead.
|
|
146
|
+
|
|
147
|
+
## Real-World Example: Dynamic Tool Discovery
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# 1. User adds YNAB server to mcpproxy-go (via UI or API)
|
|
151
|
+
|
|
152
|
+
# 2. Client discovers new tools (no reconnect!)
|
|
153
|
+
→ retrieve_tools("ynab accounts balance")
|
|
154
|
+
← [ynab__getAccounts, ynab__getTransactions, ynab__getPlans, ...]
|
|
155
|
+
|
|
156
|
+
# 3. Client calls with native args (shim handles serialization)
|
|
157
|
+
→ call_tool_read {
|
|
158
|
+
name: "utils:ynab__getAccounts",
|
|
159
|
+
args: { plan_id: "abc-123" } // ← native object, not escaped string
|
|
160
|
+
}
|
|
161
|
+
← [{ name: "Checking", balance: 1500000, ... }]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Configuration
|
|
165
|
+
|
|
166
|
+
| Environment variable | Default | Description |
|
|
167
|
+
|---------------------|---------|-------------|
|
|
168
|
+
| `MCP_URL` | **(required)** | mcpproxy-go StreamableHTTP endpoint |
|
|
169
|
+
| `https_proxy` / `HTTPS_PROXY` | — | HTTPS proxy (auto-detected via undici ProxyAgent) |
|
|
170
|
+
|
|
171
|
+
## Architecture Details
|
|
172
|
+
|
|
173
|
+
### Session Management
|
|
174
|
+
|
|
175
|
+
- Initializes upstream MCP session on startup via `initialize` + `notifications/initialized`
|
|
176
|
+
- Auto-reinitializes on session expiry (e.g., upstream restart, 405 responses)
|
|
177
|
+
- Retries transient failures with exponential backoff (1s, 2s, max 2 retries)
|
|
178
|
+
- Refreshes tool list on every `tools/list` request (upstream servers may have changed)
|
|
179
|
+
|
|
180
|
+
### Backward Compatibility
|
|
181
|
+
|
|
182
|
+
If a caller sends `args_json` directly (old style), the shim **passes it through unchanged**. You can migrate gradually — no breaking changes.
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
// Both work:
|
|
186
|
+
{ "args": { "files": [...] } } // ← new: native object (shim serializes)
|
|
187
|
+
{ "args_json": "{\"files\":[...]}" } // ← old: pre-serialized (shim passes through)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### HTTPS Proxy Support
|
|
191
|
+
|
|
192
|
+
Node.js's built-in `fetch` does **not** honor `https_proxy` environment variables. The shim uses [undici](https://github.com/nodejs/undici)'s `ProxyAgent` to automatically route through HTTPS proxies when detected. This makes it work in cloud sandboxes (e.g., claude.ai/code) where HTTPS is routed through envoy sidecars.
|
|
193
|
+
|
|
194
|
+
### SSE Support
|
|
195
|
+
|
|
196
|
+
StreamableHTTP responses may arrive as either `application/json` or `text/event-stream` (SSE). The shim detects the content type and handles both transparently.
|
|
197
|
+
|
|
198
|
+
### SDK Bugs Worked Around
|
|
199
|
+
|
|
200
|
+
| Bug | Impact | Workaround in shim |
|
|
201
|
+
|-----|--------|------------|
|
|
202
|
+
| [typescript-sdk #893](https://github.com/modelcontextprotocol/typescript-sdk/issues/893) | `McpServer.registerTool()` breaks dynamic tool registration after client connects | Uses low-level `Server` class with `setRequestHandler()` |
|
|
203
|
+
| [typescript-sdk #396](https://github.com/modelcontextprotocol/typescript-sdk/issues/396) | `StreamableHTTPClientTransport` 2nd `callTool` times out due to broken session multiplexing | Uses plain `fetch` for upstream connection (no SDK client) |
|
|
204
|
+
| [claude-code #13646](https://github.com/anthropics/claude-code/issues/13646) | Client ignores `notifications/tools/list_changed` | Refreshes tools on each `tools/list` request instead of relying on notifications |
|
|
205
|
+
|
|
206
|
+
## Development
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
git clone https://github.com/luutuankiet/sandbox-cc
|
|
210
|
+
cd sandbox-cc/mcp-shim
|
|
211
|
+
npm install
|
|
212
|
+
npm run build
|
|
213
|
+
npm start # starts the shim (connects upstream, waits for stdio)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Testing
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# Send MCP JSON-RPC over stdin:
|
|
220
|
+
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}' \
|
|
221
|
+
| MCP_URL="https://your-proxy/mcp/?apikey=KEY" node dist/index.js
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Logs go to stderr (stdout is the stdio transport):
|
|
225
|
+
```
|
|
226
|
+
[mcp-shim] Upstream: https://your-proxy.example.com/mcp/?apikey=KEY
|
|
227
|
+
[mcp-shim] Initializing upstream session...
|
|
228
|
+
[mcp-shim] Session ID: mcp-session-...
|
|
229
|
+
[mcp-shim] Fetched 10 upstream tools
|
|
230
|
+
[mcp-shim] Ready: 10 tools (3 with schema transform)
|
|
231
|
+
[mcp-shim] Stdio transport connected — shim is live
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Contributing
|
|
235
|
+
|
|
236
|
+
The ideal long-term fix is native `args: object` support in mcpproxy-go's `/mcp/call` mode. This shim is a client-side workaround until that lands. If you're a mcpproxy-go maintainer interested in this, see:
|
|
237
|
+
|
|
238
|
+
- **Why args_json is a string:** `internal/server/mcp.go` — generic dispatchers need a static schema that accepts any upstream tool's arguments
|
|
239
|
+
- **Possible server-side fix:** Accept both `args_json: string` and `args: object` in the same schema, with `args` taking precedence when present
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Stdio Shim
|
|
4
|
+
*
|
|
5
|
+
* A schema-transforming MCP proxy that sits between Claude Code (stdio)
|
|
6
|
+
* and mcpproxy-go (StreamableHTTP).
|
|
7
|
+
*
|
|
8
|
+
* What it does:
|
|
9
|
+
* - Passes through ALL upstream tools unchanged (retrieve_tools, upstream_servers, etc.)
|
|
10
|
+
* - Transforms call_tool_read / call_tool_write / call_tool_destructive schemas:
|
|
11
|
+
* upstream args_json:string → downstream args:object
|
|
12
|
+
* - On tool call: serializes args back to args_json before forwarding upstream
|
|
13
|
+
*
|
|
14
|
+
* Why not use SDK StreamableHTTPClientTransport:
|
|
15
|
+
* - Bug #396: 2nd callTool times out (broken session multiplexing)
|
|
16
|
+
* - We use plain fetch for upstream — reliable, simple, zero SDK client bugs
|
|
17
|
+
*
|
|
18
|
+
* Why low-level Server class (not McpServer):
|
|
19
|
+
* - Bug #893: McpServer.registerTool() breaks dynamic registration post-connect
|
|
20
|
+
* - Low-level Server.setRequestHandler() works correctly
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* MCP_URL="https://proxy.example.com/mcp/?apikey=KEY" npx @luutuankiet/mcp-proxy-shim
|
|
24
|
+
*
|
|
25
|
+
* .mcp.json entry:
|
|
26
|
+
* { "mcpServers": { "proxy": { "type": "stdio", "command": "npx", "args": ["-y", "@luutuankiet/mcp-proxy-shim"], "env": { "MCP_URL": "..." } } } }
|
|
27
|
+
*/
|
|
28
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Stdio Shim
|
|
4
|
+
*
|
|
5
|
+
* A schema-transforming MCP proxy that sits between Claude Code (stdio)
|
|
6
|
+
* and mcpproxy-go (StreamableHTTP).
|
|
7
|
+
*
|
|
8
|
+
* What it does:
|
|
9
|
+
* - Passes through ALL upstream tools unchanged (retrieve_tools, upstream_servers, etc.)
|
|
10
|
+
* - Transforms call_tool_read / call_tool_write / call_tool_destructive schemas:
|
|
11
|
+
* upstream args_json:string → downstream args:object
|
|
12
|
+
* - On tool call: serializes args back to args_json before forwarding upstream
|
|
13
|
+
*
|
|
14
|
+
* Why not use SDK StreamableHTTPClientTransport:
|
|
15
|
+
* - Bug #396: 2nd callTool times out (broken session multiplexing)
|
|
16
|
+
* - We use plain fetch for upstream — reliable, simple, zero SDK client bugs
|
|
17
|
+
*
|
|
18
|
+
* Why low-level Server class (not McpServer):
|
|
19
|
+
* - Bug #893: McpServer.registerTool() breaks dynamic registration post-connect
|
|
20
|
+
* - Low-level Server.setRequestHandler() works correctly
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* MCP_URL="https://proxy.example.com/mcp/?apikey=KEY" npx @luutuankiet/mcp-proxy-shim
|
|
24
|
+
*
|
|
25
|
+
* .mcp.json entry:
|
|
26
|
+
* { "mcpServers": { "proxy": { "type": "stdio", "command": "npx", "args": ["-y", "@luutuankiet/mcp-proxy-shim"], "env": { "MCP_URL": "..." } } } }
|
|
27
|
+
*/
|
|
28
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
29
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
30
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
31
|
+
// Proxy support — Node 22 built-in undici honors https_proxy via ProxyAgent
|
|
32
|
+
// This makes the shim work in both native sessions (no proxy) and cloud sandboxes
|
|
33
|
+
import { createRequire } from "node:module";
|
|
34
|
+
const _require = createRequire(import.meta.url);
|
|
35
|
+
const { ProxyAgent } = _require("undici");
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Config
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
const UPSTREAM_URL = process.env.MCP_URL ?? (() => {
|
|
40
|
+
console.error("[mcp-shim] Fatal: MCP_URL environment variable is required.");
|
|
41
|
+
console.error("[mcp-shim] Example: MCP_URL='https://your-proxy/mcp/?apikey=KEY' mcp-proxy-shim");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
})();
|
|
44
|
+
const REQUEST_TIMEOUT_MS = 120_000;
|
|
45
|
+
const MAX_RETRIES = 2;
|
|
46
|
+
// Auto-detect HTTPS proxy from environment
|
|
47
|
+
const PROXY_URL = process.env.https_proxy || process.env.HTTPS_PROXY || "";
|
|
48
|
+
const proxyDispatcher = PROXY_URL ? new ProxyAgent(PROXY_URL) : undefined;
|
|
49
|
+
// Tools whose schemas get transformed (args_json:string → args:object)
|
|
50
|
+
const CALL_TOOL_NAMES = new Set([
|
|
51
|
+
"call_tool_read",
|
|
52
|
+
"call_tool_write",
|
|
53
|
+
"call_tool_destructive",
|
|
54
|
+
]);
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Upstream MCP session (manual HTTP — avoids SDK client bugs)
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
let sessionId = null;
|
|
59
|
+
let reqId = 0;
|
|
60
|
+
function log(...args) {
|
|
61
|
+
// stderr only — stdout is the stdio transport
|
|
62
|
+
console.error("[mcp-shim]", ...args);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Send a JSON-RPC request/notification to upstream mcpproxy-go.
|
|
66
|
+
* Handles session header, timeouts, and basic retry on transient failures.
|
|
67
|
+
*/
|
|
68
|
+
async function mcpRequest(method, params, isNotification = false) {
|
|
69
|
+
const body = { jsonrpc: "2.0", method, params };
|
|
70
|
+
if (!isNotification) {
|
|
71
|
+
body.id = ++reqId;
|
|
72
|
+
}
|
|
73
|
+
const headers = {
|
|
74
|
+
"Content-Type": "application/json",
|
|
75
|
+
Accept: "application/json, text/event-stream",
|
|
76
|
+
};
|
|
77
|
+
if (sessionId) {
|
|
78
|
+
headers["Mcp-Session-Id"] = sessionId;
|
|
79
|
+
}
|
|
80
|
+
let lastError = null;
|
|
81
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
82
|
+
try {
|
|
83
|
+
// AbortSignal.timeout is self-cleaning (no timer leak on fetch failure)
|
|
84
|
+
const fetchOpts = {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers,
|
|
87
|
+
body: JSON.stringify(body),
|
|
88
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
89
|
+
};
|
|
90
|
+
if (proxyDispatcher) {
|
|
91
|
+
fetchOpts.dispatcher = proxyDispatcher;
|
|
92
|
+
}
|
|
93
|
+
const resp = await fetch(UPSTREAM_URL, fetchOpts);
|
|
94
|
+
// Capture session ID from response headers
|
|
95
|
+
const sid = resp.headers.get("mcp-session-id");
|
|
96
|
+
if (sid)
|
|
97
|
+
sessionId = sid;
|
|
98
|
+
// Notifications get 202 with no body
|
|
99
|
+
if (isNotification || resp.status === 202) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (!resp.ok) {
|
|
103
|
+
const text = await resp.text();
|
|
104
|
+
throw new Error(`Upstream HTTP ${resp.status}: ${text.slice(0, 500)}`);
|
|
105
|
+
}
|
|
106
|
+
// Handle SSE vs JSON response
|
|
107
|
+
const contentType = resp.headers.get("content-type") || "";
|
|
108
|
+
if (contentType.includes("text/event-stream")) {
|
|
109
|
+
// StreamableHTTP can return SSE — collect all data events
|
|
110
|
+
return await parseSseResponse(resp);
|
|
111
|
+
}
|
|
112
|
+
return (await resp.json());
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
lastError = err;
|
|
116
|
+
if (attempt < MAX_RETRIES) {
|
|
117
|
+
const delay = 1000 * 2 ** attempt; // 1s, 2s
|
|
118
|
+
log(`Retry ${attempt + 1}/${MAX_RETRIES} after ${delay}ms:`, lastError.message);
|
|
119
|
+
await sleep(delay);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
throw lastError || new Error("mcpRequest failed");
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Parse an SSE response stream and extract the JSON-RPC result.
|
|
127
|
+
* StreamableHTTP servers may respond with SSE for long-running operations.
|
|
128
|
+
*/
|
|
129
|
+
async function parseSseResponse(resp) {
|
|
130
|
+
const text = await resp.text();
|
|
131
|
+
const lines = text.split("\n");
|
|
132
|
+
let lastData = null;
|
|
133
|
+
for (const line of lines) {
|
|
134
|
+
if (line.startsWith("data: ")) {
|
|
135
|
+
lastData = line.slice(6);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (lastData) {
|
|
139
|
+
try {
|
|
140
|
+
return JSON.parse(lastData);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// fall through
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
throw new Error("No valid JSON-RPC response in SSE stream");
|
|
147
|
+
}
|
|
148
|
+
function sleep(ms) {
|
|
149
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
150
|
+
}
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Upstream session lifecycle
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
async function initUpstream() {
|
|
155
|
+
log("Initializing upstream session...");
|
|
156
|
+
const resp = await mcpRequest("initialize", {
|
|
157
|
+
protocolVersion: "2024-11-05",
|
|
158
|
+
capabilities: {},
|
|
159
|
+
clientInfo: { name: "mcp-stdio-shim", version: "1.0.0" },
|
|
160
|
+
});
|
|
161
|
+
if (!sessionId) {
|
|
162
|
+
throw new Error("No session ID received from upstream after initialize");
|
|
163
|
+
}
|
|
164
|
+
log("Session ID:", sessionId.slice(0, 12) + "...");
|
|
165
|
+
// Send initialized notification (MCP spec requirement)
|
|
166
|
+
await mcpRequest("notifications/initialized", {}, true);
|
|
167
|
+
}
|
|
168
|
+
async function ensureSession() {
|
|
169
|
+
if (!sessionId) {
|
|
170
|
+
await initUpstream();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Re-initialize on session expiry (e.g., upstream restart).
|
|
175
|
+
* Returns true if re-init succeeded.
|
|
176
|
+
*/
|
|
177
|
+
async function reinitOnExpiry() {
|
|
178
|
+
log("Session may have expired — re-initializing...");
|
|
179
|
+
sessionId = null;
|
|
180
|
+
try {
|
|
181
|
+
await initUpstream();
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
log("Re-init failed:", err.message);
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Transform call_tool_* schemas: replace args_json:string with args:object.
|
|
191
|
+
* All other tools pass through unchanged.
|
|
192
|
+
*/
|
|
193
|
+
function transformToolSchema(tool) {
|
|
194
|
+
if (!CALL_TOOL_NAMES.has(tool.name))
|
|
195
|
+
return tool;
|
|
196
|
+
if (!tool.inputSchema?.properties)
|
|
197
|
+
return tool;
|
|
198
|
+
const props = { ...tool.inputSchema.properties };
|
|
199
|
+
// Only transform if args_json exists
|
|
200
|
+
if (!("args_json" in props))
|
|
201
|
+
return tool;
|
|
202
|
+
// Remove args_json, add args:object
|
|
203
|
+
delete props.args_json;
|
|
204
|
+
props.args = {
|
|
205
|
+
type: "object",
|
|
206
|
+
description: "Tool arguments as a native JSON object. The shim serializes this to args_json before forwarding upstream.",
|
|
207
|
+
additionalProperties: true,
|
|
208
|
+
};
|
|
209
|
+
// Update required array
|
|
210
|
+
let required = tool.inputSchema.required;
|
|
211
|
+
if (required) {
|
|
212
|
+
required = required.map((r) => (r === "args_json" ? "args" : r));
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
...tool,
|
|
216
|
+
inputSchema: {
|
|
217
|
+
...tool.inputSchema,
|
|
218
|
+
properties: props,
|
|
219
|
+
...(required ? { required } : {}),
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* On tool call: if it's a call_tool_*, serialize the args object back to
|
|
225
|
+
* args_json string before forwarding upstream.
|
|
226
|
+
*/
|
|
227
|
+
function transformToolCallArgs(toolName, args) {
|
|
228
|
+
if (!CALL_TOOL_NAMES.has(toolName))
|
|
229
|
+
return args;
|
|
230
|
+
// If caller already sent args_json (backward compat), pass through
|
|
231
|
+
if ("args_json" in args)
|
|
232
|
+
return args;
|
|
233
|
+
// Transform: args → args_json
|
|
234
|
+
if ("args" in args) {
|
|
235
|
+
const { args: argsObj, ...rest } = args;
|
|
236
|
+
return {
|
|
237
|
+
...rest,
|
|
238
|
+
args_json: JSON.stringify(argsObj),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return args;
|
|
242
|
+
}
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Upstream tool list (cached, refreshed on tools/list)
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
let cachedTools = null;
|
|
247
|
+
async function fetchUpstreamTools() {
|
|
248
|
+
await ensureSession();
|
|
249
|
+
const resp = await mcpRequest("tools/list", {});
|
|
250
|
+
if (!resp || resp.error) {
|
|
251
|
+
throw new Error(`Failed to list upstream tools: ${JSON.stringify(resp?.error || "no response")}`);
|
|
252
|
+
}
|
|
253
|
+
const result = resp.result;
|
|
254
|
+
const tools = result?.tools || [];
|
|
255
|
+
log(`Fetched ${tools.length} upstream tools`);
|
|
256
|
+
return tools;
|
|
257
|
+
}
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// Main
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
async function main() {
|
|
262
|
+
// Mask credentials in log output (apikey params, proxy auth)
|
|
263
|
+
const maskUrl = (url) => url.replace(/apikey=[^&\s]+/gi, "apikey=***").replace(/\/\/[^@]*@/, "//***@");
|
|
264
|
+
log("Upstream:", maskUrl(UPSTREAM_URL));
|
|
265
|
+
if (proxyDispatcher) {
|
|
266
|
+
log("Using HTTPS proxy:", maskUrl(PROXY_URL));
|
|
267
|
+
}
|
|
268
|
+
// 1. Connect upstream
|
|
269
|
+
await initUpstream();
|
|
270
|
+
// 2. Fetch initial tool list
|
|
271
|
+
cachedTools = await fetchUpstreamTools();
|
|
272
|
+
const transformed = cachedTools.map(transformToolSchema);
|
|
273
|
+
const callToolCount = transformed.filter((t) => CALL_TOOL_NAMES.has(t.name)).length;
|
|
274
|
+
log(`Ready: ${transformed.length} tools (${callToolCount} with schema transform)`);
|
|
275
|
+
// 3. Create downstream stdio MCP server
|
|
276
|
+
const server = new Server({ name: "mcp-stdio-shim", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
277
|
+
// Handle tools/list — return transformed schemas
|
|
278
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
279
|
+
// Refresh tool list on each request — upstream may have changed
|
|
280
|
+
// (dynamic server management via /mcp/call mode)
|
|
281
|
+
try {
|
|
282
|
+
cachedTools = await fetchUpstreamTools();
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
log("Tool refresh failed, using cached:", err.message);
|
|
286
|
+
// Fall back to cached if refresh fails
|
|
287
|
+
if (!cachedTools)
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
290
|
+
return { tools: cachedTools.map(transformToolSchema) };
|
|
291
|
+
});
|
|
292
|
+
// Handle tools/call — transform args and forward upstream
|
|
293
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
294
|
+
const { name, arguments: args } = request.params;
|
|
295
|
+
const forwardArgs = transformToolCallArgs(name, args || {});
|
|
296
|
+
try {
|
|
297
|
+
await ensureSession();
|
|
298
|
+
const resp = await mcpRequest("tools/call", {
|
|
299
|
+
name,
|
|
300
|
+
arguments: forwardArgs,
|
|
301
|
+
});
|
|
302
|
+
if (!resp) {
|
|
303
|
+
return {
|
|
304
|
+
content: [{ type: "text", text: "No response from upstream" }],
|
|
305
|
+
isError: true,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
if (resp.error) {
|
|
309
|
+
// Check for session-expired errors
|
|
310
|
+
if (resp.error.message?.includes("session") ||
|
|
311
|
+
resp.error.message?.includes("Session") ||
|
|
312
|
+
resp.error.code === -32001) {
|
|
313
|
+
const ok = await reinitOnExpiry();
|
|
314
|
+
if (ok) {
|
|
315
|
+
// Retry once with new session
|
|
316
|
+
const retry = await mcpRequest("tools/call", {
|
|
317
|
+
name,
|
|
318
|
+
arguments: forwardArgs,
|
|
319
|
+
});
|
|
320
|
+
if (retry && !retry.error) {
|
|
321
|
+
return retry.result;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
content: [
|
|
327
|
+
{
|
|
328
|
+
type: "text",
|
|
329
|
+
text: JSON.stringify(resp.error),
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
isError: true,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
// Return the result as-is — it's already in MCP content format
|
|
336
|
+
return resp.result;
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
const msg = err.message;
|
|
340
|
+
log("Tool call error:", name, msg);
|
|
341
|
+
// Attempt re-init on network errors
|
|
342
|
+
if (msg.includes("fetch") || msg.includes("abort") || msg.includes("ECONNR")) {
|
|
343
|
+
await reinitOnExpiry();
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
347
|
+
isError: true,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
// 4. Connect stdio transport
|
|
352
|
+
const transport = new StdioServerTransport();
|
|
353
|
+
await server.connect(transport);
|
|
354
|
+
log("Stdio transport connected — shim is live");
|
|
355
|
+
}
|
|
356
|
+
main().catch((err) => {
|
|
357
|
+
log("Fatal:", err);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
});
|
|
360
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@luutuankiet/mcp-proxy-shim",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Stdio MCP shim for mcpproxy-go — transforms call_tool_* args_json:string to native args:object, eliminating ~400 tokens of JSON escaping overhead per LLM tool call",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-proxy-shim": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"dev": "npx tsx src/index.ts",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"mcpproxy-go",
|
|
23
|
+
"proxy",
|
|
24
|
+
"shim",
|
|
25
|
+
"stdio",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"args-json",
|
|
28
|
+
"schema-transform",
|
|
29
|
+
"claude",
|
|
30
|
+
"llm"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/luutuankiet/sandbox-cc",
|
|
36
|
+
"directory": "plugins/mcp-shim"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/luutuankiet/sandbox-cc/tree/main/plugins/mcp-shim#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/luutuankiet/sandbox-cc/issues"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
47
|
+
"undici": "^7.24.5"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^22.0.0",
|
|
51
|
+
"typescript": "^5.7.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=20.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|