@astramindapp/mcp-server 0.22.0 → 0.24.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/dist/server.js CHANGED
@@ -44,11 +44,44 @@ Object.defineProperty(exports, "__esModule", { value: true });
44
44
  exports.createMindMcpServer = createMindMcpServer;
45
45
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
46
46
  const zod_1 = require("zod");
47
+ const mind_client_js_1 = require("./mind-client.js");
47
48
  const integration_guide_js_1 = require("./integration-guide.js");
49
+ // ─── Shared MCP result helpers ──────────────────────────────
50
+ // Prefer these over inline `catch (err) → "mind_x error: ${err}"` in new
51
+ // tools: `ok` pretty-prints the payload, `err` flags isError so the agent
52
+ // knows to recover instead of hallucinating, and `apiDetail` surfaces the
53
+ // backend's human-readable detail sentence instead of a raw JSON body dump.
54
+ const err = (t) => ({
55
+ content: [{ type: "text", text: t }],
56
+ isError: true,
57
+ });
58
+ const ok = (result) => ({
59
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
60
+ });
61
+ // Backend detail strings beat raw JSON dumps: FastAPI returns
62
+ // {"detail": "..."} (or {"detail": {"message": "..."}}) — surface the
63
+ // human sentence, never the wire body (CLAUDE.md lesson #6).
64
+ const apiDetail = (e) => {
65
+ try {
66
+ const parsed = JSON.parse(e.body);
67
+ const d = parsed?.detail;
68
+ if (typeof d === "string")
69
+ return d;
70
+ if (d && typeof d === "object") {
71
+ const msg = d.message;
72
+ if (typeof msg === "string")
73
+ return msg;
74
+ }
75
+ }
76
+ catch {
77
+ // not JSON — fall through to the raw body/status text
78
+ }
79
+ return e.body || e.statusText;
80
+ };
48
81
  function createMindMcpServer(client) {
49
82
  const server = new mcp_js_1.McpServer({
50
83
  name: "mind",
51
- version: "0.17.0",
84
+ version: "0.24.0",
52
85
  }, {
53
86
  // Returned to every client in the MCP `initialize` response — the first
54
87
  // thing any connecting agent sees about how to integrate with MIND.
@@ -72,16 +105,21 @@ function createMindMcpServer(client) {
72
105
  // ─── mind_query ─────────────────────────────────────────
73
106
  // Semantic search across the user's knowledge graph.
74
107
  // Replaces flat MEMORY.md — returns only relevant memories.
75
- server.tool("mind_query", "Search your MIND knowledge graph. Returns AI-synthesized answer from your stored documents, entries, and feed posts. Use this BEFORE making decisions — MIND is your memory.", {
108
+ server.tool("mind_query", "Search your MIND knowledge graph. Returns RETRIEVED CONTEXT from the graph — documents, entries, entities and relationships — NOT a finished answer. YOU must read that context and write the answer yourself. Does not use MIND's LLM and costs 0 credits. Use this BEFORE making decisions — MIND is your memory. (Pass retrieve_only=false to have MIND synthesize the answer instead, which spends credits.)", {
76
109
  query: zod_1.z.string().describe("What to search for in your knowledge graph"),
77
110
  mode: zod_1.z
78
111
  .enum(["mix", "hybrid", "global", "local", "naive"])
79
112
  .optional()
80
113
  .default("hybrid")
81
114
  .describe("Search mode: hybrid (default, best results — combines semantic + graph traversal), mix (balanced), global (broad graph search), local (focused graph search), naive (simple vector search)"),
82
- }, async ({ query, mode }) => {
115
+ retrieve_only: zod_1.z
116
+ .boolean()
117
+ .optional()
118
+ .default(true)
119
+ .describe("Default true: return raw graph context for YOU to synthesize, 0 credits. Set false to have MIND's own LLM write the answer (spends credits)."),
120
+ }, async ({ query, mode, retrieve_only }) => {
83
121
  try {
84
- const result = await client.query({ query, mode });
122
+ const result = await client.query({ query, mode, retrieve_only });
85
123
  const text = [
86
124
  result.response,
87
125
  "",
@@ -126,7 +164,7 @@ function createMindMcpServer(client) {
126
164
  // ─── mind_remember ──────────────────────────────────────
127
165
  // Store/manage content in the knowledge graph. Auto-categorized by type.
128
166
  // Enhanced: now supports create, delete, search, get, list actions.
129
- server.tool("mind_remember", "Store and manage content in your MIND knowledge graph. Use for facts, decisions, learnings, research, notes — anything worth remembering. Auto-categorized. Always log outcomes here after completing tasks. Supports create (default), delete, search, get, and list.\n\n⚠️ PRIVATE vs PUBLIC: `document` and `entry` are PRIVATE to the user's knowledge graph. `feed_post` is a PUBLIC social-media post that appears on the user's public feed for everyone to see. Default to `entry` for all agent outcomes, logs, decisions, and research. NEVER use `feed_post` unless the user explicitly says \"post\", \"share\", \"tweet\", \"feed\", or \"thought to my feed\" — deploy logs, PR notes, work outcomes, and agent activity belong in `entry`, NOT on the public feed.", {
167
+ server.tool("mind_remember", "Store and manage content in your MIND knowledge graph. Use for facts, decisions, learnings, research, notes — anything worth remembering. Auto-categorized. Always log outcomes here after completing tasks. Supports create (default), delete, search, get, and list.\n\n⚠️ PRIVATE vs PUBLIC: `document` and `entry` are PRIVATE to the user's knowledge graph. `feed_post` is a PUBLIC social-media post that appears on the user's public feed for everyone to see. Default to `entry` for all agent outcomes, logs, decisions, and research. NEVER use `feed_post` unless the user explicitly says \"post\", \"share\", \"tweet\", \"feed\", or \"thought to my feed\" — deploy logs, PR notes, work outcomes, and agent activity belong in `entry`, NOT on the public feed.\n\nTitle rule: lessons are titled 'Lesson - Failure - <behavior>' / 'Lesson - Behavior - <behavior>' / 'Lesson - Win - <behavior>'; never put '/' or parentheses in a title, the card is derived from it.", {
130
168
  action: zod_1.z
131
169
  .enum(["create", "delete", "search", "get", "list"])
132
170
  .optional()
@@ -612,7 +650,7 @@ function createMindMcpServer(client) {
612
650
  // Get structured always-loaded context.
613
651
  // Equivalent to OpenClaw's SOUL.md + USER.md + AGENTS.md bootstrap injection,
614
652
  // but backed by the knowledge graph.
615
- server.tool("mind_context", "Get your persistent context from MIND — identity, preferences, rules, and recent activity. Call this at the start of every session to know who you are, who the user is, and what matters right now.", {
653
+ server.tool("mind_context", "Get your persistent context from MIND — identity, preferences, rules, and recent activity. Returns RETRIEVED CONTEXT (not a finished briefing). YOU synthesize who you are and what matters. Does not use MIND's LLM and costs 0 credits. Call this at the start of every session.", {
616
654
  sections: zod_1.z
617
655
  .array(zod_1.z.enum(["soul", "user", "rules", "priorities", "recent"]))
618
656
  .optional()
@@ -627,7 +665,7 @@ function createMindMcpServer(client) {
627
665
  priorities: "current priorities, active goals, top tasks, what to focus on right now",
628
666
  recent: "most recent activity, what happened today, latest outcomes and decisions",
629
667
  };
630
- const responses = await Promise.all(sections.map((section) => client.query({ query: sectionQueries[section], mode: "mix" }).then((r) => ({
668
+ const responses = await Promise.all(sections.map((section) => client.query({ query: sectionQueries[section], mode: "mix", retrieve_only: true }).then((r) => ({
631
669
  section,
632
670
  text: r.response,
633
671
  }))));
@@ -1392,6 +1430,155 @@ function createMindMcpServer(client) {
1392
1430
  };
1393
1431
  }
1394
1432
  });
1433
+ // ─── mind_checklists ────────────────────────────────────
1434
+ // The granular level beneath a Task. Shape: Template → Sections[phase] →
1435
+ // Items. Templates (yours + flagship) instantiate into runs attached to a
1436
+ // task or life item; items are toggled and runs completed (which mirrors the
1437
+ // finished checklist into the knowledge graph).
1438
+ server.tool("mind_checklists", "Manage checklists in MIND — the granular level beneath a Task in the Focus→Project→Outcome→Task→Checklist hierarchy. A checklist attaches to a task (parent_type=task) or a Life item (parent_type=life_item) and groups checkable steps into phase-bucketed sections (pre/run/post). Use list_templates/get_template/create_template for reusable blueprints (including global flagship templates), then create a run from a template_id or inline sections_json, toggle_item to check steps, and complete to finish + mirror it into the knowledge graph.", {
1439
+ action: zod_1.z
1440
+ .enum([
1441
+ "list_templates", "get_template", "create_template",
1442
+ "list", "create", "get", "toggle_item", "complete", "delete",
1443
+ ])
1444
+ .describe("What to do: list_templates/get_template/create_template (blueprints); list/create/get/toggle_item/complete/delete (runs)"),
1445
+ template_id: zod_1.z.string().optional().describe("Template ID — required for get_template; optional on create (instantiate from a template)"),
1446
+ checklist_id: zod_1.z.string().optional().describe("Checklist run ID — required for get/toggle_item/complete/delete"),
1447
+ item_id: zod_1.z.string().optional().describe("Checklist item ID — required for toggle_item"),
1448
+ title: zod_1.z.string().optional().describe("Title (required for create_template; optional for create run)"),
1449
+ description: zod_1.z.string().optional().describe("Template description (create_template)"),
1450
+ parent_type: zod_1.z.string().optional().describe("Attach a run to: task or life_item (required for create + list filter)"),
1451
+ parent_id: zod_1.z.string().optional().describe("ID of the parent task / life item (required for create)"),
1452
+ parent_label: zod_1.z.string().optional().describe("Optional human label for the parent"),
1453
+ sections_json: zod_1.z.string().optional().describe('Inline sections as JSON, e.g. [{"title":"Setup","phase":"pre","items":[{"label":"do x"}]}] — for create_template or an inline create run'),
1454
+ }, async ({ action, template_id, checklist_id, item_id, title, description, parent_type, parent_id, parent_label, sections_json }) => {
1455
+ try {
1456
+ const parseSections = () => {
1457
+ if (!sections_json)
1458
+ return undefined;
1459
+ const parsed = JSON.parse(sections_json);
1460
+ if (!Array.isArray(parsed))
1461
+ throw new Error("sections_json must be a JSON array of sections");
1462
+ return parsed;
1463
+ };
1464
+ const fmtTemplate = (t) => {
1465
+ const secCount = (t.sections ?? []).length;
1466
+ const itemCount = (t.sections ?? []).reduce((n, s) => n + (s.items?.length ?? 0), 0);
1467
+ return `• ${t.title}${t.origin === "flagship" ? " ⭐ flagship" : ""} — ${secCount} section(s), ${itemCount} item(s) — id: ${t.template_id}`;
1468
+ };
1469
+ const fmtSections = (sections) => (sections ?? [])
1470
+ .map((s) => {
1471
+ const head = ` [${s.phase}] ${s.title}`;
1472
+ const items = (s.items ?? [])
1473
+ .map((it) => ` ${it.checked ? "[x]" : "[ ]"} ${it.label} — id: ${it.item_id}`)
1474
+ .join("\n");
1475
+ return items ? `${head}\n${items}` : head;
1476
+ })
1477
+ .join("\n");
1478
+ const fmtChecklist = (c, withItems = false) => {
1479
+ const p = c.progress ?? { done: 0, total: 0, pct: 0 };
1480
+ const head = `• ${c.title} (${p.done}/${p.total}, ${p.pct}%)` +
1481
+ `${c.completed_at ? " ✅ complete" : ""}` +
1482
+ `${c.parent_label ? `, on ${c.parent_label}` : ""} — id: ${c.checklist_id}`;
1483
+ return withItems ? `${head}\n${fmtSections(c.sections ?? [])}` : head;
1484
+ };
1485
+ switch (action) {
1486
+ case "list_templates": {
1487
+ const result = await client.listChecklistTemplates();
1488
+ const templates = result.templates ?? [];
1489
+ if (templates.length === 0) {
1490
+ return { content: [{ type: "text", text: "No checklist templates found." }] };
1491
+ }
1492
+ return {
1493
+ content: [{
1494
+ type: "text",
1495
+ text: `${result.total} template(s):\n` + templates.map(fmtTemplate).join("\n"),
1496
+ }],
1497
+ };
1498
+ }
1499
+ case "get_template": {
1500
+ if (!template_id) {
1501
+ return { content: [{ type: "text", text: "Error: template_id is required for get_template" }], isError: true };
1502
+ }
1503
+ const t = await client.getChecklistTemplate(template_id);
1504
+ return {
1505
+ content: [{
1506
+ type: "text",
1507
+ text: `${fmtTemplate(t)}${t.description ? `\n${t.description}` : ""}\n${fmtSections(t.sections ?? [])}`,
1508
+ }],
1509
+ };
1510
+ }
1511
+ case "create_template": {
1512
+ if (!title) {
1513
+ return { content: [{ type: "text", text: "Error: title is required to create a template" }], isError: true };
1514
+ }
1515
+ const t = await client.createChecklistTemplate({
1516
+ title, description, sections: parseSections(),
1517
+ });
1518
+ return { content: [{ type: "text", text: `✅ Created template: ${fmtTemplate(t)}` }] };
1519
+ }
1520
+ case "list": {
1521
+ const result = await client.listChecklists(parent_type, parent_id);
1522
+ const checklists = result.checklists ?? [];
1523
+ if (checklists.length === 0) {
1524
+ return { content: [{ type: "text", text: "No checklists found." }] };
1525
+ }
1526
+ return {
1527
+ content: [{
1528
+ type: "text",
1529
+ text: `${result.total} checklist(s):\n` + checklists.map((c) => fmtChecklist(c)).join("\n"),
1530
+ }],
1531
+ };
1532
+ }
1533
+ case "create": {
1534
+ if (!parent_type || !parent_id) {
1535
+ return { content: [{ type: "text", text: "Error: parent_type and parent_id are required to create a checklist run" }], isError: true };
1536
+ }
1537
+ const c = await client.createChecklist({
1538
+ parent_type, parent_id, parent_label, title,
1539
+ template_id, sections: parseSections(),
1540
+ });
1541
+ return { content: [{ type: "text", text: `✅ Created checklist:\n${fmtChecklist(c, true)}` }] };
1542
+ }
1543
+ case "get": {
1544
+ if (!checklist_id) {
1545
+ return { content: [{ type: "text", text: "Error: checklist_id is required for get" }], isError: true };
1546
+ }
1547
+ const c = await client.getChecklist(checklist_id);
1548
+ return { content: [{ type: "text", text: fmtChecklist(c, true) }] };
1549
+ }
1550
+ case "toggle_item": {
1551
+ if (!checklist_id || !item_id) {
1552
+ return { content: [{ type: "text", text: "Error: checklist_id and item_id are required for toggle_item" }], isError: true };
1553
+ }
1554
+ const c = await client.toggleChecklistItem(checklist_id, item_id);
1555
+ return { content: [{ type: "text", text: `✅ Toggled item:\n${fmtChecklist(c, true)}` }] };
1556
+ }
1557
+ case "complete": {
1558
+ if (!checklist_id) {
1559
+ return { content: [{ type: "text", text: "Error: checklist_id is required for complete" }], isError: true };
1560
+ }
1561
+ const c = await client.completeChecklist(checklist_id);
1562
+ return { content: [{ type: "text", text: `✅ Completed checklist "${c.title}" — mirrored to the knowledge graph.` }] };
1563
+ }
1564
+ case "delete": {
1565
+ if (!checklist_id) {
1566
+ return { content: [{ type: "text", text: "Error: checklist_id is required for delete" }], isError: true };
1567
+ }
1568
+ await client.deleteChecklist(checklist_id);
1569
+ return { content: [{ type: "text", text: `🗑️ Deleted checklist ${checklist_id}` }] };
1570
+ }
1571
+ default:
1572
+ return { content: [{ type: "text", text: `Unknown action: ${action}` }], isError: true };
1573
+ }
1574
+ }
1575
+ catch (err) {
1576
+ return {
1577
+ content: [{ type: "text", text: `mind_checklists error: ${err instanceof Error ? err.message : String(err)}` }],
1578
+ isError: true,
1579
+ };
1580
+ }
1581
+ });
1395
1582
  // ─── mind_crm ───────────────────────────────────────────
1396
1583
  // Read/write contacts, interactions, and activities.
1397
1584
  // Enhanced: delete, get, log_activity, list_activities.
@@ -1662,7 +1849,7 @@ function createMindMcpServer(client) {
1662
1849
  preferred_llm_model: zod_1.z
1663
1850
  .union([zod_1.z.string(), zod_1.z.null()])
1664
1851
  .optional()
1665
- .describe("LLM model id (e.g. 'openai/gpt-4o', 'anthropic/claude-sonnet-4.6'). Used by the public chat at /m/{username}. Pass null to clear and fall back to platform default. Use mind_admin with action=list_featured_minds then get_featured_mind_full to see available_models."),
1852
+ .describe("LLM model id (e.g. 'deepseek/deepseek-v4-flash', 'anthropic/claude-sonnet-4.6'). Used by the public chat at /m/{username}. Pass null to clear and fall back to platform default. Use mind_admin with action=list_featured_minds then get_featured_mind_full to see available_models."),
1666
1853
  public_mind_enabled: zod_1.z.boolean().optional().describe("Whether anonymous visitors can chat with this MIND at /m/{username}"),
1667
1854
  public_mind_prompt: zod_1.z
1668
1855
  .string()
@@ -4505,6 +4692,321 @@ function createMindMcpServer(client) {
4505
4692
  };
4506
4693
  }
4507
4694
  });
4695
+ // ─── mind_trader ────────────────────────────────────────
4696
+ // TraderMIND: any agent can run the trader for you. Read the engine
4697
+ // (live feed, MIND Vision forecasts — latest cone + resolved history +
4698
+ // per-bar scores, candles, cycle summaries, calibration honesty curve,
4699
+ // engine health, the trader's own narrated journal) and act on it as YOU —
4700
+ // save/star/like objects (and undo), leave notes and structured feedback
4701
+ // the engine learns from, read back your notes/interactions/public counts,
4702
+ // keep a full-CRUD trading journal, and pull personalized insights.
4703
+ // 1:1 endpoint map (same MIND base URL, MIND API key auth):
4704
+ // feed → GET /api/mindtrades/feed · forecast_latest → GET /api/trader/forecasts/latest
4705
+ // forecasts → GET /api/trader/forecasts · bar_scores → GET /api/trader/forecasts/{id}/bar-scores
4706
+ // candles → GET /api/trader/candles · cycles → GET /api/trader/cycles
4707
+ // health → GET /api/trader/health · calibration → GET /api/trader/calibration
4708
+ // ai_journal → GET /api/trader/ai-journal · save/star/like → POST /api/trader/interact
4709
+ // unsave → DELETE /api/trader/interact · interactions → GET /api/trader/interactions
4710
+ // counts → GET /api/trader/counts · note/feedback → POST /api/trader/note
4711
+ // notes → GET /api/trader/notes · journal_* → GET/POST/PUT/DELETE /api/trader/journal[/{id}]
4712
+ // saved → GET /api/trader/saved · insights → GET /api/trader/insights
4713
+ server.tool("mind_trader", "Operate TraderMIND — the agentic trading engine. Use whenever the user asks about markets, the trader, a MIND Vision forecast, their trading journal, or wants to react to a trade idea. " +
4714
+ "Read the engine: feed (live trade-idea feed) · forecast_latest (latest MIND Vision cone for a symbol) · forecasts (recent forecast history incl. resolution — every hit and miss) · bar_scores (per-candle predicted-vs-actual for one resolved forecast_id) · candles (OHLCV) · cycles (engine cycle summaries: regime, ideas, lessons) · health (is the engine up) · calibration (published honesty curve) · ai_journal (the trader's own narrated 'what I saw / did / why'). " +
4715
+ "Act as the user: save | star | like an object (object_type forecast|idea|journal_ai + object_id) · unsave (undo one of those — pass undo_action) · note (attach a note) · feedback (agree/disagree/context — reaches the engine's brain and shapes future hypotheses) · interactions (what you already saved/starred on a batch of object_ids — check before writing) · notes (read back your notes) · saved (your saved/starred items, filterable via filter_action). Writes are rate-limited (300 save/star/like per hour, 60 notes/feedback per hour) — batch-check with action=interactions before writing. " +
4716
+ "Your journal: journal_list · journal_create · journal_get · journal_update · journal_delete (entry_id). insights = personalized insights from your activity + the trader brain (cached 15 min). " +
4717
+ "Auth: all engine reads (feed, forecast_latest, forecasts, bar_scores, candles, cycles, health, calibration, ai_journal) plus counts are public — they work even with a missing or invalid key. Only the act-as-you actions (save/star/like/unsave, note/feedback, notes, interactions, saved, journal_*, insights) authenticate as the key's owner. Saves, notes, feedback, and journal entries also land in the owner's MIND knowledge graph.", {
4718
+ action: zod_1.z
4719
+ .enum([
4720
+ "feed",
4721
+ "forecast_latest",
4722
+ "forecasts",
4723
+ "bar_scores",
4724
+ "candles",
4725
+ "cycles",
4726
+ "health",
4727
+ "calibration",
4728
+ "ai_journal",
4729
+ "save",
4730
+ "star",
4731
+ "like",
4732
+ "unsave",
4733
+ "note",
4734
+ "feedback",
4735
+ "notes",
4736
+ "interactions",
4737
+ "counts",
4738
+ "journal_list",
4739
+ "journal_create",
4740
+ "journal_get",
4741
+ "journal_update",
4742
+ "journal_delete",
4743
+ "saved",
4744
+ "insights",
4745
+ ])
4746
+ .describe("The operation to run — see the tool description for the grouped taxonomy and per-action requirements."),
4747
+ symbol: zod_1.z
4748
+ .string()
4749
+ .regex(/^[A-Za-z0-9.\-]{1,12}$/, "symbol must be 1-12 chars of A-Z, 0-9, '.' or '-'")
4750
+ .optional()
4751
+ .describe("Instrument symbol, 1–12 chars of A-Z, 0-9, '.' or '-' (e.g. ES, NQ, SPY, BTC-USD; case-insensitive — normalized to uppercase). Required for forecast_latest and candles; optional filter on forecasts/ai_journal/saved; recommended on save/star/like/note/feedback."),
4752
+ object_type: zod_1.z
4753
+ .enum(["forecast", "idea", "journal_ai"])
4754
+ .optional()
4755
+ .describe("Type of the actable object — required for save/star/like/unsave/note/feedback."),
4756
+ object_id: zod_1.z
4757
+ .string()
4758
+ .max(200)
4759
+ .optional()
4760
+ .describe("Id of the actable object (≤200 chars) — required for save/star/like/unsave/note/feedback; optional filter for notes."),
4761
+ object_ids: zod_1.z
4762
+ .array(zod_1.z.string().max(200))
4763
+ .min(1)
4764
+ .max(200)
4765
+ .optional()
4766
+ .describe("Batch of object ids (1–200, each ≤200 chars) — required for interactions and counts."),
4767
+ undo_action: zod_1.z
4768
+ .enum(["save", "star", "like"])
4769
+ .optional()
4770
+ .describe("Which interaction to undo — required for unsave."),
4771
+ forecast_id: zod_1.z
4772
+ .number()
4773
+ .int()
4774
+ .min(1)
4775
+ .optional()
4776
+ .describe("Numeric forecast id — required for bar_scores. Get ids from action=forecasts."),
4777
+ text: zod_1.z
4778
+ .string()
4779
+ .max(8000)
4780
+ .optional()
4781
+ .describe("Note or feedback text (≤8000 chars) — required for note/feedback."),
4782
+ tag: zod_1.z
4783
+ .enum(["agree", "disagree", "context"])
4784
+ .optional()
4785
+ .describe("Structured tag stored with the note/feedback (agree | disagree | context). On feedback it reaches the engine's Hypothesizer and shapes future forecasts."),
4786
+ note_kind: zod_1.z
4787
+ .enum(["note", "feedback"])
4788
+ .optional()
4789
+ .describe("action=notes: filter your notes by kind."),
4790
+ filter_action: zod_1.z
4791
+ .enum(["save", "star", "like"])
4792
+ .optional()
4793
+ .describe("action=saved: return only items with this interaction (e.g. only what you starred). Default: saves + stars."),
4794
+ title: zod_1.z
4795
+ .string()
4796
+ .max(300)
4797
+ .optional()
4798
+ .describe("Journal entry title (≤300 chars) — required for journal_create; optional on journal_update."),
4799
+ body: zod_1.z
4800
+ .string()
4801
+ .max(50000)
4802
+ .optional()
4803
+ .describe("Journal entry body (≤50000 chars) — journal_create/journal_update."),
4804
+ symbols: zod_1.z
4805
+ .array(zod_1.z
4806
+ .string()
4807
+ .regex(/^[A-Za-z0-9.\-]{1,12}$/, "each symbol must be 1-12 chars of A-Z, 0-9, '.' or '-'"))
4808
+ .max(20)
4809
+ .optional()
4810
+ .describe("Symbols linked to a journal entry, ≤20 of ≤12 chars each (journal_create/journal_update — update REPLACES the list)."),
4811
+ linked_ids: zod_1.z
4812
+ .array(zod_1.z.string().max(200))
4813
+ .max(50)
4814
+ .optional()
4815
+ .describe("Forecast/idea ids linked to a journal entry, ≤50 (journal_create/journal_update — update REPLACES the list)."),
4816
+ tags: zod_1.z
4817
+ .array(zod_1.z.string().max(50))
4818
+ .max(20)
4819
+ .optional()
4820
+ .describe("Tags for a journal entry, ≤20 (journal_create/journal_update — update REPLACES the list)."),
4821
+ entry_id: zod_1.z
4822
+ .string()
4823
+ .max(200)
4824
+ .optional()
4825
+ .describe("Journal entry id (≤200 chars) — required for journal_get/journal_update/journal_delete."),
4826
+ tf: zod_1.z
4827
+ .enum(["1m", "5m", "15m", "1h", "4h", "1d"])
4828
+ .optional()
4829
+ .describe("Candle timeframe for candles (default 5m)."),
4830
+ limit: zod_1.z
4831
+ .number()
4832
+ .int()
4833
+ .min(1)
4834
+ .max(2000)
4835
+ .optional()
4836
+ .describe("Max items. Per-action bounds/defaults: candles 1–2000 default 300 · forecasts ≤500 default 50 · feed/ai_journal/journal_list ≤200 default 50 · cycles ≤200 default 20 · saved/notes ≤500 default 100. Values above an action's cap are clamped to the cap. Use 'page' to continue past 'limit' on notes/saved/journal_list."),
4837
+ page: zod_1.z
4838
+ .number()
4839
+ .int()
4840
+ .min(1)
4841
+ .optional()
4842
+ .describe("notes / saved / journal_list: page number (default 1); responses include 'total' and 'page' so you can walk pages."),
4843
+ days: zod_1.z
4844
+ .number()
4845
+ .int()
4846
+ .min(1)
4847
+ .max(365)
4848
+ .optional()
4849
+ .describe("History window in days for calibration (1–365, default 90)."),
4850
+ kind: zod_1.z
4851
+ .enum(["execute", "no_trade", "error", "all"])
4852
+ .optional()
4853
+ .describe("Feed filter for action=feed."),
4854
+ refresh: zod_1.z
4855
+ .boolean()
4856
+ .optional()
4857
+ .describe("action=insights: bypass the 15-minute cache and recompose. A 60-second floor still applies — refresh=true within 60s of the last compose returns the cached result."),
4858
+ }, async ({ action, symbol, object_type, object_id, object_ids, undo_action, forecast_id, text, tag, note_kind, filter_action, title, body, symbols, linked_ids, tags, entry_id, tf, limit, page, days, kind, refresh, }) => {
4859
+ // ok / err / apiDetail are the module-scope shared MCP result helpers.
4860
+ // Clamp limit to each action's backend cap instead of letting the API
4861
+ // 422 — the describe documents these bounds; the clamp enforces them.
4862
+ const LIMIT_CAPS = {
4863
+ feed: 200,
4864
+ forecasts: 500,
4865
+ candles: 2000,
4866
+ cycles: 200,
4867
+ ai_journal: 200,
4868
+ notes: 500,
4869
+ journal_list: 200,
4870
+ saved: 500,
4871
+ };
4872
+ const lim = limit !== undefined ? Math.min(limit, LIMIT_CAPS[action] ?? limit) : undefined;
4873
+ try {
4874
+ switch (action) {
4875
+ case "feed":
4876
+ return ok(await client.traderFeed({ limit: lim, kind }));
4877
+ case "forecast_latest":
4878
+ if (!symbol)
4879
+ return err("Error: 'symbol' is required for forecast_latest.");
4880
+ return ok(await client.traderForecastLatest(symbol));
4881
+ case "forecasts":
4882
+ return ok(await client.traderForecasts(symbol, lim));
4883
+ case "bar_scores":
4884
+ if (forecast_id === undefined)
4885
+ return err("Error: 'forecast_id' is required for bar_scores. Get ids from action=forecasts.");
4886
+ return ok(await client.traderBarScores(forecast_id));
4887
+ case "candles":
4888
+ if (!symbol)
4889
+ return err("Error: 'symbol' is required for candles.");
4890
+ return ok(await client.traderCandles(symbol, tf, lim));
4891
+ case "cycles":
4892
+ return ok(await client.traderCycles(lim));
4893
+ case "health":
4894
+ return ok(await client.traderHealth());
4895
+ case "calibration":
4896
+ return ok(await client.traderCalibration(days));
4897
+ case "ai_journal":
4898
+ return ok(await client.traderAiJournal(symbol, lim));
4899
+ case "save":
4900
+ case "star":
4901
+ case "like": {
4902
+ if (!object_type || !object_id)
4903
+ return err(`Error: 'object_type' and 'object_id' are required for ${action}.`);
4904
+ const r = await client.traderInteract({
4905
+ object_type,
4906
+ object_id,
4907
+ action,
4908
+ symbol,
4909
+ });
4910
+ return ok(r);
4911
+ }
4912
+ case "unsave": {
4913
+ if (!object_type || !object_id)
4914
+ return err("Error: 'object_type' and 'object_id' are required for unsave.");
4915
+ if (!undo_action)
4916
+ return err("Error: 'undo_action' (save | star | like) is required for unsave.");
4917
+ const r = (await client.traderUninteract({
4918
+ object_type,
4919
+ object_id,
4920
+ action: undo_action,
4921
+ }));
4922
+ if (r && typeof r === "object" && r.deleted === false)
4923
+ return ok({
4924
+ ...r,
4925
+ hint: `Nothing was deleted — you have no existing ${undo_action} on that object. Call action=interactions with its object_id to see what you actually have.`,
4926
+ });
4927
+ return ok(r);
4928
+ }
4929
+ case "note":
4930
+ case "feedback": {
4931
+ if (!object_type || !object_id)
4932
+ return err(`Error: 'object_type' and 'object_id' are required for ${action}.`);
4933
+ if (!text)
4934
+ return err(`Error: 'text' is required for ${action}.`);
4935
+ const r = await client.traderNote({
4936
+ object_type,
4937
+ object_id,
4938
+ kind: action,
4939
+ text,
4940
+ tag,
4941
+ symbol,
4942
+ });
4943
+ return ok(r);
4944
+ }
4945
+ case "notes":
4946
+ return ok(await client.traderNotes({ object_id, kind: note_kind, limit: lim, page }));
4947
+ case "interactions":
4948
+ if (!object_ids?.length)
4949
+ return err("Error: 'object_ids' (1–200 ids) is required for interactions.");
4950
+ return ok(await client.traderInteractions(object_ids));
4951
+ case "counts":
4952
+ if (!object_ids?.length)
4953
+ return err("Error: 'object_ids' (1–200 ids) is required for counts.");
4954
+ return ok(await client.traderCounts(object_ids));
4955
+ case "journal_list":
4956
+ return ok(await client.traderJournalList(lim, page));
4957
+ case "journal_create":
4958
+ if (!title)
4959
+ return err("Error: 'title' is required for journal_create.");
4960
+ return ok(await client.traderJournalCreate({ title, body, symbols, linked_ids, tags }));
4961
+ case "journal_get":
4962
+ if (!entry_id)
4963
+ return err("Error: 'entry_id' is required for journal_get.");
4964
+ return ok(await client.traderJournalGet(entry_id));
4965
+ case "journal_update": {
4966
+ if (!entry_id)
4967
+ return err("Error: 'entry_id' is required for journal_update.");
4968
+ if (title === undefined &&
4969
+ body === undefined &&
4970
+ symbols === undefined &&
4971
+ linked_ids === undefined &&
4972
+ tags === undefined)
4973
+ return err("Error: journal_update needs at least one of 'title', 'body', 'symbols', 'linked_ids', 'tags'.");
4974
+ return ok(await client.traderJournalUpdate(entry_id, { title, body, symbols, linked_ids, tags }));
4975
+ }
4976
+ case "journal_delete":
4977
+ if (!entry_id)
4978
+ return err("Error: 'entry_id' is required for journal_delete.");
4979
+ return ok(await client.traderJournalDelete(entry_id));
4980
+ case "saved":
4981
+ return ok(await client.traderSaved({ action: filter_action, symbol, object_type, limit: lim, page }));
4982
+ case "insights":
4983
+ return ok(await client.traderInsights(refresh));
4984
+ default: {
4985
+ const _exhaustive = action;
4986
+ return err(`Unknown action: ${_exhaustive}`);
4987
+ }
4988
+ }
4989
+ }
4990
+ catch (e) {
4991
+ if (e instanceof mind_client_js_1.MindApiError) {
4992
+ if (e.status === 404 && action === "forecast_latest")
4993
+ return err(`No forecast yet for ${symbol?.trim().toUpperCase() || "that symbol"} — the engine may not cover it. Call action=feed or action=cycles to see the live universe of symbols.`);
4994
+ if (e.status === 404 &&
4995
+ (action === "journal_get" || action === "journal_update" || action === "journal_delete"))
4996
+ return err(`No journal entry with entry_id '${entry_id ?? ""}' — it may have been deleted. Call action=journal_list to get valid entry ids.`);
4997
+ if (e.status === 404 && action === "bar_scores")
4998
+ return err(`No bar scores for forecast_id ${forecast_id ?? ""} — the id is wrong or the forecast hasn't resolved yet. Call action=forecasts (resolution status included) to find resolved ids.`);
4999
+ if (e.status === 401 || e.status === 403)
5000
+ return err("mind_trader auth error: the MIND API key was rejected (missing/expired or lacks trader access). Re-run setup with a fresh key from m-i-n-d.ai → Settings → Developer → API Keys.");
5001
+ if (e.status === 429)
5002
+ return err(`mind_trader rate limit (HTTP 429): ${apiDetail(e)} Write ceilings: 300 save/star/like per hour, 60 notes/feedback per hour. counts is IP-rate-limited per minute (default 60/min); other reads are unlimited. Stop writing and retry after the window resets.`);
5003
+ if (e.status === 502 || e.status === 503)
5004
+ return err("mind_trader: the trading engine is unreachable or not configured right now. Call action=health to check engine status, and retry shortly.");
5005
+ return err(`mind_trader ${action} failed (HTTP ${e.status}): ${apiDetail(e)}`);
5006
+ }
5007
+ return err(`mind_trader error: ${e instanceof Error ? e.message : String(e)}`);
5008
+ }
5009
+ });
4508
5010
  return server;
4509
5011
  }
4510
5012
  //# sourceMappingURL=server.js.map