@anchrd/intel-api 0.13.0 → 0.15.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.
Files changed (57) hide show
  1. package/dist/adapters/cloudflare/cloudflare.js +1 -68
  2. package/dist/adapters/cloudflare/cloudflare.types.d.ts +0 -39
  3. package/dist/adapters/db/db-flows.js +1 -1
  4. package/dist/adapters/db/db-grants.js +1 -1
  5. package/dist/adapters/db/db-indexing.js +79 -0
  6. package/dist/adapters/db/db.js +81 -139
  7. package/dist/adapters/semantic-index/semantic-index.js +97 -17
  8. package/dist/adapters/semantic-index/semantic-index.types.d.ts +20 -1
  9. package/dist/bundle/bundle.js +42 -134
  10. package/dist/cli/cli.js +3 -9
  11. package/dist/http/http.js +5 -206
  12. package/dist/http/http.types.d.ts +0 -8
  13. package/dist/indexing/indexing.js +133 -55
  14. package/dist/indexing/indexing.types.d.ts +1 -0
  15. package/dist/intel/intel.js +4 -9
  16. package/dist/intel/intel.types.d.ts +0 -6
  17. package/dist/mcp/mcp.js +33 -308
  18. package/dist/mcp/mcp.types.d.ts +2 -7
  19. package/dist/nodes/document-links/document-links.d.ts +6 -8
  20. package/dist/nodes/document-links/document-links.js +8 -31
  21. package/dist/nodes/nodes.js +92 -826
  22. package/dist/nodes/nodes.types.d.ts +57 -158
  23. package/dist/tools/tools.js +37 -148
  24. package/dist/tools/tools.types.d.ts +0 -21
  25. package/migrations/0009_no_context_policy.sql +15 -0
  26. package/migrations/0017_a_vector_per_card.sql +38 -0
  27. package/migrations/0018_no_context_policy_at_last.sql +97 -0
  28. package/migrations/0019_one_name_for_the_grants.sql +52 -0
  29. package/package.json +2 -2
  30. package/dist/adapters/cloudflare-api/cloudflare-api.d.ts +0 -22
  31. package/dist/adapters/cloudflare-api/cloudflare-api.js +0 -214
  32. package/dist/adapters/cloudflare-api/cloudflare-api.types.d.ts +0 -64
  33. package/dist/adapters/cloudflare-api/cloudflare-api.types.js +0 -1
  34. package/dist/adapters/gate-applications/gate-applications.d.ts +0 -23
  35. package/dist/adapters/gate-applications/gate-applications.js +0 -88
  36. package/dist/adapters/tool-delegation/tool-delegation.d.ts +0 -22
  37. package/dist/adapters/tool-delegation/tool-delegation.js +0 -90
  38. package/dist/agent-costs/agent-costs.d.ts +0 -16
  39. package/dist/agent-costs/agent-costs.js +0 -105
  40. package/dist/agent-costs/agent-costs.types.d.ts +0 -30
  41. package/dist/agent-costs/agent-costs.types.js +0 -1
  42. package/dist/agent-runtime/agent-runtime.d.ts +0 -16
  43. package/dist/agent-runtime/agent-runtime.js +0 -150
  44. package/dist/agent-runtime/agent-runtime.types.d.ts +0 -122
  45. package/dist/agent-runtime/agent-runtime.types.js +0 -1
  46. package/dist/model-catalog/model-catalog.d.ts +0 -2
  47. package/dist/model-catalog/model-catalog.js +0 -99
  48. package/dist/model-catalog/model-catalog.types.d.ts +0 -15
  49. package/dist/model-catalog/model-catalog.types.js +0 -1
  50. package/dist/nodes/board/board.d.ts +0 -59
  51. package/dist/nodes/board/board.js +0 -528
  52. package/dist/nodes/board/board.types.d.ts +0 -31
  53. package/dist/nodes/board/board.types.js +0 -1
  54. package/migrations/0013_agents_in_the_tree.sql +0 -76
  55. package/migrations/0014_agent_applications.sql +0 -25
  56. package/migrations/0015_tools_delegated_from_a_connection.sql +0 -15
  57. package/migrations/0016_boards_in_the_tree.sql +0 -80
