storkai 1.0.0 → 1.0.2

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.
Files changed (2) hide show
  1. package/bin/storkai.js +89 -154
  2. package/package.json +1 -5
package/bin/storkai.js CHANGED
@@ -1,174 +1,109 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Stork MCP — The MCP server for MCP servers.
4
- *
5
- * Search, discover, evaluate, and configure MCP servers from inside your IDE.
6
- * Install once: npx storkai
7
- *
8
- * https://www.stork.ai/mcp
9
- */
10
-
11
- import process from "node:process";
12
- import { ConvexHttpClient } from "convex/browser";
13
-
14
- const SERVER_NAME = "storkai";
15
- const SERVER_VERSION = "1.0.0";
16
- const CONVEX_URL = "https://expert-egret-407.convex.cloud";
17
-
18
- const client = new ConvexHttpClient(process.env.STORK_CONVEX_URL || CONVEX_URL);
19
-
20
- let stdinBuffer = Buffer.alloc(0);
21
-
22
- function writeMessage(obj) {
23
- const json = JSON.stringify(obj);
24
- process.stdout.write(`Content-Length: ${Buffer.byteLength(json, "utf8")}\r\n\r\n`);
25
- process.stdout.write(json);
26
- }
27
-
28
- function parseOneMessage(buffer) {
29
- const headerEnd = buffer.indexOf("\r\n\r\n");
30
- if (headerEnd === -1) return null;
31
- const match = buffer.slice(0, headerEnd).toString("utf8").match(/Content-Length:\s*(\d+)/i);
32
- if (!match) throw new Error("Missing Content-Length header");
33
- const length = Number(match[1]);
34
- const bodyStart = headerEnd + 4;
35
- if (buffer.length < bodyStart + length) return null;
36
- const body = buffer.slice(bodyStart, bodyStart + length).toString("utf8");
37
- return { message: JSON.parse(body), rest: buffer.slice(bodyStart + length) };
38
- }
39
-
40
- function rpcError(id, code, message, data) {
41
- return { jsonrpc: "2.0", id: id ?? null, error: { code, message, ...(data !== undefined ? { data } : {}) } };
42
- }
43
-
44
- function rpcResult(id, result) {
45
- return { jsonrpc: "2.0", id, result };
46
- }
47
-
48
- function obj(v) {
49
- return v && typeof v === "object" && !Array.isArray(v) ? v : {};
2
+ var fs = require("fs");
3
+ var SERVER_NAME = "storkai";
4
+ var SERVER_VERSION = "1.0.3";
5
+ var CONVEX_URL = process.env.STORK_CONVEX_URL || "https://expert-egret-407.convex.cloud";
6
+
7
+ function convexQuery(p, a) {
8
+ return fetch(CONVEX_URL + "/api/query", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path: p, args: a || {}, format: "json" }) })
9
+ .then(function(r) { if (!r.ok) throw new Error("query " + r.status); return r.json(); })
10
+ .then(function(d) { if (d.status === "error") throw new Error(d.errorMessage || "error"); return d.value; });
50
11
  }
51
12
 
52
- function fmt(v) {
53
- try { return JSON.stringify(v, null, 2); } catch { return String(v); }
54
- }
55
-
56
- function fmtSearch(data) {
57
- if (!data?.results?.length) return "No MCP servers found. Try different keywords or use stork_list_filters to browse categories.";
58
- const lines = [`Found ${data.results.length} MCP server(s):\n`];
59
- for (const [i, r] of data.results.entries()) {
60
- lines.push(`${i + 1}. **${r.name}** (${r.slug})`);
61
- lines.push(` ${r.summary}`);
62
- if (r.trustScore != null) lines.push(` Trust Score: ${r.trustScore}/100`);
63
- if (r.tools.length) lines.push(` Tools: ${r.tools.slice(0, 5).map(t => t.name).join(", ")}${r.tools.length > 5 ? ` (+${r.tools.length - 5} more)` : ""}`);
64
- if (r.transportTypes.length) lines.push(` Transport: ${r.transportTypes.join(", ")}`);
65
- if (r.installCommand) lines.push(` Install: ${r.installCommand}`);
66
- if (r.authSummary) lines.push(` Auth: ${r.authSummary}`);
67
- if (r.npmPackage) lines.push(` npm: ${r.npmPackage}`);
68
- lines.push(` Homepage: ${r.homepageUrl}`, "");
69
- }
70
- lines.push("Use stork_server_details for full info, or stork_get_install_config for ready-to-paste config.");
71
- return lines.join("\n");
72
- }
13
+ var stdinBuf = Buffer.alloc(0);
73
14
 
