@toolsdk.ai/registry 1.0.168 → 1.0.170
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -107,6 +107,28 @@ curl -X POST http://localhost:3003/api/v1/packages/run \
|
|
|
107
107
|
}'
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
#### 🔌 MCP Gateway (Streamable HTTP Proxy)
|
|
111
|
+
|
|
112
|
+
The registry also acts as an **MCP Gateway** — any registered package can be accessed as a standard [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) endpoint, even if the original server is STDIO-only.
|
|
113
|
+
|
|
114
|
+
**Endpoint:** `POST /mcp/<packageName>`
|
|
115
|
+
|
|
116
|
+
Pass environment variables via `x-mcp-env-*` headers:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
curl -X POST http://localhost:3003/mcp/@modelcontextprotocol/server-github \
|
|
120
|
+
-H "Content-Type: application/json" \
|
|
121
|
+
-H "x-mcp-env-GITHUB_PERSONAL_ACCESS_TOKEN: ghp_your_token" \
|
|
122
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The server returns a `mcp-session-id` header — include it in subsequent requests to reuse the session (sessions expire after 30 min).
|
|
126
|
+
|
|
127
|
+
This is useful for:
|
|
128
|
+
- **Protocol Bridging** — Expose local STDIO servers as remote HTTP endpoints
|
|
129
|
+
- **Centralized Access** — Give AI agents a single HTTP gateway to all MCP tools
|
|
130
|
+
- **Client Compatibility** — Connect from any MCP client that supports Streamable HTTP
|
|
131
|
+
|
|
110
132
|
<details>
|
|
111
133
|
<summary><strong>Alternative: Use as Registry SDK (Data Only)</strong></summary>
|
|
112
134
|
|
|
@@ -274,46 +296,10 @@ const searchTool = await searchMCP.getAISDKTool('tavily-search');
|
|
|
274
296
|
|
|
275
297
|
## Contribute Your MCP Server
|
|
276
298
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
### Quick Submission
|
|
299
|
+
Want to add your MCP server to the registry? Check out our [Contributing Guide](./docs/CONTRIBUTING.md) for the full submission process, JSON schema reference, and examples (including remote servers and OAuth 2.1).
|
|
280
300
|
|
|
281
301
|
[](https://www.youtube.com/watch?v=J_oaDtCoVVo)
|
|
282
302
|
|
|
283
|
-
1. [Fork this repository](https://github.com/toolsdk-ai/toolsdk-mcp-registry/fork)
|
|
284
|
-
2. Create `your-mcp-server.json` in [packages/uncategorized](./packages/uncategorized) (or the best matching category folder)
|
|
285
|
-
3. Submit a PR
|
|
286
|
-
|
|
287
|
-
Config Example:
|
|
288
|
-
|
|
289
|
-
```json
|
|
290
|
-
{
|
|
291
|
-
"type": "mcp-server",
|
|
292
|
-
"name": "Github",
|
|
293
|
-
"packageName": "@modelcontextprotocol/server-github",
|
|
294
|
-
"description": "MCP server for using the GitHub API",
|
|
295
|
-
"url": "https://github.com/modelcontextprotocol/servers/blob/main/src/github",
|
|
296
|
-
"runtime": "node",
|
|
297
|
-
"license": "MIT",
|
|
298
|
-
"env": {
|
|
299
|
-
"GITHUB_PERSONAL_ACCESS_TOKEN": {
|
|
300
|
-
"description": "Personal access token for GitHub API access",
|
|
301
|
-
"required": true
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
Your MCP server will be:
|
|
308
|
-
- ✅ Listed in the registry
|
|
309
|
-
- 🔍 Searchable via REST API
|
|
310
|
-
- 📦 Available in npm package
|
|
311
|
-
- 🌐 Featured on [ToolSDK.ai](https://toolsdk.ai)
|
|
312
|
-
|
|
313
|
-
📖 **Source of truth (schema, fields, remotes, OAuth)**: [CONTRIBUTING.md](./CONTRIBUTING.md)
|
|
314
|
-
|
|
315
|
-
📚 Additional docs: [docs/guide.md](./docs/guide.md)
|
|
316
|
-
|
|
317
303
|
---
|
|
318
304
|
|
|
319
305
|
<a id="mcp-servers"></a>
|
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
|
}
|