@noelclaw/mcp 1.1.0 → 1.2.1
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/dist/tools/miroshark.js +194 -50
- package/package.json +1 -1
package/dist/tools/miroshark.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MIROSHARK_TOOLS = void 0;
|
|
4
4
|
exports.handleMirosharkTool = handleMirosharkTool;
|
|
5
|
-
const
|
|
5
|
+
const CONVEX_SITE = process.env.NOELCLAW_CONVEX_URL ?? "https://api.noelclaw.com";
|
|
6
6
|
exports.MIROSHARK_TOOLS = [
|
|
7
7
|
{
|
|
8
8
|
name: "miroshark_simulate",
|
|
9
|
-
description: "Run a MiroShark multi-agent simulation. Describe
|
|
10
|
-
"Returns a
|
|
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.",
|
|
11
11
|
inputSchema: {
|
|
12
12
|
type: "object",
|
|
13
13
|
properties: {
|
|
@@ -15,21 +15,13 @@ exports.MIROSHARK_TOOLS = [
|
|
|
15
15
|
type: "string",
|
|
16
16
|
description: "Plain-English description of the scenario to simulate. E.g. 'What happens if ETH drops 20% and whale wallets start selling?'",
|
|
17
17
|
},
|
|
18
|
-
agents: {
|
|
19
|
-
type: "number",
|
|
20
|
-
description: "Number of agents in the simulation (default: 10, max: 50)",
|
|
21
|
-
},
|
|
22
|
-
steps: {
|
|
23
|
-
type: "number",
|
|
24
|
-
description: "Number of simulation steps to run (default: 5)",
|
|
25
|
-
},
|
|
26
18
|
},
|
|
27
19
|
required: ["scenario"],
|
|
28
20
|
},
|
|
29
21
|
},
|
|
30
22
|
{
|
|
31
23
|
name: "miroshark_status",
|
|
32
|
-
description: "Poll the status
|
|
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.",
|
|
33
25
|
inputSchema: {
|
|
34
26
|
type: "object",
|
|
35
27
|
properties: {
|
|
@@ -42,37 +34,121 @@ exports.MIROSHARK_TOOLS = [
|
|
|
42
34
|
},
|
|
43
35
|
},
|
|
44
36
|
];
|
|
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}` } : {};
|
|
41
|
+
}
|
|
42
|
+
async function miroJson(path, method, body, timeoutMs = 90000) {
|
|
43
|
+
const res = await fetch(`${CONVEX_SITE}${path}`, {
|
|
44
|
+
method,
|
|
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),
|
|
70
|
+
});
|
|
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);
|
|
77
|
+
}
|
|
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");
|
|
98
|
+
}
|
|
99
|
+
// ── Tool handler ──────────────────────────────────────────────────────────────
|
|
45
100
|
async function handleMirosharkTool(name, args) {
|
|
46
101
|
const a = (args ?? {});
|
|
102
|
+
// ── miroshark_simulate ────────────────────────────────────────────────────
|
|
47
103
|
if (name === "miroshark_simulate") {
|
|
48
104
|
if (!a.scenario?.trim()) {
|
|
49
105
|
return { content: [{ type: "text", text: "scenario is required" }], isError: true };
|
|
50
106
|
}
|
|
51
107
|
try {
|
|
52
|
-
// Step 1:
|
|
53
|
-
const asked = await (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 });
|
|
60
133
|
const simId = created.simulation_id ?? created.id;
|
|
61
134
|
if (!simId)
|
|
62
|
-
throw new Error("No
|
|
63
|
-
// Step
|
|
64
|
-
await (
|
|
65
|
-
|
|
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;
|
|
66
139
|
return {
|
|
67
140
|
content: [{
|
|
68
141
|
type: "text",
|
|
69
142
|
text: [
|
|
70
|
-
`**MiroShark simulation
|
|
143
|
+
`**MiroShark simulation queued** ✓`,
|
|
144
|
+
``,
|
|
71
145
|
`Scenario: ${a.scenario}`,
|
|
146
|
+
`Project: \`${projectId}\``,
|
|
72
147
|
`Simulation ID: \`${simId}\``,
|
|
73
|
-
`
|
|
148
|
+
`Status: preparing agents${prepTaskId ? ` (task: ${prepTaskId})` : ""}`,
|
|
74
149
|
``,
|
|
75
|
-
`Poll
|
|
150
|
+
`Agent preparation runs in the background. Poll progress with:`,
|
|
151
|
+
`\`miroshark_status simulation_id="${simId}"\``,
|
|
76
152
|
].join("\n"),
|
|
77
153
|
}],
|
|
78
154
|
};
|
|
@@ -81,38 +157,106 @@ async function handleMirosharkTool(name, args) {
|
|
|
81
157
|
return { content: [{ type: "text", text: `MiroShark error: ${err.message}` }], isError: true };
|
|
82
158
|
}
|
|
83
159
|
}
|
|
160
|
+
// ── miroshark_status ──────────────────────────────────────────────────────
|
|
84
161
|
if (name === "miroshark_status") {
|
|
85
162
|
if (!a.simulation_id?.trim()) {
|
|
86
163
|
return { content: [{ type: "text", text: "simulation_id is required" }], isError: true };
|
|
87
164
|
}
|
|
165
|
+
const simId = a.simulation_id.trim();
|
|
88
166
|
try {
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
167
|
+
// Check run status first
|
|
168
|
+
const runStatus = await miroJson(`/miroshark/api/simulation/${simId}/run-status`, "GET").catch(() => ({ runner_status: "idle" }));
|
|
169
|
+
const runnerStatus = (runStatus?.runner_status ?? "idle").toLowerCase();
|
|
170
|
+
// If not yet running, check whether agents are prepared by probing /config
|
|
171
|
+
// (config only exists after /prepare completes)
|
|
172
|
+
if (runnerStatus === "idle") {
|
|
173
|
+
const config = await miroJson(`/miroshark/api/simulation/${simId}/config`, "GET").catch(() => null);
|
|
174
|
+
if (!config) {
|
|
175
|
+
// Preparation still in progress — check profiles for real-time progress
|
|
176
|
+
const profiles = await miroJson(`/miroshark/api/simulation/${simId}/profiles/realtime`, "GET").catch(() => null);
|
|
177
|
+
const total = profiles?.total_expected ?? "?";
|
|
178
|
+
const ready = profiles?.profiles_ready ?? 0;
|
|
179
|
+
return {
|
|
180
|
+
content: [{
|
|
181
|
+
type: "text",
|
|
182
|
+
text: [
|
|
183
|
+
`**MiroShark \`${simId}\`** — preparing agents`,
|
|
184
|
+
total !== "?" ? `Profiles: ${ready} / ${total} ready` : `Profiles generating...`,
|
|
185
|
+
``,
|
|
186
|
+
`Poll again in ~10 seconds.`,
|
|
187
|
+
].join("\n"),
|
|
188
|
+
}],
|
|
189
|
+
};
|
|
105
190
|
}
|
|
106
|
-
|
|
107
|
-
|
|
191
|
+
// Config exists → agents prepared → auto-start
|
|
192
|
+
await miroJson("/miroshark/api/simulation/start", "POST", {
|
|
193
|
+
simulation_id: simId,
|
|
194
|
+
platform: "parallel",
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
content: [{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: [
|
|
200
|
+
`**MiroShark \`${simId}\`** — simulation started`,
|
|
201
|
+
``,
|
|
202
|
+
`Agents are now active. Poll again in ~15 seconds for progress.`,
|
|
203
|
+
`\`miroshark_status simulation_id="${simId}"\``,
|
|
204
|
+
].join("\n"),
|
|
205
|
+
}],
|
|
206
|
+
};
|
|
108
207
|
}
|
|
109
|
-
|
|
110
|
-
|
|
208
|
+
if (runnerStatus === "running") {
|
|
209
|
+
const round = runStatus.current_round ?? 0;
|
|
210
|
+
const total = runStatus.total_rounds ?? "?";
|
|
211
|
+
const pct = runStatus.progress_percent?.toFixed(1) ?? "0";
|
|
212
|
+
const twitterActs = runStatus.twitter_actions_count ?? 0;
|
|
213
|
+
const redditActs = runStatus.reddit_actions_count ?? 0;
|
|
214
|
+
return {
|
|
215
|
+
content: [{
|
|
216
|
+
type: "text",
|
|
217
|
+
text: [
|
|
218
|
+
`**MiroShark \`${simId}\`** — running`,
|
|
219
|
+
`Round: ${round} / ${total} (${pct}%)`,
|
|
220
|
+
`Actions: ${twitterActs} Twitter · ${redditActs} Reddit`,
|
|
221
|
+
``,
|
|
222
|
+
`Simulation in progress — poll again in ~15 seconds.`,
|
|
223
|
+
].join("\n"),
|
|
224
|
+
}],
|
|
225
|
+
};
|
|
111
226
|
}
|
|
112
|
-
|
|
113
|
-
|
|
227
|
+
if (runnerStatus === "completed" || runnerStatus === "stopped") {
|
|
228
|
+
// Fetch a sample of agent actions for the summary
|
|
229
|
+
const actionsData = await miroJson(`/miroshark/api/simulation/${simId}/actions?limit=10`, "GET").catch(() => ({ actions: [] }));
|
|
230
|
+
const actions = actionsData?.actions ?? [];
|
|
231
|
+
const lines = [
|
|
232
|
+
`**MiroShark \`${simId}\`** — ${runnerStatus}`,
|
|
233
|
+
``,
|
|
234
|
+
`Rounds completed: ${runStatus.current_round ?? "?"}`,
|
|
235
|
+
`Total actions: ${runStatus.total_actions_count ?? actions.length}`,
|
|
236
|
+
];
|
|
237
|
+
if (actions.length > 0) {
|
|
238
|
+
lines.push("", "**Sample agent activity:**");
|
|
239
|
+
for (const act of actions.slice(0, 8)) {
|
|
240
|
+
const who = act.agent_name ?? act.agent_id ?? "agent";
|
|
241
|
+
const what = act.action_type ?? act.type ?? "action";
|
|
242
|
+
const content = act.content ?? act.text ?? "";
|
|
243
|
+
lines.push(`• **${who}** [${what}]${content ? `: ${String(content).slice(0, 80)}` : ""}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
lines.push("", `Full transcript: \`miroshark_status\` returns results above.`);
|
|
247
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
114
248
|
}
|
|
115
|
-
|
|
249
|
+
// Fallback: unknown state
|
|
250
|
+
return {
|
|
251
|
+
content: [{
|
|
252
|
+
type: "text",
|
|
253
|
+
text: [
|
|
254
|
+
`**MiroShark \`${simId}\`** — status: ${runnerStatus || "unknown"}`,
|
|
255
|
+
``,
|
|
256
|
+
`If agents are still preparing, poll again shortly.`,
|
|
257
|
+
].filter(Boolean).join("\n"),
|
|
258
|
+
}],
|
|
259
|
+
};
|
|
116
260
|
}
|
|
117
261
|
catch (err) {
|
|
118
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.1
|
|
3
|
+
"version": "1.2.1",
|
|
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": {
|