@nexusbloom/mcp-server 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/index.js CHANGED
@@ -76,6 +76,7 @@ async function fetchTools() {
76
76
  price_type: t.price_type || t.pricing || "free",
77
77
  version: t.version || "1.0.0",
78
78
  type: "builtin",
79
+ input_schema: t.input_schema || null,
79
80
  }));
80
81
  toolsCacheTime = now;
81
82
  return toolsCache;
@@ -90,42 +91,16 @@ async function fetchTools() {
90
91
  // ---------------------------------------------------------------------------
91
92
 
92
93
  async function fetchToolSchema(slug) {
93
- try {
94
- const res = await fetch(`${API_URL}/tools/${encodeURIComponent(slug)}`, {
95
- headers: apiHeaders(),
96
- ...FETCH_OPTS,
97
- });
98
- if (res.ok) {
99
- const json = await res.json();
100
- const tool = json.tool || json.data || json;
101
- if (tool && tool.slug) {
102
- return {
103
- slug: tool.slug,
104
- name: tool.name,
105
- description: tool.short_description || tool.full_description || tool.description || "",
106
- tags: (tool.tags || []).flat().filter(Boolean),
107
- input_schema: tool.input_schema || {},
108
- output_schema: tool.output_schema || {},
109
- icon: tool.icon || "🔧",
110
- price_type: tool.price_type || tool.pricing || "free",
111
- version: tool.version || "1.0.0",
112
- category: tool.category || "Platform Tools",
113
- };
114
- }
115
- }
116
- } catch (err) {
117
- console.error(`Failed to fetch schema for ${slug}:`, err.message);
118
- }
119
-
120
- // Fallback: try the manifest endpoint
94
+ // Primary: fetch manifest from /api/run/{slug} (the reliable endpoint)
121
95
  try {
122
96
  const res = await fetch(`${API_URL}/run/${encodeURIComponent(slug)}`, {
123
97
  headers: apiHeaders(),
124
98
  ...FETCH_OPTS,
125
99
  });
126
100
  if (res.ok) {
127
- const json = await res.json();
128
- const manifest = json.manifest || json.data?.manifest || json;
101
+ // API returns { success: true, data: { manifest: { ... } } }
102
+ const body = await res.json();
103
+ const manifest = body.data?.manifest || body.manifest || body;
129
104
  if (manifest && manifest.slug) {
130
105
  return {
131
106
  slug: manifest.slug,
@@ -156,7 +131,7 @@ async function executeTool(slug, params) {
156
131
  const res = await fetch(`${API_URL}/run/${encodeURIComponent(slug)}`, {
157
132
  method: "POST",
158
133
  headers: apiHeaders(),
159
- body: JSON.stringify({ input: params }),
134
+ body: JSON.stringify(params),
160
135
  ...FETCH_OPTS,
161
136
  });
162
137
 
@@ -199,6 +174,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
199
174
  description: `${t.name}: ${t.short_description || "No description"}${
200
175
  t.category ? ` [${t.category}]` : ""
201
176
  }`,
177
+ inputSchema: t.input_schema || {
178
+ type: "object",
179
+ properties: {},
180
+ },
181
+ }));
182
+
183
+ // Also expose the meta-tool for list/schema/run
184
+ toolList.push({
185
+ name: "nexusbloom",
186
+ description: `Meta-tool to list, inspect, or run any NexusBloom tool. Use this when you need to discover tools or get schema details.`,
202
187
  inputSchema: {
203
188
  type: "object",
204
189
  properties: {
@@ -221,7 +206,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
221
206
  },
222
207
  required: ["command"],
223
208
  },
224
- }));
209
+ });
225
210
 
226
211
  return { tools: toolList };
227
212
  });
@@ -230,10 +215,34 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
230
215
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
231
216
  const { name: toolName, arguments: args } = request.params;
232
217
 
