@astramindapp/mcp-server 0.29.2 → 0.30.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
@@ -37,8 +37,10 @@
37
37
  * mind_forms (Quill forms), mind_library (bookshelf)
38
38
  */
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.renderContextV1 = exports.defaultQueryMode = exports.contextV1Enabled = exports.OWNER_REPLY_BANNER = void 0;
40
41
  exports.createMindMcpServer = createMindMcpServer;
41
42
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
43
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
42
44
  const zod_1 = require("zod");
43
45
  const mind_client_js_1 = require("./mind-client.js");
44
46
  const integration_guide_js_1 = require("./integration-guide.js");
@@ -54,6 +56,41 @@ const err = (t) => ({
54
56
  const ok = (result) => ({
55
57
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
56
58
  });
59
+ /**
60
+ * mind_sessions open / append / reply / inbox: text that cannot be skimmed past.
61
+ * 1. Owner replies (non-empty pending_replies) come FIRST under OWNER_REPLY_BANNER. The call that returns
62
+ * them also marks them delivered, so a routine that only logs its turn (Meter's hourly patrol did
63
+ * exactly that on 2026-10-30) would otherwise read the reply as patrol input and never act on it.
64
+ * 2. A Grok session with no MIND wake webhook (`wake_setup_required: true` + `wake_setup`, from
65
+ * backend/services/agent_wake_targets.py) gets the ACTION REQUIRED line next.
66
+ * 3. The full JSON body follows, unchanged.
67
+ * The wording is shared with the hosted /mcp endpoint (backend/routes/mcp_server_routes.py).
68
+ */
69
+ exports.OWNER_REPLY_BANNER = "OWNER REPLY FROM BOTSBYMIND - this is a direct instruction from your owner, not part of your routine; act on it and answer it in this session before anything else:";
70
+ const ownerReplyBlock = (replies) => {
71
+ const lines = [exports.OWNER_REPLY_BANNER];
72
+ replies.forEach((raw, i) => {
73
+ const r = (raw && typeof raw === "object" ? raw : { content: raw });
74
+ const by = r.meta && typeof r.meta.replied_by === "string" ? r.meta.replied_by.trim() : "";
75
+ const content = String(r.content ?? "").trim() || "(empty message)";
76
+ lines.push(`[${i + 1}] ${by ? `(shared Editor ${by}) ` : ""}${content}`);
77
+ });
78
+ return lines.join("\n");
79
+ };
80
+ const okSession = (result) => {
81
+ const r = ok(result);
82
+ const body = (result && typeof result === "object" ? result : {});
83
+ const parts = [];
84
+ if (Array.isArray(body.pending_replies) && body.pending_replies.length > 0) {
85
+ parts.push(ownerReplyBlock(body.pending_replies));
86
+ }
87
+ if (body.wake_setup_required === true && typeof body.wake_setup === "string" && body.wake_setup) {
88
+ parts.push(`ACTION REQUIRED: you have no MIND wake webhook. ${body.wake_setup}`);
89
+ }
90
+ if (parts.length)
91
+ r.content[0].text = [...parts, r.content[0].text].join("\n\n");
92
+ return r;
93
+ };
57
94
  // Required-field guard for the generic action-dispatch tools (mind_mindmap,
58
95
  // mind_moneymind, mind_budget, mind_sheets, mind_email, mind_checklists,
59
96
  // mind_sign, mind_invoices, mind_books, mind_timer, mind_forms, mind_library).
@@ -83,10 +120,43 @@ const apiDetail = (e) => {
83
120
  }
84
121
  return e.body || e.statusText;
85
122
  };
123
+ // mind_context via POST /developer/v1/context: dated, sourced, budgeted, and
124
+ // steerable with for_task. Dark by default (MIND_CONTEXT_V1=1 opts in) until
125
+ // the context engine eval shows it beats the legacy five-query path.
126
+ const contextV1Enabled = () => ["1", "true", "yes", "on"].includes((process.env.MIND_CONTEXT_V1 ?? "").trim().toLowerCase());
127
+ exports.contextV1Enabled = contextV1Enabled;
128
+ // Mode for a mind_query call that names none. hybrid reaches text chunks
129
+ // only through extracted entities, so documents still waiting in an
130
+ // extraction backlog are invisible to it; mix adds chunk vectors. Measured
131
+ // 2026-09-29 on 16 claim questions: hybrid 14/16, mix 15/16, similar latency.
132
+ // Dark by default (MIND_QUERY_DEFAULT_MIX=1 opts in), matching the hosted
133
+ // server's MCP_QUERY_DEFAULT_MIX flag.
134
+ const defaultQueryMode = () => ["1", "true", "yes", "on"].includes((process.env.MIND_QUERY_DEFAULT_MIX ?? "").trim().toLowerCase())
135
+ ? "mix"
136
+ : "hybrid";
137
+ exports.defaultQueryMode = defaultQueryMode;
138
+ const renderContextV1 = (data) => {
139
+ const blocks = (data.sections ?? []).map((sec) => {
140
+ let head = `## ${sec.section.toUpperCase()} (${sec.status}`;
141
+ if (sec.omitted_count)
142
+ head += `, ${sec.omitted_count} omitted for budget`;
143
+ const lines = [`${head})`];
144
+ if (sec.error)
145
+ lines.push(`error: ${sec.error}`);
146
+ for (const item of sec.items ?? []) {
147
+ const meta = [item.date, item.source].filter(Boolean).join(", ");
148
+ lines.push(`- ${item.text}${meta ? ` [${meta}]` : ""}`);
149
+ }
150
+ return lines.join("\n");
151
+ });
152
+ blocks.push(`(~${data.estimated_tokens} of ${data.max_tokens} tokens; ${data.total_omitted ?? 0} items omitted)`);
153
+ return blocks.join("\n\n");
154
+ };
155
+ exports.renderContextV1 = renderContextV1;
86
156
  function createMindMcpServer(client) {
87
157
  const server = new mcp_js_1.McpServer({
88
158
  name: "mind",
89
- version: "0.29.2",
159
+ version: "0.30.0",
90
160
  }, {
91
161
  // Returned to every client in the MCP `initialize` response — the first
92
162
  // thing any connecting agent sees about how to integrate with MIND.
@@ -96,7 +166,7 @@ function createMindMcpServer(client) {
96
166
  // The full integration playbook, readable on demand by any MCP client.
97
167
  server.registerResource("integration-guide", "mind://integration-guide", {
98
168
  title: "MIND Integration Guide",
99
- description: "Complete playbook for integrating any AI agent with MIND — the three integration paths (MCP server, OpenClaw plugin, REST API), authentication, the session protocol, all 45 tools, REST endpoint mapping, and common recipes.",
169
+ description: "Complete playbook for integrating any AI agent with MIND — the three integration paths (MCP server, Daebot, REST API), authentication, the session protocol, all 45 tools, REST endpoint mapping, and common recipes.",
100
170
  mimeType: "text/markdown",
101
171
  }, async (uri) => ({
102
172
  contents: [
@@ -181,6 +251,133 @@ function createMindMcpServer(client) {
181
251
  },
182
252
  ],
183
253
  }));
254
+ // ─── resume-agent-session prompt ──────────────────────────
255
+ // "Any AI that you tell to 'run my Meter session' taps into the Meter
256
+ // MIND session and picks up where it left off with the right prompting"
257
+ // (contract, 2026-10-06). Unlike sync-agent-session above (a static
258
+ // template), this one makes the LIVE mind_sessions action=resume call
259
+ // itself and hands back its resume_prompt verbatim — the model adopts it
260
+ // as-is rather than being taught the protocol from scratch.
261
+ server.registerPrompt("resume-agent-session", {
262
+ title: "Resume an agent's session",
263
+ description: "Resolves <agent>'s most recent session, agent file, and attached context, opens a continuation session, and returns a ready-to-adopt resume_prompt. Equivalent to calling mind_sessions action=resume yourself.",
264
+ argsSchema: {
265
+ agent: zod_1.z.string().describe('The agent to resume, e.g. "Meter". Required.'),
266
+ },
267
+ }, async ({ agent }) => {
268
+ const trimmed = (agent ?? "").trim();
269
+ if (!trimmed) {
270
+ return {
271
+ messages: [
272
+ {
273
+ role: "user",
274
+ content: { type: "text", text: "Error: 'agent' is required to resume a session." },
275
+ },
276
+ ],
277
+ };
278
+ }
279
+ try {
280
+ const result = await client.resumeAgent({ agent: trimmed });
281
+ return {
282
+ messages: [
283
+ {
284
+ role: "user",
285
+ content: {
286
+ type: "text",
287
+ text: `${result.resume_prompt}\n\n(session_id: ${result.session_id} — append every turn with mind_sessions action=append using this session_id)`,
288
+ },
289
+ },
290
+ ],
291
+ };
292
+ }
293
+ catch (e) {
294
+ const message = e instanceof mind_client_js_1.MindApiError
295
+ ? `Resume failed (HTTP ${e.status}): ${apiDetail(e)}`
296
+ : `Resume error: ${e instanceof Error ? e.message : String(e)}`;
297
+ return {
298
+ messages: [{ role: "user", content: { type: "text", text: message } }],
299
+ };
300
+ }
301
+ });
302
+ // ─── mind-sync-heartbeat prompt ──────────────────────────
303
+ // The body of the 15-minute "MIND sync heartbeat" routine every agent
304
+ // creates in its OWN scheduler (AGENTS.md section 2.11). MIND cannot wake a
305
+ // client, so the standard is: install a recurring routine whose whole body
306
+ // is this prompt.
307
+ server.registerPrompt("mind-sync-heartbeat", {
308
+ title: "MIND sync heartbeat (run every 15 minutes)",
309
+ description: "The body of the recurring 15-minute routine named \"MIND sync heartbeat\": one mind_sessions action=heartbeat call, answer every pending owner reply in your own app, append both sides. Create the routine once at install; paste this prompt as its whole body.",
310
+ }, async () => ({
311
+ messages: [
312
+ { role: "user", content: { type: "text", text: (0, integration_guide_js_1.buildSyncHeartbeatPrompt)() } },
313
+ ],
314
+ }));
315
+ // ─── mind://sessions/pending (+ subscribe) ──────────────
316
+ // Pending owner replies across all of this key's sessions. A client that
317
+ // supports resources/subscribe gets notifications/resources/updated when a
318
+ // NEW reply appears, so it need not wait for the next 15-minute beat. The
319
+ // stdio server can push (long-lived process): it polls the REST pending feed
320
+ // every PENDING_POLL_MS while at least one subscription is open. The hosted
321
+ // /mcp route is stateless and cannot push; it serves the same resource.
322
+ const PENDING_URI = "mind://sessions/pending";
323
+ const PENDING_POLL_MS = Number(process.env.MIND_PENDING_POLL_MS) > 0 ? Number(process.env.MIND_PENDING_POLL_MS) : 30_000;
324
+ const pendingSubscribers = new Set();
325
+ let pendingSeen = null;
326
+ let pendingTimer = null;
327
+ const pendingList = async () => {
328
+ const res = await client.pendingAgentReplies();
329
+ return (res.pending ?? []).map((p) => ({
330
+ session_id: p.session_id,
331
+ reply_id: p.reply_id ?? p.message_id,
332
+ content: p.content,
333
+ created_at: p.created_at,
334
+ agent: p.agent,
335
+ }));
336
+ };
337
+ const pollPending = async () => {
338
+ try {
339
+ const ids = new Set((await pendingList()).map((p) => String(p.reply_id)));
340
+ const hasNew = pendingSeen !== null && [...ids].some((id) => !pendingSeen.has(id));
341
+ pendingSeen = ids;
342
+ if (hasNew)
343
+ await server.server.sendResourceUpdated({ uri: PENDING_URI });
344
+ }
345
+ catch {
346
+ /* transient; next tick retries */
347
+ }
348
+ };
349
+ server.server.registerCapabilities({ resources: { subscribe: true } });
350
+ server.server.setRequestHandler(types_js_1.SubscribeRequestSchema, async (req) => {
351
+ pendingSubscribers.add(req.params.uri);
352
+ if (req.params.uri === PENDING_URI && !pendingTimer) {
353
+ pendingSeen = null;
354
+ void pollPending();
355
+ pendingTimer = setInterval(() => void pollPending(), PENDING_POLL_MS);
356
+ pendingTimer.unref?.();
357
+ }
358
+ return {};
359
+ });
360
+ server.server.setRequestHandler(types_js_1.UnsubscribeRequestSchema, async (req) => {
361
+ pendingSubscribers.delete(req.params.uri);
362
+ if (!pendingSubscribers.has(PENDING_URI) && pendingTimer) {
363
+ clearInterval(pendingTimer);
364
+ pendingTimer = null;
365
+ }
366
+ return {};
367
+ });
368
+ server.registerResource("sessions-pending", PENDING_URI, {
369
+ title: "Pending owner replies",
370
+ description: "Owner replies typed in MINDbots / MIND Chat that no agent has answered yet, across all your open sessions, oldest first. Subscribe (resources/subscribe) to be notified when a new one arrives; otherwise read it on every 15-minute heartbeat. Reading never marks a reply delivered; an append does.",
371
+ mimeType: "application/json",
372
+ }, async (uri) => ({
373
+ contents: [
374
+ {
375
+ uri: uri.href,
376
+ mimeType: "application/json",
377
+ text: JSON.stringify({ pending_replies: await pendingList() }, null, 2),
378
+ },
379
+ ],
380
+ }));
184
381
  // ─── adopt-agent-standard prompt ─────────────────────────
185
382
  // Same content as the mind://agent-standard resource (AGENT_STANDARD_MD —
186
383
  // see integration-guide.ts for provenance and the refresh recipe), offered
@@ -205,21 +402,33 @@ function createMindMcpServer(client) {
205
402
  // ─── mind_query ─────────────────────────────────────────
206
403
  // Semantic search across the user's knowledge graph.
207
404
  // Replaces flat MEMORY.md — returns only relevant memories.
208
- server.tool("mind_query", "Search the user's MIND knowledge graph — their persistent memory of people, projects, decisions, outcomes and history. Returns RETRIEVED CONTEXT for YOU to read and synthesize — documents, entries, entities and relationships — NOT a finished answer. Does not use MIND's LLM and costs 0 credits. Call this before asserting anything about the user's world, and before saying something does not exist. (Pass retrieve_only=false to have MIND's own LLM write the answer instead, which spends credits.)\n\nHOW TO ASK — this changes answer quality more than any parameter. Ask a rich, specific, full-sentence question; \"what is X\" retrieves poorly. Best results come from retrieve-then-write in one ask: \"Find everything about X, Y and Z, then write <deliverable> in this shape: <template>. Use only what you found; mark anything missing as unknown and do not invent it.\" Name the thing you expect to find — a document title, a person, a project, a date range. If the first answer is thin, change the QUESTION, not the mode — chain a narrower ask.\n\nPICKING A MODE (mechanical): naive reads document text only — best for recalling an exact passage or wording. local walks a named entity and its immediate graph neighbours — best for one specific person, company or thing. global reasons over relationships and themes across the whole graph — best for cross-cutting patterns. hybrid combines local+global graph reasoning but reads no raw document text. mix adds raw document text on top of hybrid and is the most complete; reach for it when hybrid feels thin. hybrid is the default — drop to naive/local/global only when you specifically need that narrower lens.\n\nNEGATIVES ARE THE HIGHEST-RISK ANSWER. An empty or thin result means your query missed — it is never proof the thing does not exist. Before reporting that something is absent, re-ask with different wording and say what you searched.", {
405
+ server.tool("mind_query", "Search the user's MIND knowledge graph — their persistent memory of people, projects, decisions, outcomes and history. Returns RETRIEVED CONTEXT for YOU to read and synthesize — documents, entries, entities and relationships — NOT a finished answer. Does not use MIND's LLM and costs 0 credits. Call this before asserting anything about the user's world, and before saying something does not exist. (Pass retrieve_only=false to have MIND's own LLM write the answer instead, which spends credits.)\n\nHOW TO ASK — this changes answer quality more than any parameter. Ask a rich, specific, full-sentence question; \"what is X\" retrieves poorly. Best results come from retrieve-then-write in one ask: \"Find everything about X, Y and Z, then write <deliverable> in this shape: <template>. Use only what you found; mark anything missing as unknown and do not invent it.\" Name the thing you expect to find — a document title, a person, a project, a date range. If the first answer is thin, change the QUESTION, not the mode — chain a narrower ask.\n\nPICKING A MODE (mechanical): naive reads document text only — best for recalling an exact passage or wording. local walks a named entity and its immediate graph neighbours — best for one specific person, company or thing. global reasons over relationships and themes across the whole graph — best for cross-cutting patterns. hybrid combines local+global graph reasoning but reads no raw document text. mix adds raw document text on top of hybrid and is the most complete; reach for it when hybrid feels thin. Omit mode to get the server default; drop to naive/local/global only when you specifically need that narrower lens.\n\nNEGATIVES ARE THE HIGHEST-RISK ANSWER. An empty or thin result means your query missed — it is never proof the thing does not exist. Before reporting that something is absent, re-ask with different wording and say what you searched.", {
209
406
  query: zod_1.z.string().describe("What to search for in your knowledge graph"),
210
407
  mode: zod_1.z
211
408
  .enum(["mix", "hybrid", "global", "local", "naive"])
212
409
  .optional()
213
- .default("hybrid")
214
- .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)"),
410
+ .optional()
411
+ .describe("Search mode. Omit it to use the server default (hybrid, or mix where the server enables it). mix: graph plus raw document text, the most complete, and it also finds documents still waiting for graph extraction. hybrid: combines local and global graph reasoning. global: broad graph search. local: focused graph search. naive: simple vector search over document text."),
215
412
  retrieve_only: zod_1.z
216
413
  .boolean()
217
414
  .optional()
218
415
  .default(true)
219
416
  .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)."),
220
- }, async ({ query, mode, retrieve_only }) => {
417
+ max_context_tokens: zod_1.z
418
+ .number()
419
+ .int()
420
+ .min(1000)
421
+ .max(30000)
422
+ .optional()
423
+ .describe("Optional ceiling on retrieved context in tokens (default 30000, about 120K characters). Lower it (e.g. 6000-10000) for a focused payload, and always when retrieve_only=false: MIND's LLM reads the whole context before answering, and a length instruction in the query only bounds the answer, not what is read."),
424
+ instructions: zod_1.z
425
+ .string()
426
+ .max(4000)
427
+ .optional()
428
+ .describe("Optional: how MIND's writer should answer (shape, length, audience) when retrieve_only=false. Keep `query` to the question itself and put everything about the answer here: instruction text inside `query` becomes search terms and measurably lowers retrieval quality (relevant documents 62% clean vs 46% wrapped, 2026-09-29). Ignored when retrieve_only=true."),
429
+ }, async ({ query, mode, retrieve_only, max_context_tokens, instructions }) => {
221
430
  try {
222
- const result = await client.query({ query, mode, retrieve_only });
431
+ const result = await client.query({ query, mode: mode ?? (0, exports.defaultQueryMode)(), retrieve_only, max_context_tokens, instructions });
223
432
  const text = [
224
433
  result.response,
225
434
  "",
@@ -264,7 +473,7 @@ function createMindMcpServer(client) {
264
473
  // ─── mind_remember ──────────────────────────────────────
265
474
  // Store/manage content in the knowledge graph. Auto-categorized by type.
266
475
  // Enhanced: now supports create, delete, search, get, list actions.
267
- 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.\n\nWRITE FOR THE AGENT WHO WILL SEARCH FOR THIS LATER — retrieval quality is decided here, not at query time. State facts in the words someone will later search for. Put the entity name in the first sentence. Inline dates, ids, file paths and numbers rather than saying \"the PR\" or \"yesterday\" — a note that only says \"fixed it\" is unfindable. If this supersedes an earlier note, say so explicitly and name what it replaces, so the older fact can be closed off instead of silently competing with this one.", {
476
+ 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: titles are plain natural language, written as a person would name the thing. No type prefixes (\"Journal Entry\", \"Agent Entry\", \"Document\"), no ids or hashes, no ISO timestamps; never put '/' or parentheses in a title, the card is derived from it. Lessons keep the 'Lesson - Failure - <behavior>' / 'Lesson - Behavior - <behavior>' / 'Lesson - Win - <behavior>' shape exactly as written (MIND never rewrites a lesson title) and files them under Lessons.\n\nFILING: every write is filed, nothing lands loose. Pass `folder` (a folder id, a folder name, or a 'Parent/Child' path) when you know where it belongs; otherwise leave it out and MIND files it by what it is (agent entries, automation logs, lessons, mistakes, decisions, documents). An explicit folder always wins; a secure folder is refused. One living document per entity: when a person, project, focus, calendar event or recurring job already has a record, update that record (mind_crm, mind_life, mind_focuses) or supersede the earlier note with `supersedes`; do not write a new entry that restates it.\n\nWRITE FOR THE AGENT WHO WILL SEARCH FOR THIS LATER — retrieval quality is decided here, not at query time. State facts in the words someone will later search for. Put the entity name in the first sentence. Inline dates, ids, file paths and numbers rather than saying \"the PR\" or \"yesterday\" — a note that only says \"fixed it\" is unfindable. If this supersedes an earlier note, say so explicitly and name what it replaces, so the older fact can be closed off instead of silently competing with this one.", {
268
477
  action: zod_1.z
269
478
  .enum(["create", "delete", "search", "get", "list"])
270
479
  .optional()
@@ -279,7 +488,7 @@ function createMindMcpServer(client) {
279
488
  title: zod_1.z
280
489
  .string()
281
490
  .optional()
282
- .describe("Title for documents and entries (auto-generated if omitted for feed posts)"),
491
+ .describe("Title for documents and entries: plain natural language, no type prefixes, no ids (auto-generated if omitted for feed posts)"),
283
492
  tags: zod_1.z
284
493
  .array(zod_1.z.string())
285
494
  .optional()
@@ -305,7 +514,16 @@ function createMindMcpServer(client) {
305
514
  .number()
306
515
  .optional()
307
516
  .describe("Max items to return for list/search"),
308
- }, async ({ action, content, type, title, tags, source, item_id, query, page, limit }) => {
517
+ folder: zod_1.z
518
+ .string()
519
+ .optional()
520
+ .describe("Where to file this write: a folder id, a folder name, or a 'Parent/Child' path (mind_folders list shows them). Optional: omit it and MIND files the write by what it is. An explicit folder always wins; a secure folder is refused. Applies to documents and entries."),
521
+ supersedes: zod_1.z
522
+ .array(zod_1.z.string())
523
+ .max(10)
524
+ .optional()
525
+ .describe("Entries only: earlier records of yours this entry replaces, by id, exact title, or the exact reference title retrieval shows (e.g. \"Agent Entry: <title>\"; older entries show \"Journal Entry: <title>\"). They are tagged superseded, never deleted, and stop being retrieved once the server enables it. Use it whenever you record a fix, reversal or newer version, so MIND stops serving the old fact."),
526
+ }, async ({ action, content, type, title, tags, source, folder, item_id, query, page, limit, supersedes }) => {
309
527
  try {
310
528
  switch (action) {
311
529
  case "create": {
@@ -323,6 +541,8 @@ function createMindMcpServer(client) {
323
541
  title: title ?? `Agent Document — ${new Date().toISOString().slice(0, 10)}`,
324
542
  content,
325
543
  source: source ?? "mcp-agent",
544
+ ...(tags && tags.length ? { tags } : {}),
545
+ ...(folder ? { folder } : {}),
326
546
  });
327
547
  storedAs = "document";
328
548
  break;
@@ -337,6 +557,8 @@ function createMindMcpServer(client) {
337
557
  title,
338
558
  content,
339
559
  tags,
560
+ ...(folder ? { folder } : {}),
561
+ ...(supersedes && supersedes.length ? { supersedes } : {}),
340
562
  });
341
563
  storedAs = "entry";
342
564
  break;
@@ -452,7 +674,7 @@ function createMindMcpServer(client) {
452
674
  // ─── mind_folders ───────────────────────────────────────
453
675
  // Organize documents into folders. Folders are a presentation layer — the
454
676
  // knowledge graph still indexes every document regardless of folder.
455
- server.tool("mind_folders", "Organize your MIND documents into folders. Folders are a presentation layer — the knowledge graph still indexes and retrieves across every document regardless of folder. Use mind_remember to create a document, then mind_folders move_documents to file it. Actions: list, create, rename, move, delete, move_documents, set_hint, secure, unsecure, unlock, reset_request, reset (Secure Folders — password-gate a folder).", {
677
+ server.tool("mind_folders", "Organize your MIND documents into folders. Folders are a presentation layer — the knowledge graph still indexes and retrieves across every document regardless of folder. Every write is already filed by MIND, so you rarely need to move anything: pass `folder` to mind_remember when you know where it belongs, and use move_documents to correct a filing. Actions: list, create, rename, move, delete, move_documents, set_hint, secure, unsecure, unlock, reset_request, reset (Secure Folders — password-gate a folder).", {
456
678
  action: zod_1.z
457
679
  .enum([
458
680
  "list",
@@ -853,8 +1075,43 @@ function createMindMcpServer(client) {
853
1075
  .optional()
854
1076
  .default(["soul", "user", "rules", "priorities", "recent"])
855
1077
  .describe("Which context sections to load: soul (identity/personality), user (who the user is), rules (operating constraints), priorities (current goals/tasks), recent (latest activity)"),
856
- }, async ({ sections }) => {
1078
+ for_task: zod_1.z
1079
+ .string()
1080
+ .max(2000)
1081
+ .optional()
1082
+ .describe("Optional: one or two sentences describing the task you are about to do. Steers retrieval for soul/user/rules/priorities toward what that task needs."),
1083
+ max_tokens: zod_1.z
1084
+ .number()
1085
+ .int()
1086
+ .min(200)
1087
+ .max(50000)
1088
+ .optional()
1089
+ .describe("Optional soft ceiling on the returned context size (rough tokens, default 6000). Lowest-priority items are dropped first and the response says how many."),
1090
+ action: zod_1.z
1091
+ .enum(["load", "get_pins", "set_pins"])
1092
+ .optional()
1093
+ .default("load")
1094
+ .describe("load (default) returns context. get_pins / set_pins read or replace the owner's pinned canonical document ids for soul, user and rules; pinned sections are read directly instead of retrieved, so no look-alike document can take their place."),
1095
+ pins: zod_1.z
1096
+ .object({
1097
+ soul: zod_1.z.array(zod_1.z.string()).max(5).optional(),
1098
+ user: zod_1.z.array(zod_1.z.string()).max(5).optional(),
1099
+ rules: zod_1.z.array(zod_1.z.string()).max(5).optional(),
1100
+ })
1101
+ .optional()
1102
+ .describe("For set_pins: document ids per section (max 5). Omit a section to keep it, [] to clear it. Only your own documents are accepted."),
1103
+ }, async ({ sections, for_task, max_tokens, action, pins }) => {
857
1104
  try {
1105
+ if (action === "get_pins") {
1106
+ return ok(await client.getContextPins());
1107
+ }
1108
+ if (action === "set_pins") {
1109
+ return ok(await client.setContextPins(pins ?? {}));
1110
+ }
1111
+ if ((0, exports.contextV1Enabled)()) {
1112
+ const data = await client.context({ sections, for_task, max_tokens });
1113
+ return { content: [{ type: "text", text: (0, exports.renderContextV1)(data) }] };
1114
+ }
858
1115
  const sectionQueries = {
859
1116
  soul: "my identity, mission, personality, who I am, my role and purpose",
860
1117
  user: "user profile, preferences, timezone, communication style, who is the user",
@@ -3139,14 +3396,20 @@ function createMindMcpServer(client) {
3139
3396
  // so a client that only ever reads tool descriptions still gets it.
3140
3397
  server.tool("mind_sessions", "Log THIS agent's own live session into MIND Chat → Agents, so Anthony can read the transcript and reply — his reply reaches you on your next turn. Distinct from mind_remember (one durable fact) and mind_train (teaching the KG): this is the live, turn-by-turn session log, mirrored into a MIND document when you close it.\n\n" +
3141
3398
  "PROTOCOL (do this every session): " +
3142
- "1) CONNECT — as soon as mind_context succeeds, call action=open with source_key (your assigned MIND Chat toggle, e.g. \"claude-code-1\"), external_session_id (your runtime's own session id), runtime, title (first user ask, 6-10 words), machine/cwd/repo/branch/model. Your real source is derived from runtime x installation (device/platform/env), never the literal source_key — the response's own `source_key` tells you what it actually resolved to. If you're a named agent within a shared runtime (a scheduled job, a persona, a bot on a shared account), pass `agent` (a bare name, or {key,label}) so sessions attribute to WHO ran them, not just where — this works for every runtime, not only shared/cloud ones. If `resumed` is true, read `tail` before answering, and answer everything in `pending_replies` first — those arrived while you were away. " +
3399
+ "1) CONNECT — as soon as mind_context succeeds, call action=open with source_key (your assigned MIND Chat toggle, e.g. \"claude-code-1\"), external_session_id (your runtime's own session id), runtime, title (first user ask, 6-10 words), machine/cwd/repo/branch/model. Your real source is derived from runtime x installation (device/platform/env), never the literal source_key — the response's own `source_key` tells you what it actually resolved to. If you're a named agent within a shared runtime (a scheduled job, a persona, a bot on a shared account), pass `agent` (a bare name, or {key,label}) so sessions attribute to WHO ran them, not just where — this works for every runtime, not only shared/cloud ones. If you know which Life project the work is for, pass its item id as `project_id` so the Agent Activity Map files the session there (otherwise MIND infers it). If `resumed` is true, read `tail` before answering, and answer everything in `pending_replies` first — those arrived while you were away. " +
3143
3400
  "2) EVERY TURN — after you finish replying, call action=append with the user's message and your final reply (role user / assistant); tool calls go in as role=tool one-line summaries, never raw payloads. Check `pending_replies` on the response and answer them next turn. action=reply is a shortcut for appending a single assistant-role message when you have no user message to log alongside it. " +
3144
3401
  "3) IDLE — MIND marks the session idle after 30 minutes with no append; the next append revives it, nothing to do meanwhile. " +
3145
3402
  "4) TERMINATE — on exit, compaction, or \"done\", call action=close with a summary (what was asked, what shipped with ids/PR numbers, what is still undone); MIND mirrors the transcript into your Sessions folder as a document. " +
3146
3403
  "5) HANDOFF — to pass the conversation to another agent, call action=handoff with to_source_key=<their toggle>; they see it in their session list with the transcript as context. " +
3147
- "6) SHARE — to let another MIND user follow this session, call action=share with grantee_username and role (\"viewer\" read-only, the default, or \"replier\" which also lets them reply — a reply is a live action that wakes your process via wake_url, so grant it deliberately). It's a live mirror, never a copy: they always see the current transcript, and revoking (action=revoke_share) removes their access immediately. Never share a session with anyone who shouldn't see its full transcript.\n" +
3404
+ "6) SHARE — to let another MIND user follow this session, call action=share with grantee_username and role (\"viewer\" read-only, the default, or \"replier\" which also lets them reply — a reply is a live action that wakes your process via wake_url, so grant it deliberately). It's a live mirror, never a copy: they always see the current transcript, and revoking (action=revoke_share) removes their access immediately. Never share a session with anyone who shouldn't see its full transcript. " +
3405
+ "7) AGENT FILE — if you are a named, standing agent (not a bare runtime), keep your agent file current with action=agent_doc_update (editor=\"agent\", with a `note`) whenever your role, rules or standing work change; read it anytime with action=agent_doc_get. " +
3406
+ "8) RESUME ANYWHERE — when the user says \"run my <Agent> session\" / \"be <Agent>\" / \"resume <Agent>\", call action=resume with agent=<Agent> and adopt its `resume_prompt` verbatim as your operating brief for the rest of the turn — it already carries your agent file, attached context, where you left off, and any pending replies.\n" +
3407
+ "9) HEARTBEAT — every 15 minutes from YOUR OWN scheduler (MIND cannot wake you): action=heartbeat with source_key, agent, runtime (+ optional unsynced turns). Returns pending_replies across your open sessions, oldest first (session_id, reply_id, content, created_at), plus next_beat_seconds. Answer each in your own app, then append both sides; the append marks it delivered, a heartbeat never does. Install once: a recurring routine named \"MIND sync heartbeat\" whose body is the MCP prompt mind-sync-heartbeat. MINDbots shows \"Sync stale\" after 20 minutes of silence.\n" +
3408
+ "10) " + integration_guide_js_1.AGENT_SESSION_REPLIES_RULE + "\n" +
3148
3409
  "Never claim a session is synced without the session_id MIND returned. Never log secrets or raw tool payloads.\n\n" +
3149
- "Actions: open, append, close, list, get, reply (alias for append of one assistant message), inbox (undelivered mind-origin replies), handoff, sources (source_action=list|create|update|delete manages the MIND Chat sidebar toggles — auto-created on first open with an unknown source_key), share (grant another MIND account viewer or replier access — owner only), list_shares (owner only), revoke_share (owner only).", {
3410
+ "Actions: open, append, close, list, get, reply (alias for append of one assistant message), heartbeat (the 15-minute sync beat), wake_target_set (agent, url, secret, runtime default \"grok\": register a Grok Bot \"MIND wake\" webhook routine so MIND POSTs it the moment your owner replies; the secret is sent as `Authorization: Bearer` and is never echoed back), wake_targets (list registered wake targets, masked), wake_target_clear (agent, runtime: remove one), inbox (undelivered mind-origin replies), handoff, sources (source_action=list|create|update|delete manages the MIND Chat sidebar toggles — auto-created on first open with an unknown source_key), share (grant another MIND account viewer or replier access — owner only), list_shares (owner only), revoke_share (owner only), activity (Agent Activity Map: which agents worked on which Life projects over `days`, with sessions, turns, tool calls, active minutes and Anthony's replies per project, plus sessions that still need a project), set_project (assign a session to a Life project by `project_id`, or clear it with project_id omitted; `learn` (default true) makes the session's working folder or repo route future sessions to the same project), agent_doc_get (read a standing agent's file — its role/rules/tools/standing-work doc, identified by `agent_key`; returns a starter template when none exists yet), agent_doc_update (save a new version of that file; `editor` is \"user\" or \"agent\" — \"agent\" requires a `note` saying what changed and why; a stale `base_version` 409s with the current doc instead of clobbering a concurrent edit), attach (replace the full list of Life projects/focuses/workspaces/repos an agent is connected to, by `agent_key` and `attachments`), mentions (read/mark-delivered this key's pending @mentions), group_create, group_list, group_get, group_post, group_inbox, group_delete (see GROUP CHATS / @ MENTIONS below), resume (pick up a named agent — `agent` — wherever it last left off: its agent file, attached context with live summaries, the previous session's tail/summary, any pending replies, and a ready-to-adopt `resume_prompt`; also opens a fresh continuation session for you to keep working in).\n\n" +
3411
+ "GROUP CHATS — group_create (title, participant_keys: 2-20 source keys) opens a group thread; group_post (group_id, author_key, content) appends a message and delivers it into every OTHER participant's durable @mention inbox (see action=mentions) — group membership is an implicit mention of everyone else in the room; group_get (group_id) reads the full thread; group_list lists your groups; group_inbox (group_id, as_key) is a group-scoped view of the same inbox action=mentions reads; group_delete removes a group and its thread. " +
3412
+ "@ MENTIONS — writing \"@<source_key>\" inside ANY posted content (a 1:1 session's append/reply, or a group_post) durably queues a pending mention for that key, independent of whether it has an open session right now or lives in a different chat/group entirely. action=mentions (as_key) returns and marks delivered every pending mention of that key — poll it the same way you poll action=inbox.", {
3150
3413
  action: zod_1.z
3151
3414
  .enum([
3152
3415
  "open",
@@ -3161,12 +3424,30 @@ function createMindMcpServer(client) {
3161
3424
  "share",
3162
3425
  "list_shares",
3163
3426
  "revoke_share",
3427
+ "activity",
3428
+ "set_project",
3429
+ "agent_doc_get",
3430
+ "agent_doc_update",
3431
+ "attach",
3432
+ "resume",
3433
+ "mentions",
3434
+ "group_create",
3435
+ "group_list",
3436
+ "group_get",
3437
+ "group_post",
3438
+ "group_inbox",
3439
+ "group_delete",
3440
+ "wake_target",
3441
+ "wake_target_set",
3442
+ "wake_targets",
3443
+ "wake_target_clear",
3444
+ "heartbeat",
3164
3445
  ])
3165
3446
  .describe("Which agent-session operation to perform."),
3166
3447
  // open
3167
3448
  source_key: zod_1.z.string().optional().describe("Your assigned MIND Chat sidebar toggle slug, e.g. \"claude-code-1\" — required for open; also filters list; also required for sources action=create (as the new source's key)."),
3168
3449
  external_session_id: zod_1.z.string().optional().describe("Your runtime's own session id — required for open. Idempotent: opening the same (source_key, external_session_id) again resumes the existing session instead of creating a new one."),
3169
- runtime: zod_1.z.string().optional().describe("Runtime name, e.g. \"claude-code\", \"codex\", \"cursor\", \"openclaw\", \"grok\", \"n8n\", \"custom\" — open (used if the source is auto-created); sources action=create/update."),
3450
+ runtime: zod_1.z.string().optional().describe("Runtime name, e.g. \"claude-code\", \"codex\", \"cursor\", \"openclaw\", \"grok\", \"n8n\", \"custom\" — open (used if the source is auto-created); sources action=create/update; wake_target_set / wake_target_clear (default \"grok\")."),
3170
3451
  source_label: zod_1.z.string().optional().describe("Human-readable label for an auto-created source on open, e.g. \"Claude Code 1\" — defaults to the titlecased source_key if omitted."),
3171
3452
  title: zod_1.z.string().optional().describe("Session title (first user ask, 6-10 words) — open (initial) or append (updates it)."),
3172
3453
  machine: zod_1.z.string().optional().describe("Machine identifier — open."),
@@ -3188,9 +3469,12 @@ function createMindMcpServer(client) {
3188
3469
  agent: zod_1.z
3189
3470
  .union([zod_1.z.string(), zod_1.z.object({ key: zod_1.z.string(), label: zod_1.z.string().optional() })])
3190
3471
  .optional()
3191
- .describe("Who is actually running this session — an attribute OF the session, never a source key, and unconditional on runtime (a claude-code cron run can carry agent=\"Chief of Staff\" exactly like a grok run carries agent=\"Dae\"). Bare string used as both key and label, or {key, label}. Falls back to a legacy alias for a bot-shaped source_key (\"dae\"→Dae, \"meter\"→Meter, \"grok-bot\"→Helm, \"n8n\"→n8n), then to the runtime's own display name — open only."),
3472
+ .describe("Who is actually running this session — an attribute OF the session, never a source key, and unconditional on runtime (a claude-code cron run can carry agent=\"Chief of Staff\" exactly like a grok run carries agent=\"Dae\"). Bare string used as both key and label, or {key, label}. Falls back to a legacy alias for a bot-shaped source_key (\"dae\"→Dae, \"meter\"→Meter, \"grok-bot\"→Helm, \"n8n\"→n8n), then to the runtime's own display name — open only. For resume, a plain string naming the agent to pick up, e.g. \"Meter\" — required."),
3473
+ project_id: zod_1.z.string().optional().describe("Life project item id this session works on — open (declared) and set_project (omit to clear)."),
3474
+ days: zod_1.z.number().positive().max(90).optional().describe("Lookback window in days for activity (default 7, max 90)."),
3475
+ learn: zod_1.z.boolean().optional().describe("set_project: remember this session's working folder or repo for future sessions (default true)."),
3192
3476
  // append / reply
3193
- session_id: zod_1.z.string().optional().describe("Session id returned by open — required for append/close/get/inbox/handoff."),
3477
+ session_id: zod_1.z.string().optional().describe("Session id returned by open — required for append/close/get/inbox/handoff/set_project."),
3194
3478
  messages: zod_1.z
3195
3479
  .array(zod_1.z.object({
3196
3480
  role: zod_1.z.enum(["user", "assistant", "system", "tool"]),
@@ -3201,7 +3485,7 @@ function createMindMcpServer(client) {
3201
3485
  }))
3202
3486
  .optional()
3203
3487
  .describe("Messages to append, in order — required for append. Tool-call messages should be role=tool, one-line summaries only, never raw payloads."),
3204
- content: zod_1.z.string().optional().describe("Message text — required for reply (appended as a single role=assistant message)."),
3488
+ content: zod_1.z.string().optional().describe("Message text — required for reply (appended as a single role=assistant message). Also used for agent_doc_update: the agent file's full markdown content — required there too."),
3205
3489
  // close
3206
3490
  summary: zod_1.z.string().optional().describe("What was asked, what shipped (with ids/PR numbers), what is still undone — close. If omitted, MIND builds one from the transcript."),
3207
3491
  // list / close (status set) share this field
@@ -3220,10 +3504,37 @@ function createMindMcpServer(client) {
3220
3504
  color: zod_1.z.string().optional().describe("Sidebar chip color — sources action=create/update."),
3221
3505
  wake_url: zod_1.z.string().optional().describe("Optional push endpoint MIND POSTs {session_id, reply} to (fire-and-forget, 5s timeout) when the user replies — sources action=create/update."),
3222
3506
  force: zod_1.z.boolean().optional().describe("sources action=delete: also delete the source's sessions instead of failing 409 when sessions exist."),
3507
+ // wake_target (Grok Bot webhook routine push)
3508
+ wake_action: zod_1.z.enum(["set", "list", "clear"]).optional().describe("Sub-action for action=wake_target (default set): set registers this bot's webhook-routine URL, list shows registered bots (masked), clear removes one."),
3509
+ url: zod_1.z.string().optional().describe("wake_target_set (or wake_target set): the https URL of this bot's \"MIND wake\" webhook routine. Owner-private, never echoed back."),
3510
+ secret: zod_1.z.string().optional().describe("wake_target_set (or wake_target set): the webhook routine's API key; MIND sends it as 'Authorization: Bearer <secret>' and never in the body. Owner-private, never echoed back."),
3223
3511
  // share / list_shares / revoke_share
3224
3512
  grantee_username: zod_1.z.string().optional().describe("MIND username to share with — required for share."),
3225
3513
  role: zod_1.z.enum(["viewer", "replier"]).optional().describe("Role to grant — share (default \"viewer\"). \"replier\" additionally allows the grantee to reply, which reaches you via wake_url."),
3226
3514
  share_id: zod_1.z.string().optional().describe("Share grant id (from share or list_shares) — required for revoke_share."),
3515
+ // agent_doc_get / agent_doc_update / attach
3516
+ agent_key: zod_1.z.string().optional().describe("The agent's persona key, e.g. \"meter\" or \"Meter\" (any casing — the server normalizes it) — required for agent_doc_get, agent_doc_update, attach."),
3517
+ editor: zod_1.z.enum(["user", "agent"]).optional().describe("Who is saving this version — required for agent_doc_update. \"agent\" requires `note`."),
3518
+ note: zod_1.z.string().optional().describe("What changed and why — required for agent_doc_update when editor=\"agent\"."),
3519
+ base_version: zod_1.z.number().int().optional().describe("The version you last read, for agent_doc_update — a stale value 409s with the current doc instead of silently overwriting a concurrent edit."),
3520
+ attachments: zod_1.z
3521
+ .array(zod_1.z.object({
3522
+ kind: zod_1.z.enum(["project", "focus", "workspace", "repo"]),
3523
+ id: zod_1.z.string(),
3524
+ label: zod_1.z.string().optional(),
3525
+ }))
3526
+ .optional()
3527
+ .describe("The FULL replacement list of what this agent is connected to — required for attach. kind=\"project\"/\"focus\" ids are Life item ids, \"workspace\" is a workspace id, \"repo\" is \"owner/name\"."),
3528
+ // heartbeat
3529
+ unsynced: zod_1.z
3530
+ .array(zod_1.z.record(zod_1.z.string(), zod_1.z.any()))
3531
+ .optional()
3532
+ .describe("heartbeat: turns since your last append that MIND has not seen. Each item {session_id, role, content} or {session_id, messages:[...]}. Appended WITHOUT marking pending replies delivered."),
3533
+ // groups / mentions
3534
+ as_key: zod_1.z.string().optional().describe("Which agent key is asking — required for mentions / group_inbox: whose durable @mention inbox to read (and mark delivered)."),
3535
+ group_id: zod_1.z.string().optional().describe("A group's id, returned by group_create — required for group_get/group_post/group_inbox/group_delete."),
3536
+ participant_keys: zod_1.z.array(zod_1.z.string()).optional().describe("Source keys (2-20) of every agent in the group — required for group_create."),
3537
+ author_key: zod_1.z.string().optional().describe("Which participant is posting — required for group_post. Must be one of the group's participant_keys."),
3227
3538
  }, async (args) => {
3228
3539
  const { action } = args;
3229
3540
  try {
@@ -3234,7 +3545,7 @@ function createMindMcpServer(client) {
3234
3545
  return err("Error: 'source_key' is required for open.");
3235
3546
  if (!external_session_id)
3236
3547
  return err("Error: 'external_session_id' is required for open.");
3237
- return ok(await client.openAgentSession({
3548
+ return okSession(await client.openAgentSession({
3238
3549
  source_key,
3239
3550
  external_session_id,
3240
3551
  runtime: args.runtime,
@@ -3250,15 +3561,27 @@ function createMindMcpServer(client) {
3250
3561
  platform: args.platform,
3251
3562
  env: args.env,
3252
3563
  agent: args.agent,
3564
+ project_id: args.project_id,
3253
3565
  }));
3254
3566
  }
3567
+ case "activity":
3568
+ return ok(await client.getAgentActivity(args.days));
3569
+ case "set_project": {
3570
+ const { session_id } = args;
3571
+ if (!session_id)
3572
+ return err("Error: 'session_id' is required for set_project.");
3573
+ return ok(await client.setAgentSessionProject(session_id, args.project_id ?? null, args.learn));
3574
+ }
3255
3575
  case "append": {
3256
3576
  const { session_id, messages } = args;
3257
3577
  if (!session_id)
3258
3578
  return err("Error: 'session_id' is required for append.");
3259
3579
  if (!messages?.length)
3260
3580
  return err("Error: 'messages' (at least one) is required for append.");
3261
- return ok(await client.appendAgentSession(session_id, messages, args.title));
3581
+ const { device, platform, env } = args;
3582
+ return okSession(device || platform || env
3583
+ ? await client.appendAgentSession(session_id, messages, args.title, { device, platform, env })
3584
+ : await client.appendAgentSession(session_id, messages, args.title));
3262
3585
  }
3263
3586
  case "reply": {
3264
3587
  const { session_id, content } = args;
@@ -3266,7 +3589,7 @@ function createMindMcpServer(client) {
3266
3589
  return err("Error: 'session_id' is required for reply.");
3267
3590
  if (!content)
3268
3591
  return err("Error: 'content' is required for reply.");
3269
- return ok(await client.appendAgentSession(session_id, [{ role: "assistant", content }]));
3592
+ return okSession(await client.appendAgentSession(session_id, [{ role: "assistant", content }]));
3270
3593
  }
3271
3594
  case "close": {
3272
3595
  const { session_id } = args;
@@ -3294,11 +3617,22 @@ function createMindMcpServer(client) {
3294
3617
  before_seq: args.before_seq,
3295
3618
  }));
3296
3619
  }
3620
+ case "heartbeat": {
3621
+ const { source_key } = args;
3622
+ if (!source_key)
3623
+ return err("Error: 'source_key' is required for heartbeat.");
3624
+ return ok(await client.agentSessionHeartbeat({
3625
+ source_key,
3626
+ agent: args.agent,
3627
+ runtime: args.runtime,
3628
+ unsynced: args.unsynced,
3629
+ }));
3630
+ }
3297
3631
  case "inbox": {
3298
3632
  const { session_id } = args;
3299
3633
  if (!session_id)
3300
3634
  return err("Error: 'session_id' is required for inbox.");
3301
- return ok(await client.agentSessionInbox(session_id));
3635
+ return okSession(await client.agentSessionInbox(session_id));
3302
3636
  }
3303
3637
  case "handoff": {
3304
3638
  const { session_id, to_source_key } = args;
@@ -3353,6 +3687,39 @@ function createMindMcpServer(client) {
3353
3687
  }
3354
3688
  }
3355
3689
  }
3690
+ // Flat aliases for the three wake_target sub-actions (additive; the
3691
+ // wake_target + wake_action form below keeps working). The secret is
3692
+ // owner-private: the backend never returns it, so neither do we.
3693
+ case "wake_targets":
3694
+ return ok(await client.listAgentWakeTargets());
3695
+ case "wake_target_set":
3696
+ case "wake_target_clear": {
3697
+ if (!args.agent)
3698
+ return err(`Error: 'agent' is required for ${action}.`);
3699
+ const wakeAgent = typeof args.agent === "string" ? args.agent : args.agent.label ?? args.agent.key;
3700
+ if (!wakeAgent)
3701
+ return err(`Error: 'agent' must be a bot name for ${action}.`);
3702
+ if (action === "wake_target_clear")
3703
+ return ok(await client.clearAgentWakeTarget(wakeAgent, args.runtime ?? "grok"));
3704
+ if (!args.url)
3705
+ return err("Error: 'url' (the webhook routine URL) is required for wake_target_set.");
3706
+ return ok(await client.setAgentWakeTarget({ agent: wakeAgent, url: args.url, secret: args.secret, runtime: args.runtime ?? "grok" }));
3707
+ }
3708
+ case "wake_target": {
3709
+ const wakeAction = args.wake_action ?? "set";
3710
+ if (wakeAction === "list")
3711
+ return ok(await client.listAgentWakeTargets());
3712
+ if (!args.agent)
3713
+ return err(`Error: 'agent' is required for wake_target wake_action=${wakeAction}.`);
3714
+ const agentName = typeof args.agent === "string" ? args.agent : args.agent.label ?? args.agent.key;
3715
+ if (!agentName)
3716
+ return err("Error: 'agent' must be a bot name for wake_target.");
3717
+ if (wakeAction === "clear")
3718
+ return ok(await client.clearAgentWakeTarget(agentName, args.runtime));
3719
+ if (!args.url)
3720
+ return err("Error: 'url' (the webhook routine URL) is required for wake_target wake_action=set.");
3721
+ return ok(await client.setAgentWakeTarget({ agent: agentName, url: args.url, secret: args.secret, runtime: args.runtime }));
3722
+ }
3356
3723
  case "share": {
3357
3724
  const { session_id, grantee_username } = args;
3358
3725
  if (!session_id)
@@ -3379,6 +3746,94 @@ function createMindMcpServer(client) {
3379
3746
  await client.revokeAgentSessionShare(session_id, share_id);
3380
3747
  return ok({ ok: true, session_id, share_id });
3381
3748
  }
3749
+ case "agent_doc_get": {
3750
+ const { agent_key } = args;
3751
+ if (!agent_key)
3752
+ return err("Error: 'agent_key' is required for agent_doc_get.");
3753
+ return ok(await client.getAgentDoc(agent_key));
3754
+ }
3755
+ case "agent_doc_update": {
3756
+ const { agent_key, content, editor } = args;
3757
+ if (!agent_key)
3758
+ return err("Error: 'agent_key' is required for agent_doc_update.");
3759
+ if (content === undefined)
3760
+ return err("Error: 'content' is required for agent_doc_update.");
3761
+ if (!editor)
3762
+ return err("Error: 'editor' is required for agent_doc_update.");
3763
+ return ok(await client.updateAgentDoc(agent_key, {
3764
+ content,
3765
+ editor,
3766
+ note: args.note,
3767
+ base_version: args.base_version,
3768
+ }));
3769
+ }
3770
+ case "attach": {
3771
+ const { agent_key, attachments } = args;
3772
+ if (!agent_key)
3773
+ return err("Error: 'agent_key' is required for attach.");
3774
+ if (!attachments)
3775
+ return err("Error: 'attachments' is required for attach.");
3776
+ return ok(await client.setAgentAttachments(agent_key, attachments));
3777
+ }
3778
+ case "resume": {
3779
+ const { agent } = args;
3780
+ if (!agent || typeof agent !== "string") {
3781
+ return err("Error: 'agent' (a plain string naming the agent) is required for resume.");
3782
+ }
3783
+ return ok(await client.resumeAgent({
3784
+ agent,
3785
+ runtime: args.runtime,
3786
+ source_key: args.source_key,
3787
+ external_session_id: args.external_session_id,
3788
+ title: args.title,
3789
+ }));
3790
+ }
3791
+ case "mentions": {
3792
+ const as_key = args.as_key;
3793
+ if (!as_key)
3794
+ return err("Error: 'as_key' is required for mentions.");
3795
+ return ok(await client.getAgentMentions(as_key));
3796
+ }
3797
+ case "group_create": {
3798
+ const { participant_keys } = args;
3799
+ if (!participant_keys || participant_keys.length === 0) {
3800
+ return err("Error: 'participant_keys' (at least 2) is required for group_create.");
3801
+ }
3802
+ return ok(await client.createAgentSessionGroup(args.title ?? "New group", participant_keys));
3803
+ }
3804
+ case "group_list":
3805
+ return ok(await client.listAgentSessionGroups());
3806
+ case "group_get": {
3807
+ const { group_id } = args;
3808
+ if (!group_id)
3809
+ return err("Error: 'group_id' is required for group_get.");
3810
+ return ok(await client.getAgentSessionGroup(group_id, args.limit));
3811
+ }
3812
+ case "group_post": {
3813
+ const { group_id, author_key, content } = args;
3814
+ if (!group_id)
3815
+ return err("Error: 'group_id' is required for group_post.");
3816
+ if (!author_key)
3817
+ return err("Error: 'author_key' is required for group_post.");
3818
+ if (!content)
3819
+ return err("Error: 'content' is required for group_post.");
3820
+ return ok(await client.postAgentSessionGroupMessage(group_id, author_key, content));
3821
+ }
3822
+ case "group_inbox": {
3823
+ const { group_id, as_key } = args;
3824
+ if (!group_id)
3825
+ return err("Error: 'group_id' is required for group_inbox.");
3826
+ if (!as_key)
3827
+ return err("Error: 'as_key' is required for group_inbox.");
3828
+ return ok(await client.getAgentSessionGroupInbox(group_id, as_key));
3829
+ }
3830
+ case "group_delete": {
3831
+ const { group_id } = args;
3832
+ if (!group_id)
3833
+ return err("Error: 'group_id' is required for group_delete.");
3834
+ await client.deleteAgentSessionGroup(group_id);
3835
+ return ok({ ok: true });
3836
+ }
3382
3837
  default: {
3383
3838
  const _exhaustive = action;
3384
3839
  return err(`Unknown action: ${_exhaustive}`);