@toolsdk.ai/registry 1.0.168 → 1.0.169
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/api/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createServer } from "node:http";
|
|
2
3
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
4
|
+
import { getRequestListener } from "@hono/node-server";
|
|
4
5
|
import { swaggerUI } from "@hono/swagger-ui";
|
|
5
6
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
6
7
|
import { configRoutes } from "../domains/config/config-route";
|
|
8
|
+
import { createMcpGatewayHandler } from "../domains/mcp-gateway/mcp-gateway";
|
|
7
9
|
import { oauthDemoRoutes, oauthRoutes } from "../domains/oauth/oauth-route";
|
|
8
10
|
import { repository } from "../domains/package/package-handler";
|
|
9
11
|
import { packageRoutes } from "../domains/package/package-route";
|
|
@@ -76,9 +78,44 @@ app.onError((err, c) => {
|
|
|
76
78
|
}, 500);
|
|
77
79
|
});
|
|
78
80
|
const port = getServerPort();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
// MCP Gateway handler — intercepts /mcp/:packageName before Hono
|
|
82
|
+
const mcpGatewayHandler = createMcpGatewayHandler(repository);
|
|
83
|
+
const MCP_PATH_PREFIX = "/mcp/";
|
|
84
|
+
// Create a raw Node.js HTTP server so we can route /mcp/* to the
|
|
85
|
+
// Streamable HTTP transport (which needs raw IncomingMessage/ServerResponse)
|
|
86
|
+
// and everything else to Hono.
|
|
87
|
+
const honoListener = getRequestListener(app.fetch);
|
|
88
|
+
const server = createServer(async (req, res) => {
|
|
89
|
+
var _a;
|
|
90
|
+
const url = (_a = req.url) !== null && _a !== void 0 ? _a : "";
|
|
91
|
+
if (url.startsWith(MCP_PATH_PREFIX)) {
|
|
92
|
+
// Extract package name: /mcp/@scope/name or /mcp/name
|
|
93
|
+
// Handle scoped packages: /mcp/@scope/name → @scope/name
|
|
94
|
+
const pathAfterPrefix = url.slice(MCP_PATH_PREFIX.length);
|
|
95
|
+
// Remove query string if any
|
|
96
|
+
const packageName = decodeURIComponent(pathAfterPrefix.split("?")[0]);
|
|
97
|
+
if (!packageName) {
|
|
98
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
99
|
+
res.end(JSON.stringify({ error: "Missing package name in URL" }));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
await mcpGatewayHandler(req, res, packageName);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error("[MCP Gateway] Unhandled error:", error);
|
|
107
|
+
if (!res.headersSent) {
|
|
108
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
109
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// All other routes → Hono
|
|
115
|
+
honoListener(req, res);
|
|
116
|
+
});
|
|
117
|
+
server.listen(port, () => {
|
|
118
|
+
console.log(`🚀 Server is running on http://localhost:${port}`);
|
|
119
|
+
console.log(`🔌 MCP Gateway available at http://localhost:${port}/mcp/<packageName>`);
|
|
82
120
|
});
|
|
83
|
-
console.log(`🚀 Server is running on http://localhost:${port}`);
|
|
84
121
|
export default app;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { PackageRepository } from "../package/package-repository";
|
|
3
|
+
/**
|
|
4
|
+
* Main request handler for the MCP Gateway.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createMcpGatewayHandler(repository: PackageRepository): (req: IncomingMessage, res: ServerResponse, packageName: string) => Promise<void>;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { getMcpClient } from "../../shared/utils/mcp-client-util";
|
|
6
|
+
/**
|
|
7
|
+
* Extracts environment variables from request headers.
|
|
8
|
+
* Headers like `x-mcp-env-TAVILY_API_KEY: xxx` → { TAVILY_API_KEY: "xxx" }
|
|
9
|
+
*/
|
|
10
|
+
function extractEnvFromHeaders(req) {
|
|
11
|
+
const envs = {};
|
|
12
|
+
const prefix = "x-mcp-env-";
|
|
13
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
14
|
+
if (key.toLowerCase().startsWith(prefix) && typeof value === "string") {
|
|
15
|
+
// Node.js lowercases all header names, so restore the env key to UPPER_CASE
|
|
16
|
+
const envKey = key.slice(prefix.length).toUpperCase();
|
|
17
|
+
envs[envKey] = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return envs;
|
|
21
|
+
}
|
|
22
|
+
const sessions = new Map();
|
|
23
|
+
const SESSION_TTL_MS = 30 * 60 * 1000;
|
|
24
|
+
setInterval(() => {
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
for (const [id, session] of sessions) {
|
|
27
|
+
if (now - session.createdAt > SESSION_TTL_MS) {
|
|
28
|
+
session.server.close().catch(() => { });
|
|
29
|
+
sessions.delete(id);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}, 5 * 60 * 1000);
|
|
33
|
+
/**
|
|
34
|
+
* Creates a low-level MCP Server that proxies tools from the target package.
|
|
35
|
+
*
|
|
36
|
+
* The `sessionRef` object is shared with the session store so that
|
|
37
|
+
* `envs` can be updated on every incoming HTTP request. The tools/call
|
|
38
|
+
* handler always reads `sessionRef.envs` at call time (not at init time).
|
|
39
|
+
*/
|
|
40
|
+
async function createProxyServer(packageName, config, sessionRef) {
|
|
41
|
+
var _a, _b;
|
|
42
|
+
// Provide mock envs for listing tools (some servers validate env on startup)
|
|
43
|
+
const mockEnvs = Object.assign({}, sessionRef.envs);
|
|
44
|
+
if (config.env) {
|
|
45
|
+
for (const key of Object.keys(config.env)) {
|
|
46
|
+
if (!mockEnvs[key]) {
|
|
47
|
+
mockEnvs[key] = "mock_value";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Fetch available tools from the upstream MCP server
|
|
52
|
+
const { client: upstreamClient, closeConnection } = await getMcpClient(config, mockEnvs);
|
|
53
|
+
let upstreamTools;
|
|
54
|
+
try {
|
|
55
|
+
const result = await upstreamClient.listTools();
|
|
56
|
+
upstreamTools = result.tools;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await closeConnection();
|
|
60
|
+
}
|
|
61
|
+
// Ensure each tool's inputSchema allows additional properties.
|
|
62
|
+
// VSCode's AJV validator defaults to additionalProperties:false when
|
|
63
|
+
// the field is missing, causing "must NOT have additional properties".
|
|
64
|
+
for (const tool of upstreamTools) {
|
|
65
|
+
if (tool.inputSchema && !("additionalProperties" in tool.inputSchema)) {
|
|
66
|
+
tool.inputSchema.additionalProperties = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Build a map of tool name → allowed property keys for argument filtering
|
|
70
|
+
const toolPropertyKeys = new Map();
|
|
71
|
+
for (const tool of upstreamTools) {
|
|
72
|
+
toolPropertyKeys.set(tool.name, new Set(Object.keys((_b = (_a = tool.inputSchema) === null || _a === void 0 ? void 0 : _a.properties) !== null && _b !== void 0 ? _b : {})));
|
|
73
|
+
}
|
|
74
|
+
const server = new Server({ name: `mcpsdk-gateway/${packageName}`, version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
75
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
76
|
+
console.log(`[MCP Gateway] tools/list for ${packageName}: returning ${upstreamTools.length} tools`);
|
|
77
|
+
return { tools: upstreamTools };
|
|
78
|
+
});
|
|
79
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
80
|
+
const { name, arguments: rawArgs } = request.params;
|
|
81
|
+
// Filter to only schema-defined properties
|
|
82
|
+
const allowedKeys = toolPropertyKeys.get(name);
|
|
83
|
+
let args = rawArgs;
|
|
84
|
+
if (allowedKeys && args) {
|
|
85
|
+
const filtered = {};
|
|
86
|
+
for (const [k, v] of Object.entries(args)) {
|
|
87
|
+
if (allowedKeys.has(k)) {
|
|
88
|
+
filtered[k] = v;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
args = filtered;
|
|
92
|
+
}
|
|
93
|
+
// Read envs from the shared session ref (always up-to-date)
|
|
94
|
+
const envs = sessionRef.envs;
|
|
95
|
+
console.log(`[MCP Gateway] tools/call ${packageName}/${name}`, JSON.stringify(args).slice(0, 300), `envKeys=[${Object.keys(envs).join(",")}]`);
|
|
96
|
+
const { client, closeConnection: closeUpstream } = await getMcpClient(config, envs);
|
|
97
|
+
try {
|
|
98
|
+
const result = await client.callTool({ name, arguments: args });
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
await closeUpstream();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return { server, tools: upstreamTools };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Main request handler for the MCP Gateway.
|
|
109
|
+
*/
|
|
110
|
+
export function createMcpGatewayHandler(repository) {
|
|
111
|
+
return async (req, res, packageName) => {
|
|
112
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
113
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
114
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id, x-mcp-env-*");
|
|
115
|
+
res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
|
|
116
|
+
if (req.method === "OPTIONS") {
|
|
117
|
+
res.writeHead(204);
|
|
118
|
+
res.end();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
let config;
|
|
122
|
+
try {
|
|
123
|
+
config = repository.getPackageConfig(packageName);
|
|
124
|
+
}
|
|
125
|
+
catch (_a) {
|
|
126
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
127
|
+
res.end(JSON.stringify({ error: `Package '${packageName}' not found` }));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const reqEnvs = extractEnvFromHeaders(req);
|
|
131
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
132
|
+
console.log(`[MCP Gateway] ${req.method} ${req.url} session=${sessionId !== null && sessionId !== void 0 ? sessionId : "(new)"}`, `envKeys=[${Object.keys(reqEnvs).join(",")}]`);
|
|
133
|
+
// Existing session — merge any new env vars and delegate
|
|
134
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
135
|
+
const session = sessions.get(sessionId);
|
|
136
|
+
if (session) {
|
|
137
|
+
// Merge env vars from this request into the session
|
|
138
|
+
Object.assign(session.envs, reqEnvs);
|
|
139
|
+
await session.transport.handleRequest(req, res);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (sessionId && !sessions.has(sessionId)) {
|
|
144
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
145
|
+
res.end(JSON.stringify({ error: "Session not found or expired" }));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// New session
|
|
149
|
+
try {
|
|
150
|
+
// Shared mutable ref so the tools/call handler always sees latest envs
|
|
151
|
+
const sessionRef = { envs: Object.assign({}, reqEnvs) };
|
|
152
|
+
const transport = new StreamableHTTPServerTransport({
|
|
153
|
+
sessionIdGenerator: () => randomUUID(),
|
|
154
|
+
onsessioninitialized: (newSessionId) => {
|
|
155
|
+
sessions.set(newSessionId, {
|
|
156
|
+
transport,
|
|
157
|
+
server: mcpServer,
|
|
158
|
+
packageName,
|
|
159
|
+
config,
|
|
160
|
+
envs: sessionRef.envs,
|
|
161
|
+
createdAt: Date.now(),
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
onsessionclosed: (closedSessionId) => {
|
|
165
|
+
sessions.delete(closedSessionId);
|
|
166
|
+
},
|
|
167
|
+
enableJsonResponse: true,
|
|
168
|
+
});
|
|
169
|
+
const { server: mcpServer } = await createProxyServer(packageName, config, sessionRef);
|
|
170
|
+
await mcpServer.connect(transport);
|
|
171
|
+
await transport.handleRequest(req, res);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
console.error(`[MCP Gateway] Error for ${packageName}:`, error);
|
|
175
|
+
if (!res.headersSent) {
|
|
176
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
177
|
+
res.end(JSON.stringify({
|
|
178
|
+
error: `Failed to initialize MCP gateway for ${packageName}`,
|
|
179
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -56693,8 +56693,9 @@
|
|
|
56693
56693
|
}
|
|
56694
56694
|
},
|
|
56695
56695
|
"supercolony-mcp": {
|
|
56696
|
-
"path": "finance-fintech/supercolony-mcp.json",
|
|
56697
56696
|
"category": "finance-fintech",
|
|
56697
|
+
"path": "finance-fintech/supercolony-mcp.json",
|
|
56698
|
+
"validated": true,
|
|
56698
56699
|
"tools": {
|
|
56699
56700
|
"supercolony_read_feed": {
|
|
56700
56701
|
"name": "supercolony_read_feed",
|
|
@@ -56724,13 +56725,12 @@
|
|
|
56724
56725
|
"name": "supercolony_build_agent",
|
|
56725
56726
|
"description": "Get the complete integration guide for building an AI agent that joins SuperColony. Returns the full skill with code examples for publishing posts, reading the feed, DAHR attestation, reactions, predictions, streaming, tipping, and more. Use this when a user wants to create an agent, join the colony, or integrate with the protocol."
|
|
56726
56727
|
}
|
|
56727
|
-
}
|
|
56728
|
-
"validated": true
|
|
56728
|
+
}
|
|
56729
56729
|
},
|
|
56730
56730
|
"xquik": {
|
|
56731
|
-
"path": "search-data-extraction/xquik.json",
|
|
56732
56731
|
"category": "search-data-extraction",
|
|
56733
|
-
"
|
|
56734
|
-
"validated": false
|
|
56732
|
+
"path": "search-data-extraction/xquik.json",
|
|
56733
|
+
"validated": false,
|
|
56734
|
+
"tools": {}
|
|
56735
56735
|
}
|
|
56736
56736
|
}
|