218
+ // ── Direct tool execution by slug ──────────────────────────────
219
+ // AI agents can call any tool directly by its slug name.
220
+ // The tool's own input_schema defines the expected parameters.
221
+ // We verify the slug exists by checking against our tool list.
233
222
  if (toolName !== "nexusbloom") {
234
- throw new Error(`Unknown tool: ${toolName}`);
223
+ const tools = await fetchTools();
224
+ const matched = tools.find((t) => t.slug === toolName);
225
+ if (!matched) {
226
+ return {
227
+ content: [{ type: "text", text: `Unknown tool: "${toolName}". Use the "nexusbloom" tool with command "list" to see available tools.` }],
228
+ isError: true,
229
+ };
230
+ }
231
+ try {
232
+ const result = await executeTool(toolName, args || {});
233
+ const formatted = JSON.stringify(result.data, null, 2);
234
+ return {
235
+ content: [
236
+ { type: "text", text: `# ${matched.name} (${toolName})\n\n\`\`\`json\n${formatted}\n\`\`\`` },
237
+ { type: "resource", resource: { text: formatted, uri: "data:application/json,", mimeType: "application/json" } },
238
+ ],
239
+ };
240
+ } catch (err) {
241
+ return { content: [{ type: "text", text: `Error executing "${toolName}": ${err.message}` }], isError: true };
242
+ }
235
243
  }
236
244
 
245
+ // ── Legacy meta-tool: list/schema/run via sub-commands ─────────
237
246
  const command = args?.command;
238
247
 
