storkai 1.0.1 → 1.0.3
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 +88 -189
- package/package.json +1 -2
package/bin/storkai.js
CHANGED
|
@@ -1,210 +1,109 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import process from "node:process";
|
|
14
|
-
|
|
15
|
-
const SERVER_NAME = "storkai";
|
|
16
|
-
const SERVER_VERSION = "1.0.1";
|
|
17
|
-
const CONVEX_URL = process.env.STORK_CONVEX_URL || "https://expert-egret-407.convex.cloud";
|
|
18
|
-
|
|
19
|
-
async function convexQuery(fnPath, args) {
|
|
20
|
-
const res = await fetch(`${CONVEX_URL}/api/query`, {
|
|
21
|
-
method: "POST",
|
|
22
|
-
headers: { "Content-Type": "application/json" },
|
|
23
|
-
body: JSON.stringify({ path: fnPath, args: args ?? {}, format: "json" }),
|
|
24
|
-
});
|
|
25
|
-
if (!res.ok) throw new Error(`Convex query failed: ${res.status}`);
|
|
26
|
-
const data = await res.json();
|
|
27
|
-
if (data.status === "error") throw new Error(data.errorMessage || "Query error");
|
|
28
|
-
return data.value;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function convexAction(fnPath, args) {
|
|
32
|
-
const res = await fetch(`${CONVEX_URL}/api/action`, {
|
|
33
|
-
method: "POST",
|
|
34
|
-
headers: { "Content-Type": "application/json" },
|
|
35
|
-
body: JSON.stringify({ path: fnPath, args: args ?? {}, format: "json" }),
|
|
36
|
-
});
|
|
37
|
-
if (!res.ok) throw new Error(`Convex action failed: ${res.status}`);
|
|
38
|
-
const data = await res.json();
|
|
39
|
-
if (data.status === "error") throw new Error(data.errorMessage || "Action error");
|
|
40
|
-
return data.value;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
let stdinBuffer = Buffer.alloc(0);
|
|
44
|
-
|
|
45
|
-
function writeMessage(obj) {
|
|
46
|
-
const json = JSON.stringify(obj);
|
|
47
|
-
process.stdout.write(`Content-Length: ${Buffer.byteLength(json, "utf8")}\r\n\r\n`);
|
|
48
|
-
process.stdout.write(json);
|
|
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; });
|
|
49
11
|
}
|
|
50
12
|
|
|
51
|
-
|
|
52
|
-
const headerEnd = buffer.indexOf("\r\n\r\n");
|
|
53
|
-
if (headerEnd === -1) return null;
|
|
54
|
-
const match = buffer.slice(0, headerEnd).toString("utf8").match(/Content-Length:\s*(\d+)/i);
|
|
55
|
-
if (!match) throw new Error("Missing Content-Length header");
|
|
56
|
-
const length = Number(match[1]);
|
|
57
|
-
const bodyStart = headerEnd + 4;
|
|
58
|
-
if (buffer.length < bodyStart + length) return null;
|
|
59
|
-
const body = buffer.slice(bodyStart, bodyStart + length).toString("utf8");
|
|
60
|
-
return { message: JSON.parse(body), rest: buffer.slice(bodyStart + length) };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function rpcError(id, code, message, data) {
|
|
64
|
-
return { jsonrpc: "2.0", id: id ?? null, error: { code, message, ...(data !== undefined ? { data } : {}) } };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function rpcResult(id, result) {
|
|
68
|
-
return { jsonrpc: "2.0", id, result };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function obj(v) {
|
|
72
|
-
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
73
|
-
}
|
|
13
|
+
var stdinBuf = Buffer.alloc(0);
|
|
74
14
|
|
|
75
|
-
function
|
|
76
|
-
|
|
15
|
+
function send(obj) {
|
|
16
|
+
fs.writeSync(1, JSON.stringify(obj) + "\n");
|
|
77
17
|
}
|
|
78
18
|
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
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)` : ""}`);
|
|
87
|
-
if (r.transportTypes.length) lines.push(` Transport: ${r.transportTypes.join(", ")}`);
|
|
88
|
-
if (r.installCommand) lines.push(` Install: ${r.installCommand}`);
|
|
89
|
-
if (r.authSummary) lines.push(` Auth: ${r.authSummary}`);
|
|
90
|
-
if (r.npmPackage) lines.push(` npm: ${r.npmPackage}`);
|
|
91
|
-
lines.push(` Homepage: ${r.homepageUrl}`, "");
|
|
92
|
-
}
|
|
93
|
-
lines.push("Use stork_server_details for full info, or stork_get_install_config for ready-to-paste config.");
|
|
94
|
-
return lines.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; }
|
|
95
26
|
}
|
|
96
27
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (d.npmPackage) l.push(`**npm**: ${d.npmPackage}`);
|
|
105
|
-
if (d.installCommand) l.push(`**Install**: \`${d.installCommand}\``);
|
|
106
|
-
if (d.authSummary) l.push(`**Auth**: ${d.authSummary}`);
|
|
107
|
-
l.push(`**Homepage**: ${d.homepageUrl}`);
|
|
108
|
-
if (d.repoUrl) l.push(`**Repository**: ${d.repoUrl}`);
|
|
109
|
-
if (d.tools?.length) { l.push("\n## Tools"); for (const t of d.tools) l.push(`- **${t.name}**${t.description ? `: ${t.description}` : ""}`); }
|
|
110
|
-
l.push("\n---\nUse stork_get_install_config for ready-to-paste config.");
|
|
111
|
-
return l.join("\n");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function fmtConfig(d) {
|
|
115
|
-
if (d?.error) return `Error: ${d.error}`;
|
|
116
|
-
const l = [`# Install ${d.serverName} for ${d.client}\n`, d.instructions, "", "```json", d.configJson, "```"];
|
|
117
|
-
if (d.authSummary) l.push("", `**Auth required**: ${d.authSummary}`);
|
|
118
|
-
if (d.docsUrl) l.push(`**Documentation**: ${d.docsUrl}`);
|
|
119
|
-
return l.join("\n");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function fmtCompare(d) {
|
|
123
|
-
if (d?.error) return `Error: ${d.error}`;
|
|
124
|
-
if (!d?.comparison?.length) return "No servers to compare.";
|
|
125
|
-
const l = ["# MCP Server Comparison\n"];
|
|
126
|
-
for (const s of d.comparison) {
|
|
127
|
-
l.push(`## ${s.name} (${s.slug})`);
|
|
128
|
-
l.push(`- ${s.summary}`);
|
|
129
|
-
if (s.trustScore != null) l.push(`- Trust: ${s.trustScore}/100`);
|
|
130
|
-
l.push(`- Tools (${s.toolCount}): ${s.tools.slice(0, 8).join(", ") || "none"}`);
|
|
131
|
-
l.push(`- Transport: ${s.transportTypes.join(", ") || "stdio"}`);
|
|
132
|
-
l.push(`- License: ${s.license}`);
|
|
133
|
-
if (s.npmPackage) l.push(`- npm: ${s.npmPackage}`);
|
|
134
|
-
l.push(`- Homepage: ${s.homepageUrl}`, "");
|
|
135
|
-
}
|
|
136
|
-
return l.join("\n");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const tools = [
|
|
140
|
-
{ 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)" } } },
|
|
141
|
-
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 convexAction("mcpDiscoveryActions:semanticSearchServers", args)); } catch { return fmtSearch(await convexQuery("mcpDiscovery:searchServers", args)); } } },
|
|
142
|
-
{ 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" } } },
|
|
143
|
-
handler: async (a) => fmtDetails(await convexQuery("mcpDiscovery:getServerDetails", { slug: String(a.slug) })) },
|
|
144
|
-
{ 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" } } },
|
|
145
|
-
handler: async (a) => fmtConfig(await convexQuery("mcpDiscovery:getInstallConfig", { slug: String(a.slug), client: String(a.client) })) },
|
|
146
|
-
{ 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" } } },
|
|
147
|
-
handler: async (a) => fmtCompare(await convexQuery("mcpDiscovery:compareServers", { slugs: Array.isArray(a.slugs) ? a.slugs.map(String) : [] })) },
|
|
148
|
-
{ name: "stork_list_filters", description: "List available categories and hosting filters with server counts.", inputSchema: { type: "object", properties: {} },
|
|
149
|
-
handler: async () => { const r = await convexQuery("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"); } },
|
|
150
|
-
{ 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" } } },
|
|
151
|
-
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" } } } },
|
|
152
35
|
];
|
|
153
36
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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);
|
|
170
66
|
}
|
|
171
|
-
if (method === "notifications/initialized" || method === "initialized") return null;
|
|
172
|
-
return rpcError(id, -32601, `Method not found: ${method}`);
|
|
173
67
|
}
|
|
174
68
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
async function processBuffer() {
|
|
69
|
+
process.stdin.resume();
|
|
70
|
+
process.stdin.on("data", function(chunk) {
|
|
71
|
+
stdinBuf = Buffer.concat([stdinBuf, chunk]);
|
|
179
72
|
while (true) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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;
|
|
185
87
|
}
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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);
|
|
191
102
|
continue;
|
|
192
103
|
}
|
|
193
|
-
if (!("id" in msg)) { try { await handle(msg); } catch {} continue; }
|
|
194
|
-
pendingRequests++;
|
|
195
|
-
try { const r = await handle(msg); if (r) writeMessage(r); }
|
|
196
|
-
catch (err) { writeMessage(rpcError(msg.id ?? null, -32603, "Internal error", err?.message)); }
|
|
197
|
-
pendingRequests--;
|
|
198
|
-
if (stdinEnded && pendingRequests === 0) process.exit(0);
|
|
199
104
|
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
process.stdin.on("data", (chunk) => {
|
|
203
|
-
stdinBuffer = Buffer.concat([stdinBuffer, chunk]);
|
|
204
|
-
processBuffer();
|
|
205
105
|
});
|
|
206
106
|
|
|
207
|
-
process.stdin.on("end", ()
|
|
208
|
-
|
|
209
|
-
if (pendingRequests === 0) process.exit(0);
|
|
107
|
+
process.stdin.on("end", function() {
|
|
108
|
+
setTimeout(function() { process.exit(0); }, 500);
|
|
210
109
|
});
|
package/package.json
CHANGED