package/dist/mcp/mcp.js CHANGED
@@ -1,5 +1,5 @@
1
- import { AddBoardTaskInput, AppendTableRowsInput, ArchiveFlowInput, ArchiveNodeInput, CancelFlowRunInput, CompleteFlowRunStepInput, ConfigureBoardInput, CreateAgentInput, CreateFlowInput, CreateNodeInput, DefineTableInput, DeleteBoardTaskInput, DeleteTableRowsInput, ExecuteToolInput, GetAgentInput, GetBoardInput, GetFlowInput, GetFlowRunInput, GetFlowVersionInput, GetNodeInput, GetNodeVersionInput, GetTableInput, IntelId, ListAgentsInput, ListFlowRunsInput, ListFlowsInput, ListGrantsInput, ListNodesInput, MoveBoardTaskInput, NodeGraphInput, PauseAgentInput, PreviewFlowPublishInput, PublishFlowInput, RedefineTableInput, RelationGraphInput, ResolveNodeLinksInput, RevokeGrantInput, RotateAgentKeyInput, RunAgentNowInput, SaveAgentDefinitionInput, SaveAttachmentInput, SaveFlowVersionInput, SaveNodeVersionInput, SearchInput, ShareInput, StartFlowRunInput, TestToolInput, UnpublishFlowInput, UpdateBoardTaskInput, UpdateFlowInput, UpdateNodeInput, UpdateTableRowsInput, } from "@anchrd/intel-contract";
2
- import { McpServer, ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
1
+ import { AppendTableRowsInput, ArchiveFlowInput, ArchiveNodeInput, CancelFlowRunInput, CompleteFlowRunStepInput, CreateFlowInput, CreateNodeInput, DefineTableInput, DeleteTableRowsInput, ExecuteToolInput, GetFlowInput, GetFlowRunInput, GetFlowVersionInput, GetNodeInput, GetNodeVersionInput, GetTableInput, IntelId, ListFlowRunsInput, ListFlowsInput, ListGrantsInput, ListNodesInput, NodeGraphInput, PreviewFlowPublishInput, PublishFlowInput, RedefineTableInput, RelationGraphInput, ResolveNodeLinksInput, RevokeGrantInput, SaveAttachmentInput, SaveFlowVersionInput, SaveNodeVersionInput, SearchInput, ShareInput, StartFlowRunInput, TestToolInput, UnpublishFlowInput, UpdateFlowInput, UpdateNodeInput, UpdateTableRowsInput, } from "@anchrd/intel-contract";
2
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
4
4
  import { z } from "zod";
5
5
  import { permits } from "../shared/gate-authorization/gate-authorization.js";
@@ -8,18 +8,18 @@ function text(value) {
8
8
  return { content: [{ type: "text", text: JSON.stringify(value) }], isError: false };
9
9
  }
10
10
  /**
11
- * Registers one tool twice: under its name, and under the `knowledge_*` name it carried before #125.
11
+ * ⚠️ One name per tool, and the second one is gone for good (#149).
12
12
  *
13
- * ⚠️ The alias is not politeness. A tool name lives in someone else's configuration — a Portal
14
- * endpoint, an agent, a saved prompt and renaming it here alone breaks them at the moment we
15
- * deploy rather than at a moment they chose. Both entries share one handler and one schema, so
16
- * there is no second behavior to keep in step; only the name and the first sentence of the
17
- * description differ. The aliases are removed in anchrd/intel#149.
13
+ * Every tool here carried a `knowledge_*` alias beside its name from #125 until today. The alias
14
+ * was never politeness: a tool name lives in somebody else's configuration a portal endpoint, an
15
+ * agent definition, a saved prompt so renaming it here alone breaks them at the moment we deploy
16
+ * rather than at a moment they chose. ADR-0005 §8 therefore kept both, **for a transition**.
17
+ *
18
+ * A transition without an end date is not a transition, it is a second permanent name — and two
19
+ * names for one tool is exactly the friction #125 removed. It ends here. What follows for anybody
20
+ * writing a tool: there is one name, and a rename is a breaking change that needs its own ticket
21
+ * and its own notice, not a quiet second entry.
18
22
  */
19
- function registerWithAlias(server, names, config, handler) {
20
- server.registerTool(names.name, config, handler);
21
- server.registerTool(names.deprecated, { ...config, description: `Deprecated: use ${names.name}. ${config.description}` }, handler);
22
- }
23
23
  // MCP returns resource payloads inline as base64, so an attachment cannot be streamed to the client
24
24
  // and must fit in Worker memory twice over. Refuse oversized attachments instead of losing the
25
25
  // isolate; HTTP still serves them as a stream.
@@ -61,12 +61,6 @@ export async function handleMcp(request, deps) {
61
61
  canReadFlows: permits(deps.authorization, "flows", "read"),
62
62
  canCreateFlows: permits(deps.authorization, "flows", "create"),
63
63
  };
64
- const agentActor = {
65
- id: deps.authorization.identity.id,
66
- email: deps.authorization.identity.email,
67
- isAdmin: deps.authorization.can("intel", "admin"),
68
- canRun: permits(deps.authorization, "agents", "run"),
69
- };
70
64
  const server = new McpServer({ name: "intel", version: "0.1.0" });
71
65
  const EmptyInput = z.strictObject({});
72
66
  // `null` is the root: the whole tree as this caller may read it. The field is required rather
@@ -112,7 +106,7 @@ export async function handleMcp(request, deps) {
112
106
  name: deps.authorization.identity.name ?? null,
113
107
  }));