239
248
  if (!command || !["list", "schema", "run"].includes(command)) {
@@ -363,16 +372,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
363
372
  // ---- Start ----
364
373
  async function main() {
365
374
  try {
366
- // Verify API connectivity
367
- try {
368
- const res = await fetch(`${API_URL}/tools`, { ...FETCH_OPTS });
369
- if (res.ok) {
370
- console.error(`NexusBloom MCP server connected to ${API_URL}`);
371
- } else {
372
- console.error(`WARNING: API at ${API_URL} returned ${res.status}`);
375
+ // Verify API connectivity — try multiple possible API URLs
376
+ const apiUrlCandidates = [
377
+ API_URL,
378
+ API_URL.replace("/api", ""), // base domain without /api
379
+ ];
380
+
381
+ let connected = false;
382
+ for (const url of apiUrlCandidates) {
383
+ try {
384
+ const testUrl = url.includes("/api") ? `${url}/tools` : `${url}/api/tools`;
385
+ const res = await fetch(testUrl, { ...FETCH_OPTS });
386
+ if (res.ok || res.status === 429) {
387
+ console.error(`NexusBloom MCP server connected to ${url}`);
388
+ connected = true;
389
+ break;
390
+ } else if (res.status === 404) {
391
+ // Try without /tools path
392
+ const res2 = await fetch(url, { ...FETCH_OPTS });
393
+ if (res2.ok) {
394
+ console.error(`NexusBloom MCP server connected to ${url}`);
395
+ connected = true;
396
+ break;
397
+ }
398
+ }
399
+ } catch (_) {
400
+ // Continue to next candidate
373
401
  }
374
- } catch (err) {
375
- console.error(`WARNING: Could not reach API at ${API_URL}: ${err.message}`);
402
+ }
403
+
404
+ if (!connected) {
405
+ console.error(
406
+ `WARNING: Could not reach NexusBloom API. ` +
407
+ `The MCP server will start in degraded mode — tool execution requires a working API.\n` +
408
+ `Checked endpoints:\n` +
409
+ apiUrlCandidates.map(u => ` - ${u.includes("/api") ? `${u}/tools` : `${u}/api/tools`}`).join("\n") + "\n" +
410
+ `To fix: ensure NEXUSBLOOM_API_URL is set correctly (default: https://nexusbloom.dev/api)\n` +
411
+ `Or deploy the platform API and verify the Vercel rewrite from your frontend project.`
412
+ );
376
413
  }
377
414
 
378
415
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexusbloom/mcp-server",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "MCP server for NexusBloom — AI agents discover and execute tools through a single interface. No Supabase credentials needed, just connect to our API.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -13,4 +13,4 @@
13
13
  "dependencies": {
14
14
  "@modelcontextprotocol/sdk": "^1.8.0"
15
15
  }
16
- }
16
+ }
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server Test — uses the MCP SDK Client directly.
4
+ * Tests both direct slug execution and meta-tool paths.
5
+ *
6
+ * Usage: node test-mcp-client.mjs
7
+ */
8
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
9
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
10
+
11
+ async function main() {
12
+ console.log("═══ MCP Server Test ═══\n");
13
+
14
+ const transport = new StdioClientTransport({
15
+ command: "npx",
16
+ args: ["@nexusbloom/mcp-server"],
17
+ });
18
+
19
+ const client = new Client(
20
+ { name: "mcp-test-client", version: "1.0.0" },
21
+ { capabilities: {} }
22
+ );
23
+
24
+ await client.connect(transport);
25
+ console.log("✅ Connected to MCP server\n");
26
+
27
+ // ── Test 1: ListTools ─────────────────────────────────────────
28
+ const { tools } = await client.listTools();
29
+ console.log(`✅ Tools available: ${tools.length}`);
30
+ const slugs = tools.filter((t) => t.name !== "nexusbloom").map((t) => t.name);
31
+ const metaTool = tools.find((t) => t.name === "nexusbloom");
32
+ if (metaTool) console.log(`✅ Meta-tool "nexusbloom" registered`);
33
+ console.log(` Example tools: ${slugs.slice(0, 5).join(", ")}...`);
34
+ console.log();
35
+
36
+ // ── Test 2: Direct slug execution ─────────────────────────────
37
+ // AI agents call tools by their slug name directly
38
+ console.log("── Test: Direct slug execution ──");
39
+ const cronResult = await client.callTool({
40
+ name: "cron-expression-builder",
41
+ arguments: { expression: "0 0 * * *" },
42
+ });
43
+ const cronText = cronResult.content.find((c) => c.type === "text")?.text || "";
44
+ if (cronText.includes(`"valid": true`)) {
45
+ console.log("✅ cron-expression-builder executed directly by slug");
46
+ console.log(` → ${cronText.split("\n")[1] || "valid=true"}`);
47
+ } else {
48
+ console.log(`⚠ Unexpected: ${cronText.substring(0, 100)}`);
49
+ }
50
+
51
+ const dataResult = await client.callTool({
52
+ name: "data-generator",
53
+ arguments: { count: 2 },
54
+ });
55
+ const dataText = dataResult.content.find((c) => c.type === "text")?.text || "";
56
+ if (dataText.includes("data-generator")) {
57
+ console.log("✅ data-generator executed directly by slug");
58
+ }
59
+ console.log();
60
+
61
+ // ── Test 3: Meta-tool "list" ──────────────────────────────────
62
+ console.log("── Test: Meta-tool list ──");
63
+ const listResult = await client.callTool({
64
+ name: "nexusbloom",
65
+ arguments: { command: "list" },
66
+ });
67
+ const listText = listResult.content.find((c) => c.type === "text")?.text || "";
68
+ if (listText.includes("NexusBloom Tools")) {
69
+ console.log("✅ Meta-tool list works");
70
+ }
71
+ console.log();
72
+
73
+ // ── Test 4: Meta-tool "schema" ────────────────────────────────
74
+ console.log("── Test: Meta-tool schema ──");
75
+ const schemaResult = await client.callTool({
76
+ name: "nexusbloom",
77
+ arguments: { command: "schema", slug: "cron-expression-builder" },
78
+ });
79
+ const schemaText = schemaResult.content.find((c) => c.type === "text")?.text || "";
80
+ if (schemaText.includes("Cron Expression Builder")) {
81
+ console.log("✅ Meta-tool schema works");
82
+ }
83
+ console.log();
84
+
85
+ // ── Test 5: Meta-tool "run" ───────────────────────────────────
86
+ console.log("── Test: Meta-tool run ──");
87
+ const runResult = await client.callTool({
88
+ name: "nexusbloom",
89
+ arguments: { command: "run", slug: "data-generator", params: { count: 3 } },
90
+ });
91
+ const runText = runResult.content.find((c) => c.type === "text")?.text || "";
92
+ if (runText.includes("data-generator")) {
93
+ console.log("✅ Meta-tool run works");
94
+ }
95
+ console.log();
96
+
97
+ await client.close();
98
+ console.log("═══ All MCP tests passed! ═══");
99
+ }
100
+
101
+ main().catch((err) => {
102
+ console.error("❌ MCP test failed:", err.message);
103
+ process.exit(1);
104
+ });