74
- function fmtDetails(d) {
75
- if (!d) return "Server not found.";
76
- const l = [`# ${d.name}\n`, d.description || d.summary, ""];
77
- if (d.trustScore != null) l.push(`**Trust Score**: ${d.trustScore}/100`);
78
- l.push(`**Category**: ${d.primaryCategory}`);
79
- if (d.transportTypes?.length) l.push(`**Transport**: ${d.transportTypes.join(", ")}`);
80
- if (d.license) l.push(`**License**: ${d.license}`);
81
- if (d.npmPackage) l.push(`**npm**: ${d.npmPackage}`);
82
- if (d.installCommand) l.push(`**Install**: \`${d.installCommand}\``);
83
- if (d.authSummary) l.push(`**Auth**: ${d.authSummary}`);
84
- l.push(`**Homepage**: ${d.homepageUrl}`);
85
- if (d.repoUrl) l.push(`**Repository**: ${d.repoUrl}`);
86
- if (d.tools?.length) { l.push("\n## Tools"); for (const t of d.tools) l.push(`- **${t.name}**${t.description ? `: ${t.description}` : ""}`); }
87
- l.push("\n---\nUse stork_get_install_config for ready-to-paste config.");
88
- return l.join("\n");
15
+ function send(obj) {
16
+ fs.writeSync(1, JSON.stringify(obj) + "\n");
89
17
  }
90
18
 
91
- function fmtConfig(d) {
92
- if (d?.error) return `Error: ${d.error}`;
93
- const l = [`# Install ${d.serverName} for ${d.client}\n`, d.instructions, "", "```json", d.configJson, "```"];
94
- if (d.authSummary) l.push("", `**Auth required**: ${d.authSummary}`);
95
- if (d.docsUrl) l.push(`**Documentation**: ${d.docsUrl}`);
96
- return l.join("\n");
97
- }
98
-
99
- function fmtCompare(d) {
100
- if (d?.error) return `Error: ${d.error}`;
101
- if (!d?.comparison?.length) return "No servers to compare.";
102
- const l = ["# MCP Server Comparison\n"];
103
- for (const s of d.comparison) {
104
- l.push(`## ${s.name} (${s.slug})`);
105
- l.push(`- ${s.summary}`);
106
- if (s.trustScore != null) l.push(`- Trust: ${s.trustScore}/100`);
107
- l.push(`- Tools (${s.toolCount}): ${s.tools.slice(0, 8).join(", ") || "none"}`);
108
- l.push(`- Transport: ${s.transportTypes.join(", ") || "stdio"}`);
109
- l.push(`- License: ${s.license}`);
110
- if (s.npmPackage) l.push(`- npm: ${s.npmPackage}`);
111
- l.push(`- Homepage: ${s.homepageUrl}`, "");
112
- }
113
- return l.join("\n");
19
+ function parse(buf) {
20
+ var nl = buf.indexOf("\n");
21
+ if (nl === -1) return null;
22
+ var line = buf.slice(0, nl).toString("utf8").trim();
23
+ if (!line) return { msg: null, rest: buf.slice(nl + 1) };
24
+ try { return { msg: JSON.parse(line), rest: buf.slice(nl + 1) }; }
25
+ catch (e) { return null; }
114
26
  }
115
27
 
116
- const tools = [
117
- { name: "stork_search", description: "Search for MCP servers by natural language. Example: 'manage Jira tickets' or 'query Postgres databases'. Returns top matches with trust scores, tools, and install commands.", inputSchema: { type: "object", required: ["query"], properties: { query: { type: "string", description: "What you need the MCP server to do" }, category: { type: "string", description: "Category filter" }, transport: { type: "string", enum: ["stdio", "sse", "streamable-http"], description: "Transport filter" }, limit: { type: "number", minimum: 1, maximum: 10, description: "Max results (default 3)" } } },
118
- handler: async (a) => { const args = { query: String(a.query || ""), category: a.category || undefined, transport: a.transport || undefined, limit: typeof a.limit === "number" ? a.limit : undefined }; try { return fmtSearch(await client.action("mcpDiscoveryActions:semanticSearchServers", args)); } catch { return fmtSearch(await client.query("mcpDiscovery:searchServers", args)); } } },
119
- { name: "stork_server_details", description: "Get full details about an MCP server: tools, auth, trust scores, health, and more. Use the slug from search results.", inputSchema: { type: "object", required: ["slug"], properties: { slug: { type: "string", description: "Server slug" } } },
120
- handler: async (a) => fmtDetails(await client.query("mcpDiscovery:getServerDetails", { slug: String(a.slug) })) },
121
- { name: "stork_get_install_config", description: "Get ready-to-paste JSON config for installing an MCP server in your IDE. Supports cursor, claude-desktop, claude-code, vscode, and zed.", inputSchema: { type: "object", required: ["slug", "client"], properties: { slug: { type: "string", description: "Server slug" }, client: { type: "string", enum: ["cursor", "claude-desktop", "claude-code", "vscode", "zed"], description: "Target IDE" } } },
122
- handler: async (a) => fmtConfig(await client.query("mcpDiscovery:getInstallConfig", { slug: String(a.slug), client: String(a.client) })) },
123
- { name: "stork_compare", description: "Compare 2-5 MCP servers side by side on tools, trust, transport, auth, and more.", inputSchema: { type: "object", required: ["slugs"], properties: { slugs: { type: "array", items: { type: "string" }, minItems: 2, maxItems: 5, description: "Server slugs to compare" } } },
124
- handler: async (a) => fmtCompare(await client.query("mcpDiscovery:compareServers", { slugs: Array.isArray(a.slugs) ? a.slugs.map(String) : [] })) },
125
- { name: "stork_list_filters", description: "List available categories and hosting filters with server counts.", inputSchema: { type: "object", properties: {} },
126
- handler: async () => { const r = await client.query("mcpServers:listServerFilters", {}); const l = ["# Available Filters\n"]; if (r?.categories?.length) { l.push("## Categories"); for (const c of r.categories) l.push(`- ${c.id} (${c.count})`); } if (r?.hosting?.length) { l.push("\n## Hosting"); for (const h of r.hosting) l.push(`- ${h.id} (${h.count})`); } return l.join("\n"); } },
127
- { name: "stork_submit", description: "Submit a new MCP server to Stork. Provide a GitHub repo URL or npm package name.", inputSchema: { type: "object", required: ["url"], properties: { url: { type: "string", description: "GitHub repo URL or npm package" }, name: { type: "string", description: "Display name" }, description: { type: "string", description: "What the server does" } } },
128
- handler: async (a) => `Submission received for: ${a.url}\n\nTo complete registration, visit: https://www.stork.ai/mcp/submit\nYour server will be auto-enriched with metadata, tools, and trust signals.` },
28
+ var TOOLS = [
29
+ { name: "stork_search", description: "Search for MCP servers by natural language. Example: 'manage Jira tickets' or 'query Postgres databases'.", inputSchema: { type: "object", required: ["query"], properties: { query: { type: "string", description: "What you need" }, limit: { type: "number", description: "Max results (default 3)" } } } },
30
+ { name: "stork_server_details", description: "Get details about an MCP server by slug.", inputSchema: { type: "object", required: ["slug"], properties: { slug: { type: "string" } } } },
31
+ { name: "stork_get_install_config", description: "Get install config for your IDE.", inputSchema: { type: "object", required: ["slug", "client"], properties: { slug: { type: "string" }, client: { type: "string", enum: ["cursor", "claude-desktop", "claude-code", "vscode", "zed"] } } } },
32
+ { name: "stork_compare", description: "Compare MCP servers side by side.", inputSchema: { type: "object", required: ["slugs"], properties: { slugs: { type: "array", items: { type: "string" } } } } },
33
+ { name: "stork_list_filters", description: "List available categories and filters.", inputSchema: { type: "object", properties: {} } },
34
+ { name: "stork_submit", description: "Submit a new MCP server.", inputSchema: { type: "object", required: ["url"], properties: { url: { type: "string" } } } },
129
35
  ];
130
36
 
131
- const toolMap = new Map(tools.map(t => [t.name, t]));
132
-
133
- async function handle(msg) {
134
- const { id, method, params } = msg;
135
- if (method === "initialize") return rpcResult(id, { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: SERVER_NAME, version: SERVER_VERSION } });
136
- if (method === "tools/list") return rpcResult(id, { tools: tools.map(t => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })) });
137
- if (method === "tools/call") {
138
- const p = obj(params);
139
- const tool = toolMap.get(String(p.name ?? ""));
140
- if (!tool) return rpcResult(id, { content: [{ type: "text", text: `Unknown tool: ${p.name}` }], isError: true });
141
- try {
142
- const result = await tool.handler(obj(p.arguments));
143
- return rpcResult(id, { content: [{ type: "text", text: typeof result === "string" ? result : fmt(result) }] });
144
- } catch (err) {
145
- return rpcResult(id, { content: [{ type: "text", text: `Error: ${err?.message ?? String(err)}` }], isError: true });
146
- }
37
+ function callTool(name, args) {
38
+ switch (name) {
39
+ case "stork_search":
40
+ return convexQuery("mcpDiscovery:searchServers", { query: String(args.query || ""), limit: typeof args.limit === "number" ? args.limit : 3 })
41
+ .then(function(d) {
42
+ if (!d || !d.results || !d.results.length) return "No MCP servers found.";
43
+ return d.results.map(function(r, i) {
44
+ var s = (i+1) + ". **" + r.name + "** (" + r.slug + ")\n " + r.summary;
45
+ if (r.trustScore) s += "\n Trust: " + r.trustScore + "/100";
46
+ if (r.tools && r.tools.length) s += "\n Tools: " + r.tools.slice(0,5).map(function(t){return t.name;}).join(", ");
47
+ if (r.installCommand) s += "\n Install: " + r.installCommand;
48
+ return s;
49
+ }).join("\n\n") + "\n\nUse stork_server_details or stork_get_install_config for more.";
50
+ });
51
+ case "stork_server_details":
52
+ return convexQuery("mcpDiscovery:getServerDetails", { slug: String(args.slug) })
53
+ .then(function(d) { return d ? "# " + d.name + "\n" + (d.description||d.summary||"") + (d.tools && d.tools.length ? "\n\n## Tools\n" + d.tools.map(function(t){return "- **" + t.name + "**" + (t.description ? ": " + t.description : "");}).join("\n") : "") : "Not found."; });
54
+ case "stork_get_install_config":
55
+ return convexQuery("mcpDiscovery:getInstallConfig", { slug: String(args.slug), client: String(args.client) })
56
+ .then(function(d) { return d && d.error ? "Error: " + d.error : d.instructions + "\n\n```json\n" + d.configJson + "\n```" + (d.authSummary ? "\n\nAuth: " + d.authSummary : ""); });
57
+ case "stork_compare":
58
+ return convexQuery("mcpDiscovery:compareServers", { slugs: (args.slugs||[]).map(String) })
59
+ .then(function(d) { return d && d.error ? d.error : d.comparison ? d.comparison.map(function(s){return "## " + s.name + "\nTools: " + s.toolCount + " | Trust: " + (s.trustScore||"?") + " | " + s.license;}).join("\n\n") : "No data"; });
60
+ case "stork_list_filters":
61
+ return convexQuery("mcpServers:listServerFilters", {}).then(function(r) { return JSON.stringify(r, null, 2); });
62
+ case "stork_submit":
63
+ return Promise.resolve("Submit at https://www.stork.ai/mcp/submit");
64
+ default:
65
+ return Promise.resolve("Unknown tool: " + name);
147
66
  }
148
- if (method === "notifications/initialized" || method === "initialized") return null;
149
- return rpcError(id, -32601, `Method not found: ${method}`);
150
67
  }
151
68
 
152
- process.stdin.on("data", async (chunk) => {
153
- stdinBuffer = Buffer.concat([stdinBuffer, chunk]);
69
+ process.stdin.resume();
70
+ process.stdin.on("data", function(chunk) {
71
+ stdinBuf = Buffer.concat([stdinBuf, chunk]);
154
72
  while (true) {
155
- let parsed;
156
- try { parsed = parseOneMessage(stdinBuffer); } catch (err) {
157
- writeMessage(rpcError(null, -32700, "Parse error", err?.message));
158
- stdinBuffer = Buffer.alloc(0);
159
- return;
73
+ var p = parse(stdinBuf);
74
+ if (!p) return;
75
+ stdinBuf = p.rest;
76
+ if (!p.msg) continue;
77
+ var id = p.msg.id;
78
+ var method = p.msg.method;
79
+ if (!method) continue;
80
+
81
+ // Skip all notifications (no response expected)
82
+ if (!id && id !== 0) continue;
83
+
84
+ if (method === "initialize") {
85
+ send({ jsonrpc: "2.0", id: id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: SERVER_NAME, version: SERVER_VERSION } } });
86
+ continue;
160
87
  }
161
- if (!parsed) return;
162
- stdinBuffer = parsed.rest;
163
- const msg = parsed.message;
164
- if (!msg || typeof msg !== "object" || msg.jsonrpc !== "2.0" || !("method" in msg)) {
165
- writeMessage(rpcError(msg?.id ?? null, -32600, "Invalid Request"));
88
+ if (method === "tools/list") {
89
+ send({ jsonrpc: "2.0", id: id, result: { tools: TOOLS.map(function(t) { return { name: t.name, description: t.description, inputSchema: t.inputSchema }; }) } });
90
+ continue;
91
+ }
92
+ if (method === "tools/call") {
93
+ (function(rid) {
94
+ var tn = String(((p.msg.params||{}).name)||"");
95
+ var ta = ((p.msg.params||{}).arguments && typeof (p.msg.params||{}).arguments === "object") ? p.msg.params.arguments : {};
96
+ callTool(tn, ta).then(function(text) {
97
+ send({ jsonrpc: "2.0", id: rid, result: { content: [{ type: "text", text: String(text) }] } });
98
+ }).catch(function(err) {
99
+ send({ jsonrpc: "2.0", id: rid, result: { content: [{ type: "text", text: "Error: " + err.message }], isError: true } });
100
+ });
101
+ })(id);
166
102
  continue;
167
103
  }
168
- if (!("id" in msg)) { try { await handle(msg); } catch {} continue; }
169
- try { const r = await handle(msg); if (r) writeMessage(r); }
170
- catch (err) { writeMessage(rpcError(msg.id ?? null, -32603, "Internal error", err?.message)); }
171
104
  }
172
105
  });
173
106
 
174
- process.stdin.on("end", () => process.exit(0));
107
+ process.stdin.on("end", function() {
108
+ setTimeout(function() { process.exit(0); }, 500);
109
+ });
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "storkai",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "The MCP server for MCP servers. Search, discover, and configure MCP servers from inside your IDE.",
5
- "type": "module",
6
5
  "bin": {
7
6
  "storkai": "bin/storkai.js"
8
7
  },
@@ -30,9 +29,6 @@
30
29
  "type": "git",
31
30
  "url": "https://github.com/stork-ai/storkai"
32
31
  },
33
- "dependencies": {
34
- "convex": "^1.31.2"
35
- },
36
32
  "engines": {
37
33
  "node": ">=18.0.0"
38
34
  }