114
108
  if (permits(deps.authorization, "knowledge", "read")) {
115
- registerWithAlias(server, { name: "node_list", deprecated: "knowledge_list" }, {
109
+ server.registerTool("node_list", {
116
110
  title: "List nodes",
117
111
  description: "List authorized folders, documents, attachments, and tables under one parent.",
118
112
  inputSchema: ListNodesInput,
@@ -124,7 +118,7 @@ export async function handleMcp(request, deps) {
124
118
  openWorldHint: false,
125
119
  },
126
120
  }, async (input) => text(await deps.nodes.list(actor, input)));
127
- registerWithAlias(server, { name: "node_get", deprecated: "knowledge_get" }, {
121
+ server.registerTool("node_get", {
128
122
  title: "Get node",
129
123
  description: "Get one authorized node and its current immutable content version.",
130
124
  inputSchema: GetNodeInput,
@@ -136,7 +130,7 @@ export async function handleMcp(request, deps) {
136
130
  openWorldHint: false,
137
131
  },
138
132
  }, async (input) => text(await deps.nodes.get(actor, input.nodeId)));
139
- registerWithAlias(server, { name: "node_versions_list", deprecated: "knowledge_versions_list" }, {
133
+ server.registerTool("node_versions_list", {
140
134
  title: "List node versions",
141
135
  description: "List immutable versions of one authorized node.",
142
136
  inputSchema: GetNodeInput,
@@ -163,7 +157,7 @@ export async function handleMcp(request, deps) {
163
157
  openWorldHint: false,
164
158
  },
165
159
  }, async (input) => text(await deps.nodes.getVersion(actor, input.nodeId, input.versionId)));
166
- registerWithAlias(server, { name: "node_attachment_get", deprecated: "knowledge_attachment_get" }, {
160
+ server.registerTool("node_attachment_get", {
167
161
  title: "Get node attachment",
168
162
  description: "Get authorized immutable attachment metadata and its explicit MCP resource URI.",
169
163
  inputSchema: GetNodeInput,
@@ -199,7 +193,7 @@ export async function handleMcp(request, deps) {
199
193
  ],
200
194
  };
201
195
  });
202
- registerWithAlias(server, { name: "search", deprecated: "knowledge_search" }, {
196
+ server.registerTool("search", {
203
197
  title: "Search nodes",
204
198
  description: "Search only authorized indexed nodes and return version-pinned citations. Pass scopeId " +
205
199
  "with a folder node id to search that folder and everything beneath it instead of " +
@@ -213,34 +207,7 @@ export async function handleMcp(request, deps) {
213
207
  openWorldHint: false,
214
208
  },
215
209
  }, async (input) => text(await deps.nodes.search(actor, input)));
216
- // ⚠️ Plain `registerTool` and no alias: these tools are new with #139 and never had a
217
- // `knowledge_` name, so inventing one would create a deprecated name that was never used —
218
- // and #149, which removes the aliases, would then have one more thing to remove.
219
- server.registerTool("agent_list", {
220
- title: "List agents",
221
- description: "List the authorized agents, over the whole tree or under one folder. Omit parentId for every agent, pass null for the root level.",
222
- inputSchema: ListAgentsInput,
223
- annotations: {
224
- title: "List agents",
225
- readOnlyHint: true,
226
- destructiveHint: false,
227
- idempotentHint: true,
228
- openWorldHint: false,
229
- },
230
- }, async (input) => text(await deps.nodes.listAgents(actor, input)));
231
- server.registerTool("agent_get", {
232
- title: "Get agent",
233
- description: "Read one authorized agent with its current definition: the nodes it references and their role, its schedules, and its model.",
234
- inputSchema: GetAgentInput,
235
- annotations: {
236
- title: "Get agent",
237
- readOnlyHint: true,
238
- destructiveHint: false,
239
- idempotentHint: true,
240
- openWorldHint: false,
241
- },
242
- }, async (input) => text(await deps.nodes.getAgent(actor, input)));
243
- registerWithAlias(server, { name: "node_table_get", deprecated: "knowledge_table_get" }, {
210
+ server.registerTool("node_table_get", {
244
211
  title: "Get node table",
245
212
  description: "Read one authorized table as column names and rows.",
246
213
  inputSchema: GetTableInput,
@@ -252,22 +219,7 @@ export async function handleMcp(request, deps) {
252
219
  openWorldHint: false,
253
220
  },
254
221
  }, async (input) => text(await deps.nodes.getTable(actor, input.nodeId)));
255
- // A board answers whole: the statuses and every task, in one read. There is no per-task read
256
- // beside it, because a card is small and the interesting question is almost always about
257
- // several of them at once — what is in review, what waits for what (#285).
258
- server.registerTool("board_get", {
259
- title: "Get board",
260
- description: "Read one authorized board: its status list and every task with its full field set. A board that has never been written answers with the default statuses and no tasks.",
261
- inputSchema: GetBoardInput,
262
- annotations: {
263
- title: "Get board",
264
- readOnlyHint: true,
265
- destructiveHint: false,
266
- idempotentHint: true,
267
- openWorldHint: false,
268
- },
269
- }, async (input) => text(await deps.nodes.getBoard(actor, input)));
270
- registerWithAlias(server, { name: "node_links_list", deprecated: "knowledge_links_list" }, {
222
+ server.registerTool("node_links_list", {
271
223
  title: "List node links",
272
224
  description: "List authorized outgoing links and backlinks for one node.",
273
225
  inputSchema: GetNodeInput,
@@ -286,7 +238,7 @@ export async function handleMcp(request, deps) {
286
238
  // ⚠️ A target this caller may not reach, or one that is gone, is simply absent from the answer.
287
239
  // The two are indistinguishable on purpose: telling them apart would confirm that a document
288
240
  // exists somewhere they cannot look.
289
- registerWithAlias(server, { name: "node_links_resolve", deprecated: "knowledge_links_resolve" }, {
241
+ server.registerTool("node_links_resolve", {
290
242
  title: "Resolve node links",
291
243
  description: "Resolve document link targets to the titles this caller is authorized to see. Targets that are unreachable or deleted are absent from the result.",
292
244
  inputSchema: ResolveNodeLinksInput,
@@ -298,7 +250,7 @@ export async function handleMcp(request, deps) {
298
250
  openWorldHint: false,
299
251
  },
300
252
  }, async (input) => text(await deps.nodes.resolveLinks(actor, input)));
301
- registerWithAlias(server, { name: "node_graph", deprecated: "knowledge_graph" }, {
253
+ server.registerTool("node_graph", {
302
254
  title: "Get node graph",
303
255
  description: "Get the authorized nodes and explicit relationships for discovery.",
304
256
  inputSchema: NodeGraphInput,
@@ -330,7 +282,7 @@ export async function handleMcp(request, deps) {
330
282
  }, async (input) => text(await deps.bundle.manifest(bundleActor, input.nodeId)));
331
283
  }
332
284
  if (deps.authorization.can("intel", "admin")) {
333
- registerWithAlias(server, { name: "node_reindex", deprecated: "knowledge_reindex" }, {
285
+ server.registerTool("node_reindex", {
334
286
  title: "Reindex nodes",
335
287
  description: "Queue every current canonical node version for derived index rebuild.",
336
288
  inputSchema: EmptyInput,
@@ -344,7 +296,7 @@ export async function handleMcp(request, deps) {
344
296
  }, async () => text(await deps.nodes.reindex(actor)));
345
297
  }
346
298
  if (permits(deps.authorization, "knowledge", "create")) {
347
- registerWithAlias(server, { name: "node_create", deprecated: "knowledge_create" }, {
299
+ server.registerTool("node_create", {
348
300
  title: "Create node",
349
301
  description: "Create a governed folder, document, attachment, or table node.",
350
302
  inputSchema: CreateNodeInput,
@@ -356,66 +308,9 @@ export async function handleMcp(request, deps) {
356
308
  openWorldHint: false,
357
309
  },
358
310
  }, async (input) => text(await deps.nodes.create(actor, input)));
359
- /**
360
- * ⚠️ There is no longer ANY tool result in Intel that contains a secret, and that is the whole
361
- * of D29 seen from here. Until #207 this one tool answered with the Application key in plain
362
- * text — a deliberate exception, because the result WAS the credential and a redacted answer
363
- * would have meant "an agent you can create but never run". The exception is gone rather than
364
- * weakened: the key now travels from Gate into the agent runtime over Intel's service binding
365
- * inside this call, so there is nothing left to hand to a model. `agent_create`, `agent_get`,
366
- * `agent_list` and every node read answer with `applicationId` alone, which names the principal
367
- * without authenticating it.
368
- */
369
- server.registerTool("agent_create", {
370
- title: "Create agent",
371
- description: "Create an agent node with its first definition, and the Gate application it runs as. The definition names nodes and their role, schedules and a model — never accounts, secrets or channels. The agent's application key is handed to the agent runtime internally and never appears in this result; the agent can run straight away. A repeated idempotency key returns the existing agent and mints nothing.",
372
- inputSchema: CreateAgentInput,
373
- annotations: {
374
- title: "Create agent",
375
- readOnlyHint: false,
376
- destructiveHint: false,
377
- idempotentHint: true,
378
- // Creating an agent creates a principal in Gate, which is a system outside Intel.
379
- openWorldHint: true,
380
- },
381
- }, async (input) => text(await deps.nodes.createAgent(actor, input, { token: deps.bearer })));
382
311
  }
383
312
  if (permits(deps.authorization, "knowledge", "write")) {
384
- server.registerTool("agent_update", {
385
- title: "Update agent definition",
386
- description: "Append an immutable definition version to an agent using an optimistic base version.",
387
- inputSchema: SaveAgentDefinitionInput,
388
- annotations: {
389
- title: "Update agent definition",
390
- readOnlyHint: false,
391
- destructiveHint: false,
392
- idempotentHint: true,
393
- openWorldHint: false,
394
- },
395
- }, async (input) => text(await deps.nodes.saveAgentDefinition(actor, input, { token: deps.bearer })));
396
- /**
397
- * The MCP half of the "Replace key" action in the agent profile (D29, #207).
398
- *
399
- * ⚠️ `knowledge/write` like `agent_update`, not `agents/run`: which principal an agent acts as
400
- * is a property of the agent, changed by whoever may change the agent. And the result names the
401
- * principal and the moment — never the key, which went to the runtime and nowhere else.
402
- */
403
- server.registerTool("agent_rotate_key", {
404
- title: "Replace an agent's application key",
405
- description: "Issue a new Gate application key for an agent and give it to the agent runtime. The previous key stops working immediately. Use it when a run fails with agent_key_missing, or when a key may have been exposed. The key itself is never returned.",
406
- inputSchema: RotateAgentKeyInput,
407
- annotations: {
408
- title: "Replace an agent's application key",
409
- readOnlyHint: false,
410
- // The old key stops working the moment this runs, and anything still using it breaks.
411
- destructiveHint: true,
412
- // Asking twice issues twice, and the second answer invalidates the first key.
413
- idempotentHint: false,
414
- // The principal lives in Gate, which is a system outside Intel.
415
- openWorldHint: true,
416
- },
417
- }, async (input) => text(await deps.nodes.rotateAgentKey(actor, input, { token: deps.bearer })));
418
- registerWithAlias(server, { name: "node_save", deprecated: "knowledge_save" }, {
313
+ server.registerTool("node_save", {
419
314
  title: "Save node version",
420
315
  description: "Append an immutable content version using an optimistic base version.",
421
316
  inputSchema: SaveNodeVersionInput,
@@ -427,7 +322,7 @@ export async function handleMcp(request, deps) {
427
322
  openWorldHint: false,
428
323
  },
429
324
  }, async (input) => text(await deps.nodes.save(actor, input)));
430
- registerWithAlias(server, { name: "node_attachment_save", deprecated: "knowledge_attachment_save" }, {
325
+ server.registerTool("node_attachment_save", {
431
326
  title: "Save node attachment",
432
327
  description: "Append immutable base64 file bytes to an attachment node.",
433
328
  inputSchema: SaveAttachmentInput,
@@ -459,7 +354,7 @@ export async function handleMcp(request, deps) {
459
354
  zip: decodeZipBase64(input.zipBase64),
460
355
  idempotencyKey: input.idempotencyKey,
461
356
  })));
462
- registerWithAlias(server, { name: "node_table_define", deprecated: "knowledge_table_define" }, {
357
+ server.registerTool("node_table_define", {
463
358
  title: "Define node table columns",
464
359
  description: "Write the column names of an empty table. The header is the contract every append is checked against and cannot be rewritten.",
465
360
  inputSchema: DefineTableInput,
@@ -473,7 +368,7 @@ export async function handleMcp(request, deps) {
473
368
  }, async (input) => text(await deps.nodes.defineTable(actor, input)));
474
369
  // The tool #40 exists for: an agent collecting findings on a schedule appends them without
475
370
  // reading or resending what is already there, and two agents appending at once lose nothing.
476
- registerWithAlias(server, { name: "node_table_append", deprecated: "knowledge_table_append" }, {
371
+ server.registerTool("node_table_append", {
477
372
  title: "Append node table rows",
478
373
  description: "Append rows to a table without reading or resending its existing content. Each row must have exactly as many cells as the table has columns; a row that does not is rejected and nothing is written.",
479
374
  inputSchema: AppendTableRowsInput,
@@ -528,80 +423,7 @@ export async function handleMcp(request, deps) {
528
423
  openWorldHint: false,
529
424
  },
530
425
  }, async (input) => text(await deps.nodes.redefineTable(actor, input)));
531
- /**
532
- * The board's write surface (#285). No `knowledge_*` alias on any of them — these are new under
533
- * the post-#125 naming.
534
- *
535
- * ⚠️ Not one of them takes a `baseVersionId`, and the descriptions say so. That is the property
536
- * an agent has to be able to rely on: two agents working the same board at the same time touch
537
- * different tasks, and neither is asked to read the whole board first or to retry a conflict
538
- * that says nothing about what they were doing. The server applies each call by task id to the
539
- * board as it stands at that moment.
540
- */
541
- server.registerTool("board_configure", {
542
- title: "Configure board statuses",
543
- description: 'Write the board\'s whole status list, in the order it should be drawn. Adding, renaming and reordering columns are all this call. The "archived" status is part of every board and cannot be removed, and a status that tasks still sit in cannot be removed either. Set "terminal" on a column to say that standing in it means the work is finished — that is what decides whether a task waiting on another is still blocked. Several columns may be terminal; "archived" always is and cannot be set otherwise. Omitting it leaves an ordinary column not terminal.',
544
- inputSchema: ConfigureBoardInput,
545
- annotations: {
546
- title: "Configure board statuses",
547
- readOnlyHint: false,
548
- // Removing a column is possible here, so this is not a purely additive write.
549
- destructiveHint: true,
550
- idempotentHint: true,
551
- openWorldHint: false,
552
- },
553
- }, async (input) => text(await deps.nodes.configureBoard(actor, input)));
554
- server.registerTool("board_task_add", {
555
- title: "Add board task",
556
- description: "Add one task to a board. The server assigns its id and its place in the order; name afterTaskId or beforeTaskId to put it between two cards, or neither to put it last in its column. parentId makes it a subtask of another task on the same board, dependsOn names tasks of the same board it waits for, and references names Intel nodes. dependsOn, labels and references are sets: naming the same task, word or node twice is refused rather than quietly folded together. Needs no baseVersionId: tasks are addressed by id, so parallel writers do not collide.",
557
- inputSchema: AddBoardTaskInput,
558
- annotations: {
559
- title: "Add board task",
560
- readOnlyHint: false,
561
- destructiveHint: false,
562
- idempotentHint: true,
563
- openWorldHint: false,
564
- },
565
- }, async (input) => text(await deps.nodes.addBoardTask(actor, input)));
566
- server.registerTool("board_task_update", {
567
- title: "Update board task",
568
- description: "Change what a task says: title, assignee, labels, dates, dependencies, description or references. Where a task SITS — its status, its parent, its order — is board_task_move instead. Every named field replaces its current value whole; fields that are not named stay as they are. dependsOn, labels and references are sets: appending to a list this task already holds is refused rather than quietly folded together, so read the current value before you write it back.",
569
- inputSchema: UpdateBoardTaskInput,
570
- annotations: {
571
- title: "Update board task",
572
- readOnlyHint: false,
573
- // Every named field replaces what stood there; the version history keeps it, the task
574
- // does not.
575
- destructiveHint: true,
576
- idempotentHint: true,
577
- openWorldHint: false,
578
- },
579
- }, async (input) => text(await deps.nodes.updateBoardTask(actor, input)));
580
- server.registerTool("board_task_move", {
581
- title: "Move board task",
582
- description: 'Move one task: into another status, under another parent, or between two neighbours. Archiving a task is this call with status "archived" — there is no separate verb, and nothing is deleted by it. Two moves on two different tasks both go through.',
583
- inputSchema: MoveBoardTaskInput,
584
- annotations: {
585
- title: "Move board task",
586
- readOnlyHint: false,
587
- destructiveHint: false,
588
- idempotentHint: true,
589
- openWorldHint: false,
590
- },
591
- }, async (input) => text(await deps.nodes.moveBoardTask(actor, input)));
592
- server.registerTool("board_task_delete", {
593
- title: "Delete board task",
594
- description: 'Remove a task AND every subtask under it, recursively. The answer counts everything that went, and dependsOn entries in the remaining tasks that pointed at a deleted one are removed with it. To take a task off the board without losing it, move it to the "archived" status instead.',
595
- inputSchema: DeleteBoardTaskInput,
596
- annotations: {
597
- title: "Delete board task",
598
- readOnlyHint: false,
599
- destructiveHint: true,
600
- idempotentHint: true,
601
- openWorldHint: false,
602
- },
603
- }, async (input) => text(await deps.nodes.deleteBoardTask(actor, input)));
604
- registerWithAlias(server, { name: "node_update", deprecated: "knowledge_update" }, {
426
+ server.registerTool("node_update", {
605
427
  title: "Update node",
606
428
  description: "Rename, move, or describe a node.",
607
429
  inputSchema: UpdateNodeInput,
@@ -613,7 +435,7 @@ export async function handleMcp(request, deps) {
613
435
  openWorldHint: false,
614
436
  },
615
437
  }, async (input) => text(await deps.nodes.update(actor, input)));
616
- registerWithAlias(server, { name: "node_archive", deprecated: "knowledge_archive" }, {
438
+ server.registerTool("node_archive", {
617
439
  title: "Archive node",
618
440
  description: "Archive or restore one node using optimistic concurrency. Archiving an agent also switches off its Gate application; restoring switches it back on.",
619
441
  inputSchema: ArchiveNodeInput,
@@ -632,7 +454,7 @@ export async function handleMcp(request, deps) {
632
454
  async (input) => text(await deps.nodes.archive(actor, input, { token: deps.bearer })));
633
455
  }
634
456
  if (permits(deps.authorization, "knowledge", "share")) {
635
- registerWithAlias(server, { name: "node_shares_list", deprecated: "knowledge_shares_list" }, {
457
+ server.registerTool("node_shares_list", {
636
458
  title: "List node shares",
637
459
  description: "List direct grants for nodes the caller is allowed to manage.",
638
460
  inputSchema: ListGrantsInput,
@@ -644,7 +466,7 @@ export async function handleMcp(request, deps) {
644
466
  openWorldHint: false,
645
467
  },
646
468
  }, async (input) => text(await deps.nodes.listGrants(actor, input.resourceId)));
647
- registerWithAlias(server, { name: "node_share", deprecated: "knowledge_share" }, {
469
+ server.registerTool("node_share", {
648
470
  title: "Share node",
649
471
  description: "Grant inherited node access to a Gate user, verified email, or the organization.",
650
472
  inputSchema: ShareInput,
@@ -656,7 +478,7 @@ export async function handleMcp(request, deps) {
656
478
  openWorldHint: false,
657
479
  },
658
480
  }, async (input) => text(await deps.nodes.share(actor, input)));
659
- registerWithAlias(server, { name: "node_revoke_share", deprecated: "knowledge_revoke_share" }, {
481
+ server.registerTool("node_revoke_share", {
660
482
  title: "Revoke node share",
661
483
  description: "Revoke one direct node grant by ID.",
662
484
  inputSchema: RevokeGrantInput,
@@ -951,101 +773,6 @@ export async function handleMcp(request, deps) {
951
773
  },
952
774
  }, async (input) => text(await deps.flows.cancelRun(flowActor, input)));
953
775
  }
954
- /**
955
- * Driving an agent, over the same door the screen uses.
956
- *
957
- * ⚠️ The three below are the MCP half of the two head actions on the agent page — the repository's
958
- * "one business layer, three thin surfaces" rule, kept in the same delivery. They call the runtime
959
- * through `deps.agents`, which resolves the resource ACL and forwards the CALLER's bearer; a tool
960
- * that reached the runtime with anything else would be Intel acting as somebody it is not.
961
- */
962
- if (permits(deps.authorization, "agents", "run")) {
963
- server.registerTool("agent_pause", {
964
- title: "Pause agent",
965
- description: "Stop an agent from acting on its own: no schedule fires, no task and no mail is taken. Its chat keeps answering, and nothing is queued up for later.",
966
- inputSchema: PauseAgentInput,
967
- annotations: {
968
- title: "Pause agent",
969
- readOnlyHint: false,
970
- // Nothing is deleted, and yet work that would have happened does not — a client deciding
971
- // whether to ask first has to be told that.
972
- destructiveHint: true,
973
- idempotentHint: true,
974
- openWorldHint: false,
975
- },
976
- }, async (input) => text(await deps.agents.request({
977
- actor: agentActor,
978
- token: deps.bearer,
979
- agentId: input.nodeId,
980
- method: "POST",
981
- path: "/pause",
982
- body: "{}",
983
- })));
984
- server.registerTool("agent_resume", {
985
- title: "Resume agent",
986
- description: "Let a paused agent act again. Its schedules are armed from the definition as it stands now; nothing that was due while it stood still is caught up.",
987
- inputSchema: PauseAgentInput,
988
- annotations: {
989
- title: "Resume agent",
990
- readOnlyHint: false,
991
- destructiveHint: false,
992
- idempotentHint: true,
993
- openWorldHint: false,
994
- },
995
- }, async (input) => text(await deps.agents.request({
996
- actor: agentActor,
997
- token: deps.bearer,
998
- agentId: input.nodeId,
999
- method: "POST",
1000
- path: "/resume",
1001
- body: "{}",
1002
- })));
1003
- /**
1004
- * ⚠️ The MCP half of the profile's cost figures, in the same delivery slice — the repository's
1005
- * "every user-visible capability has an Intel MCP equivalent" rule.
1006
- *
1007
- * ⚠️ Under `agents/run` beside pause and resume, not under `knowledge/read`. What an agent
1008
- * spends is as sensitive as what it did, and this answer stands beside the run list rather than
1009
- * beside the node.
1010
- *
1011
- * ⚠️ `status` travels with the numbers and is not flattened away. A client that read an empty
1012
- * `runs` list as "this agent is free" would be reporting a Cloudflare outage as a saving.
1013
- */
1014
- server.registerTool("agent_costs", {
1015
- title: "Agent costs",
1016
- description: "What one agent has actually cost, read from Cloudflare's AI Gateway log: per run, and as a total over the last 7 and 30 days. `status` says whether the numbers were read at all — `not_configured` and `unreadable` both mean no figures, never zero cost. `partial` means the read hit its page limit, so the totals are a floor.",
1017
- inputSchema: PauseAgentInput,
1018
- annotations: {
1019
- title: "Agent costs",
1020
- readOnlyHint: true,
1021
- destructiveHint: false,
1022
- idempotentHint: true,
1023
- openWorldHint: false,
1024
- },
1025
- }, async (input) => text(await deps.agentCosts.read(agentActor, input.nodeId)));
1026
- server.registerTool("agent_run_now", {
1027
- title: "Run agent now",
1028
- description: "Fire one of the agent's own schedules straight away, against the document or flow it names. The run appears in the agent's run list; a target the agent does not schedule is refused.",
1029
- inputSchema: RunAgentNowInput,
1030
- annotations: {
1031
- title: "Run agent now",
1032
- readOnlyHint: false,
1033
- destructiveHint: false,
1034
- // Every run is a new run, so asking twice runs it twice.
1035
- idempotentHint: false,
1036
- // The agent may reach whatever its own definition grants it, so what a run touches is not
1037
- // knowable from this schema.
1038
- openWorldHint: true,
1039
- },
1040
- }, async (input) => text(await deps.agents.request({
1041
- actor: agentActor,
1042
- token: deps.bearer,
1043
- agentId: input.nodeId,
1044
- method: "POST",
1045
- path: "/run",
1046
- body: JSON.stringify({ target: input.target }),
1047
- })));
1048
- }
1049
776
  if (permits(deps.authorization, "tools", "read")) {
1050
777
  server.registerTool("tools_catalog_list", {
1051
778
  title: "List tools",
@@ -1059,9 +786,7 @@ export async function handleMcp(request, deps) {
1059
786
  openWorldHint: false,
1060
787
  },
1061
788
  }, async () => text(await deps.tools.catalog(toolActor)));
1062
- // The MCP twin of the picker in the agent profile (D30): whole servers, so an agent can be
1063
- // given tools from a client instead of from the screen. Assigning and removing is
1064
- // `agent_update` — a delegation is a field of the definition, not a surface of its own.
789
+ // The servers behind the same live list (D30), as the portal itself names them.
1065
790
  server.registerTool("tools_servers_list", {
1066
791
  title: "List tool servers",
1067
792
  description: "List the MCP servers the calling user reaches through the portal, as the portal itself names them. These are the handles an agent definition may delegate.",
@@ -1,6 +1,4 @@
1
1
  import type { Authorized } from "@anchrd/gate-sdk";
2
- import type { AgentCostsService } from "../agent-costs/agent-costs.types.js";
3
- import type { AgentRuntimeService } from "../agent-runtime/agent-runtime.types.js";
4
2
  import type { BundleService } from "../bundle/bundle.types.js";
5
3
  import type { FlowService } from "../flows/flows.types.js";
6
4
  import type { NodeService } from "../nodes/nodes.types.js";
@@ -8,15 +6,12 @@ import type { ToolService } from "../tools/tools.types.js";
8
6
  export interface McpDeps {
9
7
  authorization: Authorized;
10
8
  /**
11
- * The bearer this call was authorized with, forwarded to the agent runtime so a run is driven by
12
- * the caller and not by Intel. Nothing else reads it, and nothing writes it anywhere.
9
+ * The bearer this call was authorized with, passed on where Intel has to speak to Gate as the
10
+ * person asking rather than as itself. Nothing else reads it, and nothing writes it anywhere.
13
11
  */
14
12
  bearer: string;
15
13
  nodes: NodeService;
16
14
  flows: FlowService;
17
15
  tools: ToolService;
18
16
  bundle: BundleService;
19
- agents: AgentRuntimeService;
20
- /** What an agent has cost, out of the AI Gateway log — the MCP half of the profile's figures (#251). */
21
- agentCosts: AgentCostsService;
22
17
  }
@@ -9,14 +9,12 @@
9
9
  * newer editor wrote; a strict parse would turn "I do not recognise this block" into "this document
10
10
  * cannot be saved". What it does not recognise contributes no link, which is the safe half.
11
11
  *
12
- * ⚠️ A board reaches the same link graph, through the same `text` origin (#285). Its links are not
13
- * inline elements but the `references` of its tasks, which is a difference in where they are
14
- * written and not in what they are: somebody put a node id into a body, and the graph has to see it
15
- * from the other side. Reading it here rather than in the board service is what keeps ONE answer to
16
- * "what does this body point at" — the writer (`reconcileTextLinks`) asks this function and does
17
- * not care which kind it is holding.
12
+ * ⚠️ ONE answer to "what does this body point at", whatever kind holds the body: the writer
13
+ * (`reconcileTextLinks`) asks this function and does not care. Until #390 a board answered here
14
+ * too, through the `references` of its tasks a difference in where the ids were written and not
15
+ * in what they were.
18
16
  *
19
- * Content that is neither has no links at all: an attachment, a CSV table or plain markdown carries
20
- * nothing to read.
17
+ * Content that is not a BlockNote document has no links at all: an attachment, a CSV table or plain
18
+ * markdown carries nothing to read.
21
19
  */
22
20
  export declare function documentLinkTargets(mediaType: string, content: string): string[];
@@ -1,5 +1,4 @@
1
- import { BlockNoteDocument, BlockNoteMediaType, BoardDocument, BoardMediaType, DocumentLinkInlineType, } from "@anchrd/intel-contract";
2
- import { upgradeStoredBoard } from "../board/board.js";
1
+ import { BlockNoteDocument, BlockNoteMediaType, DocumentLinkInlineType, } from "@anchrd/intel-contract";
3
2
  function isRecord(value) {
4
3
  return typeof value === "object" && value !== null;
5
4
  }
@@ -31,18 +30,16 @@ function collect(value, found) {
31
30
  * newer editor wrote; a strict parse would turn "I do not recognise this block" into "this document
32
31
  * cannot be saved". What it does not recognise contributes no link, which is the safe half.
33
32
  *
34
- * ⚠️ A board reaches the same link graph, through the same `text` origin (#285). Its links are not
35
- * inline elements but the `references` of its tasks, which is a difference in where they are
36
- * written and not in what they are: somebody put a node id into a body, and the graph has to see it
37
- * from the other side. Reading it here rather than in the board service is what keeps ONE answer to
38
- * "what does this body point at" — the writer (`reconcileTextLinks`) asks this function and does
39
- * not care which kind it is holding.
33
+ * ⚠️ ONE answer to "what does this body point at", whatever kind holds the body: the writer
34
+ * (`reconcileTextLinks`) asks this function and does not care. Until #390 a board answered here
35
+ * too, through the `references` of its tasks a difference in where the ids were written and not
36
+ * in what they were.
40
37
  *
41
- * Content that is neither has no links at all: an attachment, a CSV table or plain markdown carries
42
- * nothing to read.
38
+ * Content that is not a BlockNote document has no links at all: an attachment, a CSV table or plain
39
+ * markdown carries nothing to read.
43
40
  */
44
41
  export function documentLinkTargets(mediaType, content) {
45
- if (mediaType !== BlockNoteMediaType && mediaType !== BoardMediaType)
42
+ if (mediaType !== BlockNoteMediaType)
46
43
  return [];
47
44
  let parsed;
48
45
  try {
@@ -52,26 +49,6 @@ export function documentLinkTargets(mediaType, content) {
52
49
  return [];
53
50
  }
54
51
  const found = new Set();
55
- if (mediaType === BoardMediaType) {
56
- /**
57
- * ⚠️ Upgraded first, like `parseStoredBoard` and `indexing.ts` — this is the FOURTH place a
58
- * board body is parsed, and the only one that cannot complain (anchrd/intel#321).
59
- *
60
- * A board it fails to parse simply has no links, which reads exactly like a board that points
61
- * at nothing — and the reconciliation this feeds removes the links a body no longer names, so
62
- * an unparsed old board would quietly leave the link graph. Every caller hands it a body written
63
- * moments earlier today, so it has never met one; the upgrade is what keeps that from being the
64
- * only reason.
65
- */
66
- const board = BoardDocument.safeParse(upgradeStoredBoard(parsed));
67
- if (!board.success)
68
- return [];
69
- for (const task of board.data.tasks) {
70
- for (const reference of task.references)
71
- found.add(reference);
72
- }
73
- return [...found];
74
- }
75
52
  const document = BlockNoteDocument.safeParse(parsed);
76
53
  if (!document.success)
77
54
  return [];