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.
- package/bin/storkai.js +89 -154
- package/package.json +1 -5
package/bin/storkai.js
CHANGED
|
@@ -1,174 +1,109 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
|
75
|
-
|
|
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
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
return
|
|
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
|
-
|
|
117
|
-
{ name: "stork_search", description: "Search for MCP servers by natural language. Example: 'manage Jira tickets' or 'query Postgres databases'.
|
|
118
|
-
|
|
119
|
-
{ name: "
|
|
120
|
-
|
|
121
|
-
{ name: "
|
|
122
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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.
|
|
153
|
-
|
|
69
|
+
process.stdin.resume();
|
|
70
|
+
process.stdin.on("data", function(chunk) {
|
|
71
|
+
stdinBuf = Buffer.concat([stdinBuf, chunk]);
|
|
154
72
|
while (true) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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 (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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", ()
|
|
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.
|
|
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
|
}
|