mcpstore-gateway 1.3.1 → 2.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/dist/index.d.ts +7 -6
- package/dist/index.js +324 -135
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* MCP Store Gateway
|
|
3
|
+
* MCP Store Gateway v2.0.0
|
|
4
4
|
*
|
|
5
|
-
* A single MCP server that
|
|
6
|
-
*
|
|
5
|
+
* A single MCP server that proxies ALL your installed MCPs from mcpclaudecode.com.
|
|
6
|
+
* Install MCPs on the website → they appear automatically, no extra restart needed.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* - Hosted MCPs: tools proxied through
|
|
10
|
-
* - External MCPs:
|
|
8
|
+
* Architecture:
|
|
9
|
+
* - Hosted MCPs: tools proxied through the backend API (debate, quick_review)
|
|
10
|
+
* - External MCPs: spawned as child processes, tools aggregated into this gateway
|
|
11
|
+
* - Periodic sync: re-checks every 60s, notifies Claude via tools/list_changed
|
|
11
12
|
*
|
|
12
13
|
* Setup (one command, that's it):
|
|
13
14
|
* npx mcpstore-gateway --setup YOUR_API_KEY
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* MCP Store Gateway
|
|
3
|
+
* MCP Store Gateway v2.0.0
|
|
4
4
|
*
|
|
5
|
-
* A single MCP server that
|
|
6
|
-
*
|
|
5
|
+
* A single MCP server that proxies ALL your installed MCPs from mcpclaudecode.com.
|
|
6
|
+
* Install MCPs on the website → they appear automatically, no extra restart needed.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* - Hosted MCPs: tools proxied through
|
|
10
|
-
* - External MCPs:
|
|
8
|
+
* Architecture:
|
|
9
|
+
* - Hosted MCPs: tools proxied through the backend API (debate, quick_review)
|
|
10
|
+
* - External MCPs: spawned as child processes, tools aggregated into this gateway
|
|
11
|
+
* - Periodic sync: re-checks every 60s, notifies Claude via tools/list_changed
|
|
11
12
|
*
|
|
12
13
|
* Setup (one command, that's it):
|
|
13
14
|
* npx mcpstore-gateway --setup YOUR_API_KEY
|
|
@@ -15,18 +16,166 @@
|
|
|
15
16
|
* Runtime (called automatically by Claude Code):
|
|
16
17
|
* npx mcpstore-gateway --api-key YOUR_API_KEY
|
|
17
18
|
*/
|
|
18
|
-
import {
|
|
19
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
19
20
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
21
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
22
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
23
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
20
24
|
import * as fs from "node:fs";
|
|
21
25
|
import * as path from "node:path";
|
|
22
26
|
import * as os from "node:os";
|
|
23
27
|
const GATEWAY_URL = process.env.MCPSTORE_GATEWAY_URL ||
|
|
24
28
|
"https://rshsqjofouqyhzvzezos.supabase.co/functions/v1";
|
|
25
|
-
// Prefix for auto-managed MCP entries
|
|
29
|
+
// Prefix for auto-managed MCP entries (legacy, cleaned up on start)
|
|
26
30
|
const MANAGED_PREFIX = "mcpstore-";
|
|
27
|
-
|
|
31
|
+
const SYNC_INTERVAL_MS = 60_000; // Re-sync every 60 seconds
|
|
32
|
+
const SPAWN_TIMEOUT_MS = 30_000; // 30s timeout for child MCP connection
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Child MCP Management
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
const children = new Map();
|
|
37
|
+
/** Resolve ${VAR} templates in env values from process.env */
|
|
38
|
+
function resolveEnv(env) {
|
|
39
|
+
if (!env)
|
|
40
|
+
return {};
|
|
41
|
+
const resolved = {};
|
|
42
|
+
for (const [key, value] of Object.entries(env)) {
|
|
43
|
+
resolved[key] = value.replace(/\$\{(\w+)\}/g, (_, varName) => process.env[varName] || "");
|
|
44
|
+
}
|
|
45
|
+
return resolved;
|
|
46
|
+
}
|
|
47
|
+
/** Spawn a child MCP process and connect as client */
|
|
48
|
+
async function spawnChild(mcp) {
|
|
49
|
+
try {
|
|
50
|
+
let command = mcp.config.command;
|
|
51
|
+
let args = mcp.config.args || [];
|
|
52
|
+
// On Windows, wrap npx/node/uvx commands with cmd /c
|
|
53
|
+
if (os.platform() === "win32" &&
|
|
54
|
+
["npx", "node", "uvx"].includes(command)) {
|
|
55
|
+
args = ["/c", command, ...args];
|
|
56
|
+
command = "cmd";
|
|
57
|
+
}
|
|
58
|
+
const env = {
|
|
59
|
+
...Object.fromEntries(Object.entries(process.env).filter((e) => e[1] != null)),
|
|
60
|
+
...resolveEnv(mcp.config.env),
|
|
61
|
+
};
|
|
62
|
+
const transport = new StdioClientTransport({
|
|
63
|
+
command,
|
|
64
|
+
args,
|
|
65
|
+
env,
|
|
66
|
+
stderr: "pipe", // Don't leak child stderr into our stdio
|
|
67
|
+
});
|
|
68
|
+
const client = new Client({ name: "mcpstore-gateway", version: "2.0.0" }, { capabilities: {} });
|
|
69
|
+
// Connect with timeout
|
|
70
|
+
await Promise.race([
|
|
71
|
+
client.connect(transport),
|
|
72
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Connection timeout")), SPAWN_TIMEOUT_MS)),
|
|
73
|
+
]);
|
|
74
|
+
const { tools } = await client.listTools();
|
|
75
|
+
return {
|
|
76
|
+
slug: mcp.slug,
|
|
77
|
+
name: mcp.name,
|
|
78
|
+
client,
|
|
79
|
+
transport,
|
|
80
|
+
tools: tools || [],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
process.stderr.write(`[mcpstore] Failed to spawn ${mcp.slug}: ${err.message}\n`);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Kill a child MCP process */
|
|
89
|
+
async function killChild(slug) {
|
|
90
|
+
const child = children.get(slug);
|
|
91
|
+
if (!child)
|
|
92
|
+
return;
|
|
93
|
+
try {
|
|
94
|
+
await child.client.close();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Best effort
|
|
98
|
+
}
|
|
99
|
+
children.delete(slug);
|
|
100
|
+
}
|
|
101
|
+
/** Kill all child MCP processes */
|
|
102
|
+
async function killAllChildren() {
|
|
103
|
+
for (const slug of [...children.keys()]) {
|
|
104
|
+
await killChild(slug);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Tool Aggregation
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
/** Build aggregated tool list from all child MCPs */
|
|
111
|
+
function getProxiedTools() {
|
|
112
|
+
const tools = [];
|
|
113
|
+
for (const [slug, child] of children) {
|
|
114
|
+
for (const tool of child.tools) {
|
|
115
|
+
tools.push({
|
|
116
|
+
...tool,
|
|
117
|
+
name: `${slug}__${tool.name}`,
|
|
118
|
+
description: `[${child.name}] ${tool.description || ""}`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return tools;
|
|
123
|
+
}
|
|
124
|
+
/** Find which child owns a prefixed tool name */
|
|
125
|
+
function findToolOwner(prefixedName) {
|
|
126
|
+
const sepIdx = prefixedName.indexOf("__");
|
|
127
|
+
if (sepIdx === -1)
|
|
128
|
+
return null;
|
|
129
|
+
const slug = prefixedName.slice(0, sepIdx);
|
|
130
|
+
const toolName = prefixedName.slice(sepIdx + 2);
|
|
131
|
+
const child = children.get(slug);
|
|
132
|
+
if (!child)
|
|
133
|
+
return null;
|
|
134
|
+
return { child, toolName };
|
|
135
|
+
}
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Hosted Tools (Backend API proxy — debate, quick_review)
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
let hostedTools = [];
|
|
140
|
+
async function fetchHostedTools(apiKey) {
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(`${GATEWAY_URL}/gateway-tools`, {
|
|
143
|
+
headers: {
|
|
144
|
+
Authorization: `Bearer ${apiKey}`,
|
|
145
|
+
"Content-Type": "application/json",
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
if (!res.ok)
|
|
149
|
+
return [];
|
|
150
|
+
const data = (await res.json());
|
|
151
|
+
return data.tools || [];
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function callHostedTool(apiKey, toolName, args) {
|
|
158
|
+
const res = await fetch(`${GATEWAY_URL}/gateway-call`, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: {
|
|
161
|
+
Authorization: `Bearer ${apiKey}`,
|
|
162
|
+
"Content-Type": "application/json",
|
|
163
|
+
},
|
|
164
|
+
body: JSON.stringify({ tool: toolName, arguments: args }),
|
|
165
|
+
});
|
|
166
|
+
if (!res.ok) {
|
|
167
|
+
const body = await res.json().catch(() => ({}));
|
|
168
|
+
throw new Error(body.error || `Gateway error: ${res.status}`);
|
|
169
|
+
}
|
|
170
|
+
const data = (await res.json());
|
|
171
|
+
const result = data.result ?? data;
|
|
172
|
+
return typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
173
|
+
}
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Sync: fetch installed MCPs and manage child processes
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
async function fetchInstalledMcps(apiKey) {
|
|
28
178
|
try {
|
|
29
|
-
// 1. Fetch installed external MCPs from the API
|
|
30
179
|
const res = await fetch(`${GATEWAY_URL}/gateway-sync`, {
|
|
31
180
|
headers: {
|
|
32
181
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -34,60 +183,73 @@ async function syncExternalMcps(apiKey) {
|
|
|
34
183
|
},
|
|
35
184
|
});
|
|
36
185
|
if (!res.ok)
|
|
37
|
-
return;
|
|
186
|
+
return [];
|
|
38
187
|
const data = (await res.json());
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
188
|
+
return data.external_mcps || [];
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/** Sync installed MCPs: spawn new, kill removed, notify if changed */
|
|
195
|
+
async function sync(apiKey, server) {
|
|
196
|
+
const installed = await fetchInstalledMcps(apiKey);
|
|
197
|
+
const installedSlugs = new Set(installed.map((m) => m.slug));
|
|
198
|
+
let changed = false;
|
|
199
|
+
// Remove uninstalled MCPs
|
|
200
|
+
for (const slug of [...children.keys()]) {
|
|
201
|
+
if (!installedSlugs.has(slug)) {
|
|
202
|
+
await killChild(slug);
|
|
203
|
+
changed = true;
|
|
53
204
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const mcpConfig = { ...mcp.config };
|
|
62
|
-
if (isWindows && (mcpConfig.command === "npx" || mcpConfig.command === "node")) {
|
|
63
|
-
const originalCommand = mcpConfig.command;
|
|
64
|
-
const originalArgs = mcpConfig.args || [];
|
|
65
|
-
mcpConfig.command = "cmd";
|
|
66
|
-
mcpConfig.args = ["/c", originalCommand, ...originalArgs];
|
|
67
|
-
}
|
|
68
|
-
const existing = config.mcpServers[key];
|
|
69
|
-
if (!existing || JSON.stringify(existing) !== JSON.stringify(mcpConfig)) {
|
|
70
|
-
config.mcpServers[key] = mcpConfig;
|
|
205
|
+
}
|
|
206
|
+
// Spawn newly installed MCPs (skip already running)
|
|
207
|
+
for (const mcp of installed) {
|
|
208
|
+
if (!children.has(mcp.slug)) {
|
|
209
|
+
const child = await spawnChild(mcp);
|
|
210
|
+
if (child) {
|
|
211
|
+
children.set(mcp.slug, child);
|
|
71
212
|
changed = true;
|
|
72
213
|
}
|
|
73
214
|
}
|
|
74
|
-
|
|
215
|
+
}
|
|
216
|
+
// Notify Claude Code if tool list changed
|
|
217
|
+
if (changed) {
|
|
218
|
+
try {
|
|
219
|
+
await server.sendToolListChanged();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Best effort — server might not be connected yet
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Legacy cleanup: remove old mcpstore-* entries from .mcp.json
|
|
228
|
+
// (v1 wrote external MCPs to .mcp.json; v2 proxies them instead)
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
function cleanupLegacyEntries() {
|
|
231
|
+
try {
|
|
232
|
+
const mcpJsonPath = path.join(os.homedir(), ".mcp.json");
|
|
233
|
+
if (!fs.existsSync(mcpJsonPath))
|
|
234
|
+
return;
|
|
235
|
+
const raw = fs.readFileSync(mcpJsonPath, "utf-8");
|
|
236
|
+
const config = JSON.parse(raw);
|
|
237
|
+
if (!config.mcpServers)
|
|
238
|
+
return;
|
|
239
|
+
let changed = false;
|
|
75
240
|
for (const key of Object.keys(config.mcpServers)) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
changed = true;
|
|
81
|
-
}
|
|
241
|
+
// Remove mcpstore-* entries (but keep "mcpstore" — that's the gateway itself)
|
|
242
|
+
if (key.startsWith(MANAGED_PREFIX) && key !== "mcpstore") {
|
|
243
|
+
delete config.mcpServers[key];
|
|
244
|
+
changed = true;
|
|
82
245
|
}
|
|
83
246
|
}
|
|
84
|
-
// 5. Write back if changed
|
|
85
247
|
if (changed) {
|
|
86
248
|
fs.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
87
249
|
}
|
|
88
250
|
}
|
|
89
251
|
catch {
|
|
90
|
-
//
|
|
252
|
+
// Best effort
|
|
91
253
|
}
|
|
92
254
|
}
|
|
93
255
|
// ---------------------------------------------------------------------------
|
|
@@ -133,6 +295,12 @@ async function runSetup(apiKey) {
|
|
|
133
295
|
if (!config.mcpServers) {
|
|
134
296
|
config.mcpServers = {};
|
|
135
297
|
}
|
|
298
|
+
// Clean up legacy mcpstore-* entries from v1
|
|
299
|
+
for (const key of Object.keys(config.mcpServers)) {
|
|
300
|
+
if (key.startsWith(MANAGED_PREFIX) && key !== "mcpstore") {
|
|
301
|
+
delete config.mcpServers[key];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
136
304
|
// On Windows, wrap npx with cmd /c so Claude Code can spawn the process
|
|
137
305
|
if (os.platform() === "win32") {
|
|
138
306
|
config.mcpServers.mcpstore = {
|
|
@@ -161,102 +329,123 @@ async function runSetup(apiKey) {
|
|
|
161
329
|
console.log(" +--------------------------------------------------+");
|
|
162
330
|
console.log("");
|
|
163
331
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
Authorization: `Bearer ${apiKey}`,
|
|
168
|
-
"Content-Type": "application/json",
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
if (!res.ok) {
|
|
172
|
-
const body = await res.json().catch(() => ({}));
|
|
173
|
-
throw new Error(body.error || `Gateway error: ${res.status}`);
|
|
174
|
-
}
|
|
175
|
-
const data = (await res.json());
|
|
176
|
-
return data.tools || [];
|
|
177
|
-
}
|
|
178
|
-
async function callTool(apiKey, toolName, args) {
|
|
179
|
-
const res = await fetch(`${GATEWAY_URL}/gateway-call`, {
|
|
180
|
-
method: "POST",
|
|
181
|
-
headers: {
|
|
182
|
-
Authorization: `Bearer ${apiKey}`,
|
|
183
|
-
"Content-Type": "application/json",
|
|
184
|
-
},
|
|
185
|
-
body: JSON.stringify({ tool: toolName, arguments: args }),
|
|
186
|
-
});
|
|
187
|
-
if (!res.ok) {
|
|
188
|
-
const body = await res.json().catch(() => ({}));
|
|
189
|
-
throw new Error(body.error || `Gateway call error: ${res.status}`);
|
|
190
|
-
}
|
|
191
|
-
const data = (await res.json());
|
|
192
|
-
return data.result ?? data;
|
|
193
|
-
}
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
// Runtime mode: MCP server with proxy architecture
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
194
335
|
async function runServer(apiKey) {
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
});
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
336
|
+
// 1. Clean up legacy mcpstore-* entries from .mcp.json (v1 → v2 migration)
|
|
337
|
+
cleanupLegacyEntries();
|
|
338
|
+
// 2. Fetch hosted tools (debate, quick_review — proxied through backend)
|
|
339
|
+
hostedTools = await fetchHostedTools(apiKey);
|
|
340
|
+
// 3. Create server with listChanged capability
|
|
341
|
+
const server = new Server({ name: "MCP Store", version: "2.0.0" }, { capabilities: { tools: { listChanged: true } } });
|
|
342
|
+
// 4. Initial spawn — fetch installed MCPs and start child processes
|
|
343
|
+
const installed = await fetchInstalledMcps(apiKey);
|
|
344
|
+
for (const mcp of installed) {
|
|
345
|
+
const child = await spawnChild(mcp);
|
|
346
|
+
if (child) {
|
|
347
|
+
children.set(mcp.slug, child);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// 5. Handle tools/list
|
|
351
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
352
|
+
const tools = [];
|
|
353
|
+
// Hosted tools (backend API proxy)
|
|
354
|
+
for (const ht of hostedTools) {
|
|
355
|
+
tools.push({
|
|
356
|
+
name: ht.name,
|
|
357
|
+
description: ht.description,
|
|
358
|
+
inputSchema: ht.inputSchema,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
// Proxied external tools (from child MCPs)
|
|
362
|
+
tools.push(...getProxiedTools());
|
|
363
|
+
// Fallback info tool if nothing installed
|
|
204
364
|
if (tools.length === 0) {
|
|
205
|
-
|
|
365
|
+
tools.push({
|
|
366
|
+
name: "mcpstore__info",
|
|
367
|
+
description: "No MCPs installed yet. Visit https://www.mcpclaudecode.com/browse to install MCPs.",
|
|
368
|
+
inputSchema: { type: "object", properties: {} },
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return { tools };
|
|
372
|
+
});
|
|
373
|
+
// 6. Handle tools/call
|
|
374
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
375
|
+
const { name, arguments: args = {} } = request.params;
|
|
376
|
+
// Info tool
|
|
377
|
+
if (name === "mcpstore__info") {
|
|
378
|
+
return {
|
|
206
379
|
content: [
|
|
207
380
|
{
|
|
208
381
|
type: "text",
|
|
209
|
-
text: "You have no MCPs installed. Visit https://www.mcpclaudecode.com/browse to browse and install MCPs. They will appear here automatically!",
|
|
382
|
+
text: "You have no MCPs installed yet. Visit https://www.mcpclaudecode.com/browse to browse and install MCPs. They will appear here automatically!",
|
|
210
383
|
},
|
|
211
384
|
],
|
|
212
|
-
}
|
|
385
|
+
};
|
|
213
386
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
catch (err) {
|
|
232
|
-
return {
|
|
233
|
-
content: [
|
|
234
|
-
{
|
|
235
|
-
type: "text",
|
|
236
|
-
text: `Error: ${err.message}`,
|
|
237
|
-
},
|
|
238
|
-
],
|
|
239
|
-
isError: true,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
});
|
|
387
|
+
// Check hosted tools first
|
|
388
|
+
const hosted = hostedTools.find((t) => t.name === name);
|
|
389
|
+
if (hosted) {
|
|
390
|
+
try {
|
|
391
|
+
const text = await callHostedTool(apiKey, name, args);
|
|
392
|
+
return { content: [{ type: "text", text }] };
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
return {
|
|
396
|
+
content: [
|
|
397
|
+
{
|
|
398
|
+
type: "text",
|
|
399
|
+
text: `Error: ${err.message}`,
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
isError: true,
|
|
403
|
+
};
|
|
243
404
|
}
|
|
244
405
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
406
|
+
// Route to child MCP
|
|
407
|
+
const owner = findToolOwner(name);
|
|
408
|
+
if (!owner) {
|
|
409
|
+
return {
|
|
410
|
+
content: [
|
|
411
|
+
{ type: "text", text: `Unknown tool: ${name}` },
|
|
412
|
+
],
|
|
413
|
+
isError: true,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const result = await owner.child.client.callTool({
|
|
418
|
+
name: owner.toolName,
|
|
419
|
+
arguments: args,
|
|
420
|
+
});
|
|
421
|
+
return result;
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
return {
|
|
425
|
+
content: [
|
|
426
|
+
{
|
|
427
|
+
type: "text",
|
|
428
|
+
text: `Error calling ${name}: ${err.message}`,
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
isError: true,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
// 7. Connect to Claude Code via stdio
|
|
258
436
|
const transport = new StdioServerTransport();
|
|
259
437
|
await server.connect(transport);
|
|
438
|
+
// 8. Start periodic sync (after connection — can now send notifications)
|
|
439
|
+
setInterval(() => {
|
|
440
|
+
sync(apiKey, server).catch(() => { });
|
|
441
|
+
}, SYNC_INTERVAL_MS);
|
|
442
|
+
// 9. Cleanup on exit
|
|
443
|
+
const cleanup = async () => {
|
|
444
|
+
await killAllChildren();
|
|
445
|
+
process.exit(0);
|
|
446
|
+
};
|
|
447
|
+
process.on("SIGINT", cleanup);
|
|
448
|
+
process.on("SIGTERM", cleanup);
|
|
260
449
|
}
|
|
261
450
|
// ---------------------------------------------------------------------------
|
|
262
451
|
// Entry point
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcpstore-gateway",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "MCP Store Gateway — One MCP server for all your installed MCPs from mcpclaudecode.com",
|
|
5
5
|
"keywords": ["mcp", "claude", "claude-code", "ai", "gateway", "marketplace"],
|
|
6
6
|
"license": "MIT",
|