@noelclaw/mcp 1.0.0 → 1.2.0

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
@@ -5,7 +5,7 @@
5
5
  Noelclaw as an MCP skill — persistent memory, multi-agent coordination, scenario simulation, DeFi execution, and Sentinel-gated playbooks. Works with Claude, Cursor, Hermes, Windsurf, and any MCP-compatible client.
6
6
 
7
7
  ```bash
8
- npx @noelclaw/mcp@latest
8
+ npx @noelclaw/mcp
9
9
  ```
10
10
 
11
11
  ---
@@ -14,12 +14,12 @@ npx @noelclaw/mcp@latest
14
14
 
15
15
  ### Claude Code
16
16
  ```bash
17
- claude mcp add noelclaw -- npx @noelclaw/mcp@latest
17
+ claude mcp add noelclaw -- npx @noelclaw/mcp
18
18
  ```
19
19
 
20
20
  Set your API key:
21
21
  ```bash
22
- claude mcp add noelclaw -e NOELCLAW_API_KEY=noel_... -- npx @noelclaw/mcp@latest
22
+ claude mcp add noelclaw -e NOELCLAW_API_KEY=noel_... -- npx @noelclaw/mcp
23
23
  ```
24
24
 
25
25
  ### Claude Desktop
@@ -30,7 +30,7 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac) or
30
30
  "mcpServers": {
31
31
  "noelclaw": {
32
32
  "command": "npx",
33
- "args": ["@noelclaw/mcp@latest"],
33
+ "args": ["@noelclaw/mcp"],
34
34
  "env": {
35
35
  "NOELCLAW_API_KEY": "noel_..."
36
36
  }
@@ -45,7 +45,7 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac) or
45
45
  "mcpServers": {
46
46
  "noelclaw": {
47
47
  "command": "npx",
48
- "args": ["@noelclaw/mcp@latest"],
48
+ "args": ["@noelclaw/mcp"],
49
49
  "env": {
50
50
  "NOELCLAW_API_KEY": "noel_..."
51
51
  }
@@ -60,7 +60,7 @@ mcp_servers:
60
60
  noelclaw:
61
61
  command: npx
62
62
  args:
63
- - "@noelclaw/mcp@latest"
63
+ - "@noelclaw/mcp"
64
64
  env:
65
65
  NOELCLAW_API_KEY: "noel_..."
66
66
  ```
@@ -175,13 +175,6 @@ Set `NOELCLAW_API_KEY` in your MCP config. That's it.
175
175
  |-----|-------------|
176
176
  | `NOELCLAW_API_KEY` | Your API key (`noel_...`) — get one at `POST https://api.noelclaw.com/auth/key` |
177
177
 
178
- ### MiroShark (optional)
179
-
180
- | Var | Description |
181
- |-----|-------------|
182
- | `MIROSHARK_URL` | URL of your deployed MiroShark instance |
183
- | `MIROSHARK_ADMIN_TOKEN` | Admin token set on your MiroShark deployment |
184
-
185
178
  ### BYOK (optional)
186
179
 
187
180
  | Var | Used for |
@@ -234,7 +227,6 @@ get_noel_ledger
234
227
  |-------|-----|
235
228
  | Tools not appearing | Restart your MCP client after adding the config |
236
229
  | `401 Unauthorized` | Check `NOELCLAW_API_KEY` is set — get one at `POST https://api.noelclaw.com/auth/key` |
237
- | `miroshark_simulate` error | Set `MIROSHARK_URL` and `MIROSHARK_ADMIN_TOKEN` |
238
230
  | Server starts but no response | Expected — server waits for MCP stdin, not HTTP |
239
231
 
240
232
  ---
@@ -242,5 +234,5 @@ get_noel_ledger
242
234
  ## Links
243
235
 
244
236
  - npm: [npmjs.com/package/@noelclaw/mcp](https://npmjs.com/package/@noelclaw/mcp)
245
- - GitHub: [github.com/noelclaw/research](https://github.com/noelclaw/mcp)
237
+ - GitHub: [github.com/noelclaw/mcp](https://github.com/noelclaw/mcp)
246
238
  - Platform: [noelclaw.com](https://noelclaw.com)
@@ -2,11 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MIROSHARK_TOOLS = void 0;
4
4
  exports.handleMirosharkTool = handleMirosharkTool;
5
+ const CONVEX_SITE = process.env.NOELCLAW_CONVEX_URL ?? "https://api.noelclaw.com";
5
6
  exports.MIROSHARK_TOOLS = [
6
7
  {
7
8
  name: "miroshark_simulate",
8
- description: "Run a MiroShark multi-agent simulation. Describe a scenario in plain English and get back strategic insights from a network of AI agents (market actors, risk managers, analysts). " +
9
- "Returns a simulation ID you can poll with miroshark_status.",
9
+ description: "Run a MiroShark multi-agent simulation. Describe any scenario in plain English — market crashes, policy changes, social events — and get back a running simulation with AI agents acting as market participants, analysts, and social actors. " +
10
+ "Handles the full setup automatically (knowledge graph, agent profiles). Returns a simulation_id to poll with miroshark_status.",
10
11
  inputSchema: {
11
12
  type: "object",
12
13
  properties: {
@@ -14,21 +15,13 @@ exports.MIROSHARK_TOOLS = [
14
15
  type: "string",
15
16
  description: "Plain-English description of the scenario to simulate. E.g. 'What happens if ETH drops 20% and whale wallets start selling?'",
16
17
  },
17
- agents: {
18
- type: "number",
19
- description: "Number of agents in the simulation (default: 10, max: 50)",
20
- },
21
- steps: {
22
- type: "number",
23
- description: "Number of simulation steps to run (default: 5)",
24
- },
25
18
  },
26
19
  required: ["scenario"],
27
20
  },
28
21
  },
29
22
  {
30
23
  name: "miroshark_status",
31
- description: "Poll the status and results of a MiroShark simulation by ID. Returns agent outputs, consensus findings, and final strategic insights when complete.",
24
+ description: "Poll the status of a MiroShark simulation. Returns preparation progress, running progress, or final results. Automatically starts the simulation when agent preparation completes.",
32
25
  inputSchema: {
33
26
  type: "object",
34
27
  properties: {
@@ -41,71 +34,121 @@ exports.MIROSHARK_TOOLS = [
41
34
  },
42
35
  },
43
36
  ];
44
- function getConfig() {
45
- const url = process.env.MIROSHARK_URL?.replace(/\/$/, "");
46
- const token = process.env.MIROSHARK_ADMIN_TOKEN;
47
- return { url, token };
37
+ // ── HTTP helpers ──────────────────────────────────────────────────────────────
38
+ function authHeaders() {
39
+ const key = process.env.NOELCLAW_API_KEY ?? process.env.NOELCLAW_SESSION_TOKEN;
40
+ return key ? { Authorization: `Bearer ${key}` } : {};
48
41
  }
49
- async function mirofetch(path, method, body) {
50
- const { url, token } = getConfig();
51
- if (!url)
52
- throw new Error("MIROSHARK_URL not set");
53
- if (!token)
54
- throw new Error("MIROSHARK_ADMIN_TOKEN not set");
55
- const res = await fetch(`${url}${path}`, {
42
+ async function miroJson(path, method, body, timeoutMs = 90000) {
43
+ const res = await fetch(`${CONVEX_SITE}${path}`, {
56
44
  method,
57
- headers: {
58
- "Content-Type": "application/json",
59
- "Authorization": `Bearer ${token}`,
60
- },
61
- ...(body ? { body: JSON.stringify(body) } : {}),
62
- signal: AbortSignal.timeout(30000),
45
+ headers: { ...authHeaders(), "Content-Type": "application/json" },
46
+ body: body !== undefined ? JSON.stringify(body) : undefined,
47
+ signal: AbortSignal.timeout(timeoutMs),
48
+ });
49
+ const text = await res.text();
50
+ if (!res.ok)
51
+ throw new Error(`MiroShark ${method} ${path} [${res.status}]: ${text.slice(0, 300)}`);
52
+ let json;
53
+ try {
54
+ json = JSON.parse(text);
55
+ }
56
+ catch {
57
+ throw new Error(`MiroShark non-JSON response: ${text.slice(0, 200)}`);
58
+ }
59
+ if (json.success === false)
60
+ throw new Error(`MiroShark error: ${json.error ?? JSON.stringify(json).slice(0, 300)}`);
61
+ return json.data ?? json;
62
+ }
63
+ async function miroForm(path, form, timeoutMs = 120000) {
64
+ // No Content-Type header — browser/fetch sets multipart boundary automatically
65
+ const res = await fetch(`${CONVEX_SITE}${path}`, {
66
+ method: "POST",
67
+ headers: authHeaders(),
68
+ body: form,
69
+ signal: AbortSignal.timeout(timeoutMs),
63
70
  });
64
- if (!res.ok) {
65
- const err = await res.text();
66
- throw new Error(`MiroShark ${res.status}: ${err.slice(0, 200)}`);
71
+ const text = await res.text();
72
+ if (!res.ok)
73
+ throw new Error(`MiroShark POST ${path} [${res.status}]: ${text.slice(0, 300)}`);
74
+ let json;
75
+ try {
76
+ json = JSON.parse(text);
67
77
  }
68
- return res.json();
78
+ catch {
79
+ throw new Error(`MiroShark non-JSON response: ${text.slice(0, 200)}`);
80
+ }
81
+ if (json.success === false)
82
+ throw new Error(`MiroShark error: ${json.error ?? JSON.stringify(json).slice(0, 300)}`);
83
+ return json.data ?? json;
84
+ }
85
+ async function pollUntilDone(taskPath, pollIntervalMs = 8000, maxWaitMs = 180000) {
86
+ const deadline = Date.now() + maxWaitMs;
87
+ while (Date.now() < deadline) {
88
+ await new Promise(r => setTimeout(r, pollIntervalMs));
89
+ const task = await miroJson(taskPath, "GET");
90
+ const s = (task.status ?? "").toLowerCase();
91
+ if (s === "completed" || s === "success")
92
+ return task;
93
+ if (s === "failed" || s === "error") {
94
+ throw new Error(`Task failed: ${task.error ?? task.message ?? s}`);
95
+ }
96
+ }
97
+ throw new Error("Task timed out after 3 minutes");
69
98
  }
99
+ // ── Tool handler ──────────────────────────────────────────────────────────────
70
100
  async function handleMirosharkTool(name, args) {
71
101
  const a = (args ?? {});
102
+ // ── miroshark_simulate ────────────────────────────────────────────────────
72
103
  if (name === "miroshark_simulate") {
73
104
  if (!a.scenario?.trim()) {
74
105
  return { content: [{ type: "text", text: "scenario is required" }], isError: true };
75
106
  }
76
- const { url, token } = getConfig();
77
- if (!url || !token) {
78
- return {
79
- content: [{ type: "text", text: "MiroShark not configured — set MIROSHARK_URL and MIROSHARK_ADMIN_TOKEN" }],
80
- isError: true,
81
- };
82
- }
83
107
  try {
84
- // Step 1: ask (parse scenario into simulation params)
85
- const asked = await mirofetch("/api/simulation/ask", "POST", { question: a.scenario });
86
- // Step 2: create simulation
87
- const created = await mirofetch("/api/simulation/create", "POST", {
88
- ...asked,
89
- num_agents: Math.min(a.agents ?? 10, 50),
90
- num_steps: a.steps ?? 5,
91
- });
108
+ // Step 1: convert plain-English question into a structured seed document
109
+ const asked = await miroJson("/miroshark/api/simulation/ask", "POST", { question: a.scenario });
110
+ const { title, seed_document, simulation_requirement } = asked;
111
+ // Step 2: generate knowledge-graph ontology from the seed document
112
+ const form = new FormData();
113
+ form.append("simulation_requirement", simulation_requirement ?? a.scenario);
114
+ form.append("project_name", (title ?? a.scenario).slice(0, 100));
115
+ form.append("url_docs", JSON.stringify([{
116
+ title: title ?? "Simulation Context",
117
+ url: "",
118
+ text: seed_document ?? a.scenario,
119
+ }]));
120
+ const ontology = await miroForm("/miroshark/api/graph/ontology/generate", form);
121
+ const projectId = ontology.project_id;
122
+ if (!projectId)
123
+ throw new Error("No project_id in ontology response");
124
+ // Step 3: kick off the async graph build
125
+ const built = await miroJson("/miroshark/api/graph/build", "POST", { project_id: projectId });
126
+ const graphTaskId = built.task_id;
127
+ if (!graphTaskId)
128
+ throw new Error("No task_id in graph build response");
129
+ // Step 4: wait for graph to finish (up to 3 min)
130
+ await pollUntilDone(`/miroshark/api/graph/task/${graphTaskId}`);
131
+ // Step 5: create simulation from the built graph
132
+ const created = await miroJson("/miroshark/api/simulation/create", "POST", { project_id: projectId });
92
133
  const simId = created.simulation_id ?? created.id;
93
134
  if (!simId)
94
- throw new Error("No simulation ID in create response");
95
- // Step 3: prepare
96
- await mirofetch(`/api/simulation/${simId}/prepare`, "POST", {});
97
- // Step 4: start
98
- await mirofetch(`/api/simulation/${simId}/start`, "POST", {});
135
+ throw new Error("No simulation_id in create response");
136
+ // Step 6: kick off agent preparation (async — don't block)
137
+ const prepared = await miroJson("/miroshark/api/simulation/prepare", "POST", { simulation_id: simId });
138
+ const prepTaskId = prepared.task_id;
99
139
  return {
100
140
  content: [{
101
141
  type: "text",
102
142
  text: [
103
- `🦈 **MiroShark simulation started**`,
143
+ `**MiroShark simulation queued** ✓`,
144
+ ``,
104
145
  `Scenario: ${a.scenario}`,
146
+ `Project: \`${projectId}\``,
105
147
  `Simulation ID: \`${simId}\``,
106
- `Agents: ${a.agents ?? 10} · Steps: ${a.steps ?? 5}`,
148
+ `Status: preparing agents${prepTaskId ? ` (task: ${prepTaskId})` : ""}`,
107
149
  ``,
108
- `Poll results with: \`miroshark_status simulation_id="${simId}"\``,
150
+ `Agent preparation runs in the background. Poll progress with:`,
151
+ `\`miroshark_status simulation_id="${simId}"\``,
109
152
  ].join("\n"),
110
153
  }],
111
154
  };
@@ -114,38 +157,106 @@ async function handleMirosharkTool(name, args) {
114
157
  return { content: [{ type: "text", text: `MiroShark error: ${err.message}` }], isError: true };
115
158
  }
116
159
  }
160
+ // ── miroshark_status ──────────────────────────────────────────────────────
117
161
  if (name === "miroshark_status") {
118
162
  if (!a.simulation_id?.trim()) {
119
163
  return { content: [{ type: "text", text: "simulation_id is required" }], isError: true };
120
164
  }
165
+ const simId = a.simulation_id.trim();
121
166
  try {
122
- const data = await mirofetch(`/api/simulation/${a.simulation_id}/status`, "GET");
123
- const status = data.status ?? "unknown";
124
- const lines = [
125
- `🦈 **MiroShark Simulation \`${a.simulation_id}\`**`,
126
- `Status: **${status}**`,
127
- ];
128
- if (data.progress != null)
129
- lines.push(`Progress: ${data.progress}%`);
130
- if (status === "completed" && data.results) {
131
- lines.push("", "**Results**");
132
- if (data.results.summary)
133
- lines.push(data.results.summary);
134
- if (Array.isArray(data.results.insights)) {
135
- for (const insight of data.results.insights.slice(0, 5)) {
136
- lines.push(`• ${typeof insight === "string" ? insight : JSON.stringify(insight)}`);
137
- }
138
- }
139
- if (data.results.consensus)
140
- lines.push("", `**Consensus:** ${data.results.consensus}`);
167
+ // Check if agents are still being prepared
168
+ const prepStatus = await miroJson("/miroshark/api/simulation/prepare/status", "POST", { simulation_id: simId }).catch(() => null);
169
+ const prepState = (prepStatus?.status ?? "").toLowerCase();
170
+ if (prepState === "processing" || prepState === "pending") {
171
+ const pct = prepStatus?.progress ?? 0;
172
+ const msg = prepStatus?.message ?? "preparing agents...";
173
+ return {
174
+ content: [{
175
+ type: "text",
176
+ text: [
177
+ `**MiroShark \`${simId}\`** — preparing agents`,
178
+ `Progress: ${pct}%`,
179
+ `${msg}`,
180
+ ``,
181
+ `Poll again in a few seconds.`,
182
+ ].join("\n"),
183
+ }],
184
+ };
141
185
  }
142
- else if (status === "failed") {
143
- lines.push("", `Error: ${data.error ?? "unknown"}`);
186
+ // Agents ready (or prepare already done) — check run status
187
+ const runStatus = await miroJson(`/miroshark/api/simulation/${simId}/run-status`, "GET").catch(() => ({ runner_status: "idle" }));
188
+ const runnerStatus = (runStatus?.runner_status ?? "idle").toLowerCase();
189
+ // Auto-start the simulation when agents are ready but not yet running
190
+ if (runnerStatus === "idle" && (prepState === "ready" || prepState === "completed" || prepStatus?.already_prepared)) {
191
+ await miroJson("/miroshark/api/simulation/start", "POST", {
192
+ simulation_id: simId,
193
+ platform: "parallel",
194
+ });
195
+ return {
196
+ content: [{
197
+ type: "text",
198
+ text: [
199
+ `**MiroShark \`${simId}\`** — simulation started`,
200
+ ``,
201
+ `Agents are now active. Poll again in ~15 seconds for progress.`,
202
+ `\`miroshark_status simulation_id="${simId}"\``,
203
+ ].join("\n"),
204
+ }],
205
+ };
144
206
  }
145
- else {
146
- lines.push("", "Simulation still running poll again in a few seconds.");
207
+ if (runnerStatus === "running") {
208
+ const round = runStatus.current_round ?? 0;
209
+ const total = runStatus.total_rounds ?? "?";
210
+ const pct = runStatus.progress_percent?.toFixed(1) ?? "0";
211
+ const twitterActs = runStatus.twitter_actions_count ?? 0;
212
+ const redditActs = runStatus.reddit_actions_count ?? 0;
213
+ return {
214
+ content: [{
215
+ type: "text",
216
+ text: [
217
+ `**MiroShark \`${simId}\`** — running`,
218
+ `Round: ${round} / ${total} (${pct}%)`,
219
+ `Actions: ${twitterActs} Twitter · ${redditActs} Reddit`,
220
+ ``,
221
+ `Simulation in progress — poll again in ~15 seconds.`,
222
+ ].join("\n"),
223
+ }],
224
+ };
147
225
  }
148
- return { content: [{ type: "text", text: lines.join("\n") }] };
226
+ if (runnerStatus === "completed" || runnerStatus === "stopped") {
227
+ // Fetch a sample of agent actions for the summary
228
+ const actionsData = await miroJson(`/miroshark/api/simulation/${simId}/actions?limit=10`, "GET").catch(() => ({ actions: [] }));
229
+ const actions = actionsData?.actions ?? [];
230
+ const lines = [
231
+ `**MiroShark \`${simId}\`** — ${runnerStatus}`,
232
+ ``,
233
+ `Rounds completed: ${runStatus.current_round ?? "?"}`,
234
+ `Total actions: ${runStatus.total_actions_count ?? actions.length}`,
235
+ ];
236
+ if (actions.length > 0) {
237
+ lines.push("", "**Sample agent activity:**");
238
+ for (const act of actions.slice(0, 8)) {
239
+ const who = act.agent_name ?? act.agent_id ?? "agent";
240
+ const what = act.action_type ?? act.type ?? "action";
241
+ const content = act.content ?? act.text ?? "";
242
+ lines.push(`• **${who}** [${what}]${content ? `: ${String(content).slice(0, 80)}` : ""}`);
243
+ }
244
+ }
245
+ lines.push("", `Full transcript: \`miroshark_status\` returns results above.`);
246
+ return { content: [{ type: "text", text: lines.join("\n") }] };
247
+ }
248
+ // Fallback: unknown state
249
+ return {
250
+ content: [{
251
+ type: "text",
252
+ text: [
253
+ `**MiroShark \`${simId}\`** — status: ${runnerStatus || "unknown"}`,
254
+ prepState ? `Prepare state: ${prepState}` : "",
255
+ ``,
256
+ `If agents are still preparing, poll again shortly.`,
257
+ ].filter(Boolean).join("\n"),
258
+ }],
259
+ };
149
260
  }
150
261
  catch (err) {
151
262
  return { content: [{ type: "text", text: `MiroShark error: ${err.message}` }], isError: true };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noelclaw/mcp",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Noelclaw as an MCP skill — persistent memory, multi-agent coordination, scenario simulation, DeFi execution, and Sentinel-gated playbooks.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -27,11 +27,11 @@
27
27
  ],
28
28
  "repository": {
29
29
  "type": "git",
30
- "url": "git+https://github.com/noelclaw/noelmcp.git"
30
+ "url": "git+https://github.com/noelclaw/mcp.git"
31
31
  },
32
- "homepage": "https://github.com/noelclaw/noelmcp#readme",
32
+ "homepage": "https://github.com/noelclaw/mcp#readme",
33
33
  "bugs": {
34
- "url": "https://github.com/noelclaw/noelmcp/issues"
34
+ "url": "https://github.com/noelclaw/mcp/issues"
35
35
  },
36
36
  "license": "MIT",
37
37
  "files": [