@anchrd/intel-api 0.6.7 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -3
- package/dist/adapters/cloudflare/cloudflare.js +102 -37
- package/dist/adapters/cloudflare/cloudflare.types.d.ts +20 -0
- package/dist/adapters/content/content.d.ts +1 -1
- package/dist/adapters/db/db-flows.js +148 -20
- package/dist/adapters/db/db-grants.d.ts +13 -2
- package/dist/adapters/db/db-grants.js +25 -8
- package/dist/adapters/db/db-indexing.d.ts +2 -2
- package/dist/adapters/db/db-indexing.js +26 -19
- package/dist/adapters/db/db.d.ts +3 -3
- package/dist/adapters/db/db.js +442 -118
- package/dist/adapters/gate-applications/gate-applications.d.ts +23 -0
- package/dist/adapters/gate-applications/gate-applications.js +88 -0
- package/dist/adapters/index-queue/index-queue.d.ts +1 -1
- package/dist/adapters/index-queue/index-queue.js +2 -2
- package/dist/adapters/semantic-index/semantic-index.types.d.ts +2 -2
- package/dist/adapters/tool-delegation/tool-delegation.d.ts +22 -0
- package/dist/adapters/tool-delegation/tool-delegation.js +90 -0
- package/dist/agent-runtime/agent-runtime.d.ts +16 -0
- package/dist/agent-runtime/agent-runtime.js +150 -0
- package/dist/agent-runtime/agent-runtime.types.d.ts +122 -0
- package/dist/bundle/bundle.d.ts +4 -0
- package/dist/bundle/bundle.js +1048 -0
- package/dist/bundle/bundle.types.d.ts +33 -0
- package/dist/bundle/bundle.types.js +1 -0
- package/dist/cli/cli.js +10 -1
- package/dist/flows/flows.d.ts +8 -8
- package/dist/flows/flows.js +158 -42
- package/dist/flows/flows.types.d.ts +40 -7
- package/dist/http/http.d.ts +1 -0
- package/dist/http/http.js +348 -61
- package/dist/http/http.types.d.ts +6 -2
- package/dist/indexing/indexing.js +14 -2
- package/dist/indexing/indexing.types.d.ts +2 -2
- package/dist/intel/intel.js +12 -3
- package/dist/intel/intel.types.d.ts +6 -2
- package/dist/mcp/mcp.js +519 -124
- package/dist/mcp/mcp.types.d.ts +11 -2
- package/dist/nodes/nodes.d.ts +2 -0
- package/dist/nodes/nodes.js +1466 -0
- package/dist/nodes/nodes.types.d.ts +402 -0
- package/dist/nodes/nodes.types.js +1 -0
- package/dist/tools/tool-servers/tool-servers.d.ts +46 -0
- package/dist/tools/tool-servers/tool-servers.js +114 -0
- package/dist/tools/tools.js +190 -31
- package/dist/tools/tools.types.d.ts +23 -1
- package/migrations/0011_one_name_for_the_tree.sql +53 -0
- package/migrations/0012_table_snapshots.sql +29 -0
- package/migrations/0013_agents_in_the_tree.sql +76 -0
- package/migrations/0014_agent_applications.sql +25 -0
- package/migrations/0015_tools_delegated_from_a_connection.sql +15 -0
- package/package.json +3 -2
- package/dist/knowledge/knowledge.d.ts +0 -2
- package/dist/knowledge/knowledge.js +0 -761
- package/dist/knowledge/knowledge.types.d.ts +0 -198
- /package/dist/{knowledge/knowledge.types.js → agent-runtime/agent-runtime.types.js} +0 -0
- /package/dist/{knowledge → nodes}/document-links/document-links.d.ts +0 -0
- /package/dist/{knowledge → nodes}/document-links/document-links.js +0 -0
package/dist/mcp/mcp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1
|
+
import { AppendTableRowsInput, ArchiveFlowInput, ArchiveNodeInput, CancelFlowRunInput, CompleteFlowRunStepInput, CreateAgentInput, CreateFlowInput, CreateNodeInput, DefineTableInput, DeleteTableRowsInput, ExecuteToolInput, GetAgentInput, GetFlowInput, GetFlowRunInput, GetFlowVersionInput, GetNodeInput, GetNodeVersionInput, GetTableInput, IntelId, ListAgentsInput, ListFlowRunsInput, ListFlowsInput, ListGrantsInput, ListNodesInput, NodeGraphInput, PauseAgentInput, PreviewFlowPublishInput, PublishFlowInput, RedefineTableInput, RelationGraphInput, ResolveNodeLinksInput, RevokeGrantInput, RotateAgentKeyInput, RunAgentNowInput, SaveAgentDefinitionInput, 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";
|
|
@@ -7,6 +7,19 @@ import { IntelError } from "../shared/intel-error/intel-error.js";
|
|
|
7
7
|
function text(value) {
|
|
8
8
|
return { content: [{ type: "text", text: JSON.stringify(value) }], isError: false };
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Registers one tool twice: under its name, and under the `knowledge_*` name it carried before #125.
|
|
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.
|
|
18
|
+
*/
|
|
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
|
+
}
|
|
10
23
|
// MCP returns resource payloads inline as base64, so an attachment cannot be streamed to the client
|
|
11
24
|
// and must fit in Worker memory twice over. Refuse oversized attachments instead of losing the
|
|
12
25
|
// isolate; HTTP still serves them as a stream.
|
|
@@ -41,59 +54,129 @@ export async function handleMcp(request, deps) {
|
|
|
41
54
|
canRun: permits(deps.authorization, "flows", "run"),
|
|
42
55
|
isAdmin: deps.authorization.can("intel", "admin"),
|
|
43
56
|
};
|
|
57
|
+
const bundleActor = {
|
|
58
|
+
id: deps.authorization.identity.id,
|
|
59
|
+
email: deps.authorization.identity.email,
|
|
60
|
+
isAdmin: deps.authorization.can("intel", "admin"),
|
|
61
|
+
canReadFlows: permits(deps.authorization, "flows", "read"),
|
|
62
|
+
canCreateFlows: permits(deps.authorization, "flows", "create"),
|
|
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
|
+
};
|
|
44
70
|
const server = new McpServer({ name: "intel", version: "0.1.0" });
|
|
45
71
|
const EmptyInput = z.strictObject({});
|
|
72
|
+
// `null` is the root: the whole tree as this caller may read it. The field is required rather
|
|
73
|
+
// than defaulted so "everything" is always said, never fallen into.
|
|
74
|
+
const ExportManifestInput = z.strictObject({ nodeId: IntelId.nullable() });
|
|
75
|
+
// The zip travels inline as base64, which is MCP's one way of carrying bytes. ~14M characters is
|
|
76
|
+
// ~10 MB of zip — the same order as the attachment inline limit, and for the same isolate-memory
|
|
77
|
+
// reason. Bigger bundles take the HTTP door, which streams.
|
|
78
|
+
const ImportBundleInput = z.strictObject({
|
|
79
|
+
nodeId: IntelId.nullable(),
|
|
80
|
+
zipBase64: z.string().min(1).max(14_000_000),
|
|
81
|
+
idempotencyKey: z.string().min(8).max(200),
|
|
82
|
+
});
|
|
83
|
+
function decodeZipBase64(value) {
|
|
84
|
+
if (value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) {
|
|
85
|
+
throw new IntelError(400, "import_invalid_bundle", "zipBase64 is not valid base64");
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return Uint8Array.from(atob(value), (character) => character.charCodeAt(0));
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
throw new IntelError(400, "import_invalid_bundle", "zipBase64 is not valid base64");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// The caller's own identity (#148): the MCP twin of `GET /session`, and like it behind no
|
|
95
|
+
// capability at all — the answer is the token's own subject, and an agent that may connect may
|
|
96
|
+
// ask who it is connected as. Deliberately id, email, and name and nothing else: no
|
|
97
|
+
// capabilities, no token, nothing a caller could mistake for a permission.
|
|
98
|
+
server.registerTool("session_get", {
|
|
99
|
+
title: "Get session identity",
|
|
100
|
+
description: "Get the identity this connection acts as: user id, email, and display name. The MCP counterpart of GET /session; it answers from the token's own subject and never lists permissions.",
|
|
101
|
+
inputSchema: EmptyInput,
|
|
102
|
+
annotations: {
|
|
103
|
+
title: "Get session identity",
|
|
104
|
+
readOnlyHint: true,
|
|
105
|
+
destructiveHint: false,
|
|
106
|
+
idempotentHint: true,
|
|
107
|
+
openWorldHint: false,
|
|
108
|
+
},
|
|
109
|
+
}, async () => text({
|
|
110
|
+
id: deps.authorization.identity.id,
|
|
111
|
+
email: deps.authorization.identity.email,
|
|
112
|
+
name: deps.authorization.identity.name ?? null,
|
|
113
|
+
}));
|
|
46
114
|
if (permits(deps.authorization, "knowledge", "read")) {
|
|
47
|
-
server
|
|
48
|
-
title: "List
|
|
115
|
+
registerWithAlias(server, { name: "node_list", deprecated: "knowledge_list" }, {
|
|
116
|
+
title: "List nodes",
|
|
49
117
|
description: "List authorized folders, documents, attachments, and tables under one parent.",
|
|
50
|
-
inputSchema:
|
|
118
|
+
inputSchema: ListNodesInput,
|
|
51
119
|
annotations: {
|
|
52
|
-
title: "List
|
|
120
|
+
title: "List nodes",
|
|
53
121
|
readOnlyHint: true,
|
|
54
122
|
destructiveHint: false,
|
|
55
123
|
idempotentHint: true,
|
|
56
124
|
openWorldHint: false,
|
|
57
125
|
},
|
|
58
|
-
}, async (input) => text(await deps.
|
|
59
|
-
server
|
|
60
|
-
title: "Get
|
|
61
|
-
description: "Get one authorized
|
|
62
|
-
inputSchema:
|
|
126
|
+
}, async (input) => text(await deps.nodes.list(actor, input)));
|
|
127
|
+
registerWithAlias(server, { name: "node_get", deprecated: "knowledge_get" }, {
|
|
128
|
+
title: "Get node",
|
|
129
|
+
description: "Get one authorized node and its current immutable content version.",
|
|
130
|
+
inputSchema: GetNodeInput,
|
|
63
131
|
annotations: {
|
|
64
|
-
title: "Get
|
|
132
|
+
title: "Get node",
|
|
65
133
|
readOnlyHint: true,
|
|
66
134
|
destructiveHint: false,
|
|
67
135
|
idempotentHint: true,
|
|
68
136
|
openWorldHint: false,
|
|
69
137
|
},
|
|
70
|
-
}, async (input) => text(await deps.
|
|
71
|
-
server
|
|
72
|
-
title: "List
|
|
73
|
-
description: "List immutable versions of one authorized
|
|
74
|
-
inputSchema:
|
|
138
|
+
}, async (input) => text(await deps.nodes.get(actor, input.nodeId)));
|
|
139
|
+
registerWithAlias(server, { name: "node_versions_list", deprecated: "knowledge_versions_list" }, {
|
|
140
|
+
title: "List node versions",
|
|
141
|
+
description: "List immutable versions of one authorized node.",
|
|
142
|
+
inputSchema: GetNodeInput,
|
|
75
143
|
annotations: {
|
|
76
|
-
title: "List
|
|
144
|
+
title: "List node versions",
|
|
77
145
|
readOnlyHint: true,
|
|
78
146
|
destructiveHint: false,
|
|
79
147
|
idempotentHint: true,
|
|
80
148
|
openWorldHint: false,
|
|
81
149
|
},
|
|
82
|
-
}, async (input) => text(await deps.
|
|
83
|
-
|
|
84
|
-
|
|
150
|
+
}, async (input) => text(await deps.nodes.listVersions(actor, input.nodeId)));
|
|
151
|
+
// The content behind one row of the version list (#147): what a version-pinned search citation
|
|
152
|
+
// points at. No `knowledge_*` alias — the tool is new under the post-#125 naming and never
|
|
153
|
+
// carried the old prefix.
|
|
154
|
+
server.registerTool("node_version_get", {
|
|
155
|
+
title: "Get node version content",
|
|
156
|
+
description: "Read one pinned immutable version of an authorized node, content included — the version a search citation names, whether or not it is current. For an attachment the answer is its metadata without the bytes. A version that does not belong to the node is not found.",
|
|
157
|
+
inputSchema: GetNodeVersionInput,
|
|
158
|
+
annotations: {
|
|
159
|
+
title: "Get node version content",
|
|
160
|
+
readOnlyHint: true,
|
|
161
|
+
destructiveHint: false,
|
|
162
|
+
idempotentHint: true,
|
|
163
|
+
openWorldHint: false,
|
|
164
|
+
},
|
|
165
|
+
}, async (input) => text(await deps.nodes.getVersion(actor, input.nodeId, input.versionId)));
|
|
166
|
+
registerWithAlias(server, { name: "node_attachment_get", deprecated: "knowledge_attachment_get" }, {
|
|
167
|
+
title: "Get node attachment",
|
|
85
168
|
description: "Get authorized immutable attachment metadata and its explicit MCP resource URI.",
|
|
86
|
-
inputSchema:
|
|
169
|
+
inputSchema: GetNodeInput,
|
|
87
170
|
annotations: {
|
|
88
|
-
title: "Get
|
|
171
|
+
title: "Get node attachment",
|
|
89
172
|
readOnlyHint: true,
|
|
90
173
|
destructiveHint: false,
|
|
91
174
|
idempotentHint: true,
|
|
92
175
|
openWorldHint: false,
|
|
93
176
|
},
|
|
94
|
-
}, async (input) => text(await deps.
|
|
95
|
-
server.registerResource("
|
|
96
|
-
const { attachment, body } = await deps.
|
|
177
|
+
}, async (input) => text(await deps.nodes.getAttachment(actor, input.nodeId)));
|
|
178
|
+
server.registerResource("node-attachment", new ResourceTemplate("intel://nodes/{nodeId}/attachment", { list: undefined }), { title: "Intel node attachment" }, async (uri, variables) => {
|
|
179
|
+
const { attachment, body } = await deps.nodes.readAttachment(actor, String(variables.nodeId));
|
|
97
180
|
if (attachment.version.size > AttachmentInlineLimit) {
|
|
98
181
|
await body.cancel();
|
|
99
182
|
throw new IntelError(413, "attachment_too_large", "Attachment is too large to inline; read it over HTTP instead");
|
|
@@ -108,155 +191,279 @@ export async function handleMcp(request, deps) {
|
|
|
108
191
|
],
|
|
109
192
|
};
|
|
110
193
|
});
|
|
111
|
-
server.registerResource("
|
|
112
|
-
const document = await deps.
|
|
194
|
+
server.registerResource("node-document", new ResourceTemplate("intel://nodes/{nodeId}", { list: undefined }), { title: "Intel node document", mimeType: "application/json" }, async (uri, variables) => {
|
|
195
|
+
const document = await deps.nodes.get(actor, String(variables.nodeId));
|
|
113
196
|
return {
|
|
114
197
|
contents: [
|
|
115
198
|
{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(document) },
|
|
116
199
|
],
|
|
117
200
|
};
|
|
118
201
|
});
|
|
119
|
-
server
|
|
120
|
-
title: "Search
|
|
121
|
-
description: "Search only authorized indexed
|
|
122
|
-
|
|
202
|
+
registerWithAlias(server, { name: "search", deprecated: "knowledge_search" }, {
|
|
203
|
+
title: "Search nodes",
|
|
204
|
+
description: "Search only authorized indexed nodes and return version-pinned citations. Pass scopeId " +
|
|
205
|
+
"with a folder node id to search that folder and everything beneath it instead of " +
|
|
206
|
+
"everything readable.",
|
|
207
|
+
inputSchema: SearchInput,
|
|
208
|
+
annotations: {
|
|
209
|
+
title: "Search nodes",
|
|
210
|
+
readOnlyHint: true,
|
|
211
|
+
destructiveHint: false,
|
|
212
|
+
idempotentHint: true,
|
|
213
|
+
openWorldHint: false,
|
|
214
|
+
},
|
|
215
|
+
}, 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,
|
|
123
235
|
annotations: {
|
|
124
|
-
title: "
|
|
236
|
+
title: "Get agent",
|
|
125
237
|
readOnlyHint: true,
|
|
126
238
|
destructiveHint: false,
|
|
127
239
|
idempotentHint: true,
|
|
128
240
|
openWorldHint: false,
|
|
129
241
|
},
|
|
130
|
-
}, async (input) => text(await deps.
|
|
131
|
-
server
|
|
132
|
-
title: "Get
|
|
242
|
+
}, async (input) => text(await deps.nodes.getAgent(actor, input)));
|
|
243
|
+
registerWithAlias(server, { name: "node_table_get", deprecated: "knowledge_table_get" }, {
|
|
244
|
+
title: "Get node table",
|
|
133
245
|
description: "Read one authorized table as column names and rows.",
|
|
134
|
-
inputSchema:
|
|
246
|
+
inputSchema: GetTableInput,
|
|
135
247
|
annotations: {
|
|
136
|
-
title: "Get
|
|
248
|
+
title: "Get node table",
|
|
137
249
|
readOnlyHint: true,
|
|
138
250
|
destructiveHint: false,
|
|
139
251
|
idempotentHint: true,
|
|
140
252
|
openWorldHint: false,
|
|
141
253
|
},
|
|
142
|
-
}, async (input) => text(await deps.
|
|
143
|
-
server
|
|
144
|
-
title: "List
|
|
145
|
-
description: "List authorized outgoing links and backlinks for one
|
|
146
|
-
inputSchema:
|
|
254
|
+
}, async (input) => text(await deps.nodes.getTable(actor, input.nodeId)));
|
|
255
|
+
registerWithAlias(server, { name: "node_links_list", deprecated: "knowledge_links_list" }, {
|
|
256
|
+
title: "List node links",
|
|
257
|
+
description: "List authorized outgoing links and backlinks for one node.",
|
|
258
|
+
inputSchema: GetNodeInput,
|
|
147
259
|
annotations: {
|
|
148
|
-
title: "List
|
|
260
|
+
title: "List node links",
|
|
149
261
|
readOnlyHint: true,
|
|
150
262
|
destructiveHint: false,
|
|
151
263
|
idempotentHint: true,
|
|
152
264
|
openWorldHint: false,
|
|
153
265
|
},
|
|
154
|
-
}, async (input) => text(await deps.
|
|
266
|
+
}, async (input) => text(await deps.nodes.listLinks(actor, input.nodeId)));
|
|
155
267
|
// The reader's half of a document link (#41): the titles of the linked documents this caller
|
|
156
268
|
// may see. There is no tool to create or delete a link — a relationship is written in the text
|
|
157
|
-
// and `
|
|
269
|
+
// and `node_save` is what records it, so there is only one way to make one.
|
|
158
270
|
//
|
|
159
271
|
// ⚠️ A target this caller may not reach, or one that is gone, is simply absent from the answer.
|
|
160
272
|
// The two are indistinguishable on purpose: telling them apart would confirm that a document
|
|
161
273
|
// exists somewhere they cannot look.
|
|
162
|
-
server
|
|
163
|
-
title: "Resolve
|
|
274
|
+
registerWithAlias(server, { name: "node_links_resolve", deprecated: "knowledge_links_resolve" }, {
|
|
275
|
+
title: "Resolve node links",
|
|
164
276
|
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.",
|
|
165
|
-
inputSchema:
|
|
277
|
+
inputSchema: ResolveNodeLinksInput,
|
|
278
|
+
annotations: {
|
|
279
|
+
title: "Resolve node links",
|
|
280
|
+
readOnlyHint: true,
|
|
281
|
+
destructiveHint: false,
|
|
282
|
+
idempotentHint: true,
|
|
283
|
+
openWorldHint: false,
|
|
284
|
+
},
|
|
285
|
+
}, async (input) => text(await deps.nodes.resolveLinks(actor, input)));
|
|
286
|
+
registerWithAlias(server, { name: "node_graph", deprecated: "knowledge_graph" }, {
|
|
287
|
+
title: "Get node graph",
|
|
288
|
+
description: "Get the authorized nodes and explicit relationships for discovery.",
|
|
289
|
+
inputSchema: NodeGraphInput,
|
|
166
290
|
annotations: {
|
|
167
|
-
title: "
|
|
291
|
+
title: "Get node graph",
|
|
168
292
|
readOnlyHint: true,
|
|
169
293
|
destructiveHint: false,
|
|
170
294
|
idempotentHint: true,
|
|
171
295
|
openWorldHint: false,
|
|
172
296
|
},
|
|
173
|
-
}, async (input) => text(await deps.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
297
|
+
}, async (input) => text(await deps.nodes.graph(actor, input)));
|
|
298
|
+
// The export as MCP offers it (#136): the manifest — every readable entry with its bundle path
|
|
299
|
+
// and media type. The contents behind the paths are read one by one through `node_get`,
|
|
300
|
+
// `node_table_get`, and `node_attachment_get`. ⚠️ Deliberately no binary zip over MCP: a
|
|
301
|
+
// resource payload travels inline as base64 and would have to fit in Worker memory twice, the
|
|
302
|
+
// same limit that caps `node_attachment_get` at 10 MB — a whole subtree is the case that limit
|
|
303
|
+
// exists for. HTTP `GET /nodes/:nodeId/export` streams the real archive.
|
|
304
|
+
server.registerTool("node_export_manifest", {
|
|
305
|
+
title: "Get export manifest",
|
|
306
|
+
description: "Plan a bundle export: the manifest of one authorized subtree (or the whole tree when nodeId is null) with each entry's bundle path, kind, and media type. Content bytes are fetched per node; the binary zip is served over HTTP.",
|
|
307
|
+
inputSchema: ExportManifestInput,
|
|
178
308
|
annotations: {
|
|
179
|
-
title: "Get
|
|
309
|
+
title: "Get export manifest",
|
|
180
310
|
readOnlyHint: true,
|
|
181
311
|
destructiveHint: false,
|
|
182
312
|
idempotentHint: true,
|
|
183
313
|
openWorldHint: false,
|
|
184
314
|
},
|
|
185
|
-
}, async (input) => text(await deps.
|
|
315
|
+
}, async (input) => text(await deps.bundle.manifest(bundleActor, input.nodeId)));
|
|
186
316
|
}
|
|
187
317
|
if (deps.authorization.can("intel", "admin")) {
|
|
188
|
-
server
|
|
189
|
-
title: "Reindex
|
|
190
|
-
description: "Queue every current canonical
|
|
318
|
+
registerWithAlias(server, { name: "node_reindex", deprecated: "knowledge_reindex" }, {
|
|
319
|
+
title: "Reindex nodes",
|
|
320
|
+
description: "Queue every current canonical node version for derived index rebuild.",
|
|
191
321
|
inputSchema: EmptyInput,
|
|
192
322
|
annotations: {
|
|
193
|
-
title: "Reindex
|
|
323
|
+
title: "Reindex nodes",
|
|
194
324
|
readOnlyHint: false,
|
|
195
325
|
destructiveHint: false,
|
|
196
326
|
idempotentHint: true,
|
|
197
327
|
openWorldHint: false,
|
|
198
328
|
},
|
|
199
|
-
}, async () => text(await deps.
|
|
329
|
+
}, async () => text(await deps.nodes.reindex(actor)));
|
|
200
330
|
}
|
|
201
331
|
if (permits(deps.authorization, "knowledge", "create")) {
|
|
202
|
-
server
|
|
203
|
-
title: "Create
|
|
332
|
+
registerWithAlias(server, { name: "node_create", deprecated: "knowledge_create" }, {
|
|
333
|
+
title: "Create node",
|
|
204
334
|
description: "Create a governed folder, document, attachment, or table node.",
|
|
205
|
-
inputSchema:
|
|
335
|
+
inputSchema: CreateNodeInput,
|
|
206
336
|
annotations: {
|
|
207
|
-
title: "Create
|
|
337
|
+
title: "Create node",
|
|
208
338
|
readOnlyHint: false,
|
|
209
339
|
destructiveHint: false,
|
|
210
340
|
idempotentHint: true,
|
|
211
341
|
openWorldHint: false,
|
|
212
342
|
},
|
|
213
|
-
}, async (input) => text(await deps.
|
|
343
|
+
}, async (input) => text(await deps.nodes.create(actor, input)));
|
|
344
|
+
/**
|
|
345
|
+
* ⚠️ There is no longer ANY tool result in Intel that contains a secret, and that is the whole
|
|
346
|
+
* of D29 seen from here. Until #207 this one tool answered with the Application key in plain
|
|
347
|
+
* text — a deliberate exception, because the result WAS the credential and a redacted answer
|
|
348
|
+
* would have meant "an agent you can create but never run". The exception is gone rather than
|
|
349
|
+
* weakened: the key now travels from Gate into the agent runtime over Intel's service binding
|
|
350
|
+
* inside this call, so there is nothing left to hand to a model. `agent_create`, `agent_get`,
|
|
351
|
+
* `agent_list` and every node read answer with `applicationId` alone, which names the principal
|
|
352
|
+
* without authenticating it.
|
|
353
|
+
*/
|
|
354
|
+
server.registerTool("agent_create", {
|
|
355
|
+
title: "Create agent",
|
|
356
|
+
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.",
|
|
357
|
+
inputSchema: CreateAgentInput,
|
|
358
|
+
annotations: {
|
|
359
|
+
title: "Create agent",
|
|
360
|
+
readOnlyHint: false,
|
|
361
|
+
destructiveHint: false,
|
|
362
|
+
idempotentHint: true,
|
|
363
|
+
// Creating an agent creates a principal in Gate, which is a system outside Intel.
|
|
364
|
+
openWorldHint: true,
|
|
365
|
+
},
|
|
366
|
+
}, async (input) => text(await deps.nodes.createAgent(actor, input, { token: deps.bearer })));
|
|
214
367
|
}
|
|
215
368
|
if (permits(deps.authorization, "knowledge", "write")) {
|
|
216
|
-
server.registerTool("
|
|
217
|
-
title: "
|
|
369
|
+
server.registerTool("agent_update", {
|
|
370
|
+
title: "Update agent definition",
|
|
371
|
+
description: "Append an immutable definition version to an agent using an optimistic base version.",
|
|
372
|
+
inputSchema: SaveAgentDefinitionInput,
|
|
373
|
+
annotations: {
|
|
374
|
+
title: "Update agent definition",
|
|
375
|
+
readOnlyHint: false,
|
|
376
|
+
destructiveHint: false,
|
|
377
|
+
idempotentHint: true,
|
|
378
|
+
openWorldHint: false,
|
|
379
|
+
},
|
|
380
|
+
}, async (input) => text(await deps.nodes.saveAgentDefinition(actor, input, { token: deps.bearer })));
|
|
381
|
+
/**
|
|
382
|
+
* The MCP half of the "Replace key" action in the agent profile (D29, #207).
|
|
383
|
+
*
|
|
384
|
+
* ⚠️ `knowledge/write` like `agent_update`, not `agents/run`: which principal an agent acts as
|
|
385
|
+
* is a property of the agent, changed by whoever may change the agent. And the result names the
|
|
386
|
+
* principal and the moment — never the key, which went to the runtime and nowhere else.
|
|
387
|
+
*/
|
|
388
|
+
server.registerTool("agent_rotate_key", {
|
|
389
|
+
title: "Replace an agent's application key",
|
|
390
|
+
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.",
|
|
391
|
+
inputSchema: RotateAgentKeyInput,
|
|
392
|
+
annotations: {
|
|
393
|
+
title: "Replace an agent's application key",
|
|
394
|
+
readOnlyHint: false,
|
|
395
|
+
// The old key stops working the moment this runs, and anything still using it breaks.
|
|
396
|
+
destructiveHint: true,
|
|
397
|
+
// Asking twice issues twice, and the second answer invalidates the first key.
|
|
398
|
+
idempotentHint: false,
|
|
399
|
+
// The principal lives in Gate, which is a system outside Intel.
|
|
400
|
+
openWorldHint: true,
|
|
401
|
+
},
|
|
402
|
+
}, async (input) => text(await deps.nodes.rotateAgentKey(actor, input, { token: deps.bearer })));
|
|
403
|
+
registerWithAlias(server, { name: "node_save", deprecated: "knowledge_save" }, {
|
|
404
|
+
title: "Save node version",
|
|
218
405
|
description: "Append an immutable content version using an optimistic base version.",
|
|
219
|
-
inputSchema:
|
|
406
|
+
inputSchema: SaveNodeVersionInput,
|
|
220
407
|
annotations: {
|
|
221
|
-
title: "Save
|
|
408
|
+
title: "Save node version",
|
|
222
409
|
readOnlyHint: false,
|
|
223
410
|
destructiveHint: false,
|
|
224
411
|
idempotentHint: true,
|
|
225
412
|
openWorldHint: false,
|
|
226
413
|
},
|
|
227
|
-
}, async (input) => text(await deps.
|
|
228
|
-
server
|
|
229
|
-
title: "Save
|
|
414
|
+
}, async (input) => text(await deps.nodes.save(actor, input)));
|
|
415
|
+
registerWithAlias(server, { name: "node_attachment_save", deprecated: "knowledge_attachment_save" }, {
|
|
416
|
+
title: "Save node attachment",
|
|
230
417
|
description: "Append immutable base64 file bytes to an attachment node.",
|
|
231
|
-
inputSchema:
|
|
418
|
+
inputSchema: SaveAttachmentInput,
|
|
419
|
+
annotations: {
|
|
420
|
+
title: "Save node attachment",
|
|
421
|
+
readOnlyHint: false,
|
|
422
|
+
destructiveHint: false,
|
|
423
|
+
idempotentHint: true,
|
|
424
|
+
openWorldHint: false,
|
|
425
|
+
},
|
|
426
|
+
}, async (input) => text(await deps.nodes.saveAttachment(actor, input)));
|
|
427
|
+
// The import half of the bundle round trip (#137). At minimum the manifest way: a zip made by
|
|
428
|
+
// the export — or a naked zipped folder — lands as a new subtree under the target. Same
|
|
429
|
+
// service as HTTP, so authorization, limits, remapping, and the all-or-nothing write cannot
|
|
430
|
+
// differ by surface; only the transport differs (inline base64 here, a streamed body there).
|
|
431
|
+
server.registerTool("node_import_bundle", {
|
|
432
|
+
title: "Import bundle",
|
|
433
|
+
description: "Import a zip bundle (base64) as a new subtree under an authorized target folder — null files at the root. Kinds come from manifest.json when present, from file extensions otherwise. Always creates new nodes; links and flow references between bundled entries are rewritten to the new IDs. Bundles beyond ~10 MB take the streaming HTTP route POST /nodes/:nodeId/import.",
|
|
434
|
+
inputSchema: ImportBundleInput,
|
|
232
435
|
annotations: {
|
|
233
|
-
title: "
|
|
436
|
+
title: "Import bundle",
|
|
234
437
|
readOnlyHint: false,
|
|
235
438
|
destructiveHint: false,
|
|
236
439
|
idempotentHint: true,
|
|
237
440
|
openWorldHint: false,
|
|
238
441
|
},
|
|
239
|
-
}, async (input) => text(await deps.
|
|
240
|
-
|
|
241
|
-
|
|
442
|
+
}, async (input) => text(await deps.bundle.importBundle(bundleActor, {
|
|
443
|
+
targetNodeId: input.nodeId,
|
|
444
|
+
zip: decodeZipBase64(input.zipBase64),
|
|
445
|
+
idempotencyKey: input.idempotencyKey,
|
|
446
|
+
})));
|
|
447
|
+
registerWithAlias(server, { name: "node_table_define", deprecated: "knowledge_table_define" }, {
|
|
448
|
+
title: "Define node table columns",
|
|
242
449
|
description: "Write the column names of an empty table. The header is the contract every append is checked against and cannot be rewritten.",
|
|
243
|
-
inputSchema:
|
|
450
|
+
inputSchema: DefineTableInput,
|
|
244
451
|
annotations: {
|
|
245
|
-
title: "Define
|
|
452
|
+
title: "Define node table columns",
|
|
246
453
|
readOnlyHint: false,
|
|
247
454
|
destructiveHint: false,
|
|
248
455
|
idempotentHint: true,
|
|
249
456
|
openWorldHint: false,
|
|
250
457
|
},
|
|
251
|
-
}, async (input) => text(await deps.
|
|
458
|
+
}, async (input) => text(await deps.nodes.defineTable(actor, input)));
|
|
252
459
|
// The tool #40 exists for: an agent collecting findings on a schedule appends them without
|
|
253
460
|
// reading or resending what is already there, and two agents appending at once lose nothing.
|
|
254
|
-
server
|
|
255
|
-
title: "Append
|
|
461
|
+
registerWithAlias(server, { name: "node_table_append", deprecated: "knowledge_table_append" }, {
|
|
462
|
+
title: "Append node table rows",
|
|
256
463
|
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.",
|
|
257
|
-
inputSchema:
|
|
464
|
+
inputSchema: AppendTableRowsInput,
|
|
258
465
|
annotations: {
|
|
259
|
-
title: "Append
|
|
466
|
+
title: "Append node table rows",
|
|
260
467
|
readOnlyHint: false,
|
|
261
468
|
destructiveHint: false,
|
|
262
469
|
// The idempotency key makes a repeat of the same call a no-op; without one, appending
|
|
@@ -264,74 +471,120 @@ export async function handleMcp(request, deps) {
|
|
|
264
471
|
idempotentHint: true,
|
|
265
472
|
openWorldHint: false,
|
|
266
473
|
},
|
|
267
|
-
}, async (input) => text(await deps.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
474
|
+
}, async (input) => text(await deps.nodes.appendTableRows(actor, input)));
|
|
475
|
+
// The other half of #135: an agent that wrote a wrong finding can correct it. Positions address
|
|
476
|
+
// rows — they carry no IDs on purpose — so every mutation names the version the positions were
|
|
477
|
+
// read from and is refused with `version_conflict` when the table moved on. No `knowledge_*`
|
|
478
|
+
// alias: these tools are new under the post-#125 naming and never carried the old prefix.
|
|
479
|
+
server.registerTool("node_table_update_rows", {
|
|
480
|
+
title: "Update node table rows",
|
|
481
|
+
description: "Replace rows at the given zero-based positions. Requires the baseVersionId the positions were read from (see node_table_get); answers version_conflict and writes nothing when the table has changed since. Each replacement row must match the column count.",
|
|
482
|
+
inputSchema: UpdateTableRowsInput,
|
|
272
483
|
annotations: {
|
|
273
|
-
title: "Update
|
|
484
|
+
title: "Update node table rows",
|
|
274
485
|
readOnlyHint: false,
|
|
275
|
-
|
|
486
|
+
// Replacing a cell discards what stood there; the history keeps it, the table does not.
|
|
487
|
+
destructiveHint: true,
|
|
488
|
+
idempotentHint: true,
|
|
489
|
+
openWorldHint: false,
|
|
490
|
+
},
|
|
491
|
+
}, async (input) => text(await deps.nodes.updateTableRows(actor, input)));
|
|
492
|
+
server.registerTool("node_table_delete_rows", {
|
|
493
|
+
title: "Delete node table rows",
|
|
494
|
+
description: "Remove rows at the given zero-based positions. Requires the baseVersionId the positions were read from; answers version_conflict and writes nothing when the table has changed since.",
|
|
495
|
+
inputSchema: DeleteTableRowsInput,
|
|
496
|
+
annotations: {
|
|
497
|
+
title: "Delete node table rows",
|
|
498
|
+
readOnlyHint: false,
|
|
499
|
+
destructiveHint: true,
|
|
276
500
|
idempotentHint: true,
|
|
277
501
|
openWorldHint: false,
|
|
278
502
|
},
|
|
279
|
-
}, async (input) => text(await deps.
|
|
280
|
-
server.registerTool("
|
|
281
|
-
title: "
|
|
282
|
-
description: "
|
|
283
|
-
inputSchema:
|
|
503
|
+
}, async (input) => text(await deps.nodes.deleteTableRows(actor, input)));
|
|
504
|
+
server.registerTool("node_table_redefine", {
|
|
505
|
+
title: "Redefine node table columns",
|
|
506
|
+
description: "Change the header of a defined table through an explicit column mapping: each new column names the current column that fills it (rename or keep), names none to start empty (add), and a current column no entry names is removed with its cells. Requires the baseVersionId the mapping was read from. Defining over an existing header without a mapping stays refused.",
|
|
507
|
+
inputSchema: RedefineTableInput,
|
|
284
508
|
annotations: {
|
|
285
|
-
title: "
|
|
509
|
+
title: "Redefine node table columns",
|
|
286
510
|
readOnlyHint: false,
|
|
287
511
|
destructiveHint: true,
|
|
288
512
|
idempotentHint: true,
|
|
289
513
|
openWorldHint: false,
|
|
290
514
|
},
|
|
291
|
-
}, async (input) => text(await deps.
|
|
515
|
+
}, async (input) => text(await deps.nodes.redefineTable(actor, input)));
|
|
516
|
+
registerWithAlias(server, { name: "node_update", deprecated: "knowledge_update" }, {
|
|
517
|
+
title: "Update node",
|
|
518
|
+
description: "Rename, move, or describe a node.",
|
|
519
|
+
inputSchema: UpdateNodeInput,
|
|
520
|
+
annotations: {
|
|
521
|
+
title: "Update node",
|
|
522
|
+
readOnlyHint: false,
|
|
523
|
+
destructiveHint: false,
|
|
524
|
+
idempotentHint: true,
|
|
525
|
+
openWorldHint: false,
|
|
526
|
+
},
|
|
527
|
+
}, async (input) => text(await deps.nodes.update(actor, input)));
|
|
528
|
+
registerWithAlias(server, { name: "node_archive", deprecated: "knowledge_archive" }, {
|
|
529
|
+
title: "Archive node",
|
|
530
|
+
description: "Archive or restore one node using optimistic concurrency. Archiving an agent also switches off its Gate application; restoring switches it back on.",
|
|
531
|
+
inputSchema: ArchiveNodeInput,
|
|
532
|
+
annotations: {
|
|
533
|
+
title: "Archive node",
|
|
534
|
+
readOnlyHint: false,
|
|
535
|
+
destructiveHint: true,
|
|
536
|
+
idempotentHint: true,
|
|
537
|
+
// The one node mutation that can reach a system outside Intel: archiving an agent
|
|
538
|
+
// switches its Gate Application (#182). Nothing else here leaves the installation.
|
|
539
|
+
openWorldHint: true,
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
// The caller's own bearer, exactly as the runtime proxy uses it and for the same reason:
|
|
543
|
+
// switching a machine principal is an act in Gate, performed by the person asking.
|
|
544
|
+
async (input) => text(await deps.nodes.archive(actor, input, { token: deps.bearer })));
|
|
292
545
|
}
|
|
293
546
|
if (permits(deps.authorization, "knowledge", "share")) {
|
|
294
|
-
server
|
|
295
|
-
title: "List
|
|
296
|
-
description: "List direct grants for
|
|
297
|
-
inputSchema:
|
|
547
|
+
registerWithAlias(server, { name: "node_shares_list", deprecated: "knowledge_shares_list" }, {
|
|
548
|
+
title: "List node shares",
|
|
549
|
+
description: "List direct grants for nodes the caller is allowed to manage.",
|
|
550
|
+
inputSchema: ListGrantsInput,
|
|
298
551
|
annotations: {
|
|
299
|
-
title: "List
|
|
552
|
+
title: "List node shares",
|
|
300
553
|
readOnlyHint: true,
|
|
301
554
|
destructiveHint: false,
|
|
302
555
|
idempotentHint: true,
|
|
303
556
|
openWorldHint: false,
|
|
304
557
|
},
|
|
305
|
-
}, async (input) => text(await deps.
|
|
306
|
-
server
|
|
307
|
-
title: "Share
|
|
308
|
-
description: "Grant inherited
|
|
309
|
-
inputSchema:
|
|
558
|
+
}, async (input) => text(await deps.nodes.listGrants(actor, input.resourceId)));
|
|
559
|
+
registerWithAlias(server, { name: "node_share", deprecated: "knowledge_share" }, {
|
|
560
|
+
title: "Share node",
|
|
561
|
+
description: "Grant inherited node access to a Gate user, verified email, or the organization.",
|
|
562
|
+
inputSchema: ShareInput,
|
|
310
563
|
annotations: {
|
|
311
|
-
title: "Share
|
|
564
|
+
title: "Share node",
|
|
312
565
|
readOnlyHint: false,
|
|
313
566
|
destructiveHint: false,
|
|
314
567
|
idempotentHint: true,
|
|
315
568
|
openWorldHint: false,
|
|
316
569
|
},
|
|
317
|
-
}, async (input) => text(await deps.
|
|
318
|
-
server
|
|
319
|
-
title: "Revoke
|
|
320
|
-
description: "Revoke one direct
|
|
321
|
-
inputSchema:
|
|
570
|
+
}, async (input) => text(await deps.nodes.share(actor, input)));
|
|
571
|
+
registerWithAlias(server, { name: "node_revoke_share", deprecated: "knowledge_revoke_share" }, {
|
|
572
|
+
title: "Revoke node share",
|
|
573
|
+
description: "Revoke one direct node grant by ID.",
|
|
574
|
+
inputSchema: RevokeGrantInput,
|
|
322
575
|
annotations: {
|
|
323
|
-
title: "Revoke
|
|
576
|
+
title: "Revoke node share",
|
|
324
577
|
readOnlyHint: false,
|
|
325
578
|
destructiveHint: true,
|
|
326
579
|
idempotentHint: true,
|
|
327
580
|
openWorldHint: false,
|
|
328
581
|
},
|
|
329
|
-
}, async (input) => text(await deps.
|
|
582
|
+
}, async (input) => text(await deps.nodes.revokeGrant(actor, input)));
|
|
330
583
|
}
|
|
331
584
|
if (permits(deps.authorization, "flows", "read")) {
|
|
332
585
|
server.registerTool("flows_list", {
|
|
333
586
|
title: "List flows",
|
|
334
|
-
description: "List authorized flows, either all of them or the ones filed in one folder of the shared
|
|
587
|
+
description: "List authorized flows, either all of them or the ones filed in one folder of the shared tree.",
|
|
335
588
|
inputSchema: ListFlowsInput,
|
|
336
589
|
annotations: {
|
|
337
590
|
title: "List flows",
|
|
@@ -353,6 +606,33 @@ export async function handleMcp(request, deps) {
|
|
|
353
606
|
openWorldHint: false,
|
|
354
607
|
},
|
|
355
608
|
}, async (input) => text(await deps.flows.get(flowActor, input.flowId)));
|
|
609
|
+
// Named like `knowledge_versions_list`, because it is the same question about the other kind of
|
|
610
|
+
// thing in the tree. This pair is what turns the `versionId` that `flow_publish` and
|
|
611
|
+
// `flow_publish_preview` demand into something an MCP client can actually obtain (#144).
|
|
612
|
+
server.registerTool("flow_versions_list", {
|
|
613
|
+
title: "List flow versions",
|
|
614
|
+
description: "List the immutable versions of one authorized flow, oldest first, marking which of them is published. Metadata only; flow_version_get loads a version's graph.",
|
|
615
|
+
inputSchema: GetFlowInput,
|
|
616
|
+
annotations: {
|
|
617
|
+
title: "List flow versions",
|
|
618
|
+
readOnlyHint: true,
|
|
619
|
+
destructiveHint: false,
|
|
620
|
+
idempotentHint: true,
|
|
621
|
+
openWorldHint: false,
|
|
622
|
+
},
|
|
623
|
+
}, async (input) => text(await deps.flows.listVersions(flowActor, input.flowId)));
|
|
624
|
+
server.registerTool("flow_version_get", {
|
|
625
|
+
title: "Get flow version",
|
|
626
|
+
description: "Get one immutable version of an authorized flow, including its graph.",
|
|
627
|
+
inputSchema: GetFlowVersionInput,
|
|
628
|
+
annotations: {
|
|
629
|
+
title: "Get flow version",
|
|
630
|
+
readOnlyHint: true,
|
|
631
|
+
destructiveHint: false,
|
|
632
|
+
idempotentHint: true,
|
|
633
|
+
openWorldHint: false,
|
|
634
|
+
},
|
|
635
|
+
}, async (input) => text(await deps.flows.getVersion(flowActor, input)));
|
|
356
636
|
server.registerTool("flow_calls_list", {
|
|
357
637
|
title: "List called flows",
|
|
358
638
|
description: "List the flows one flow calls, read out of its graph rather than out of where it is filed.",
|
|
@@ -394,7 +674,7 @@ export async function handleMcp(request, deps) {
|
|
|
394
674
|
}, async (input) => text(await deps.flows.validate(flowActor, input.flowId)));
|
|
395
675
|
server.registerTool("flow_requirements_list", {
|
|
396
676
|
title: "List what a flow needs",
|
|
397
|
-
description: "List the
|
|
677
|
+
description: "List the documents and MCP tools one flow's graph names. Documents the calling user cannot see are counted rather than named, and no claim is made about whether anyone may reach them: for tools that cannot be known in advance, because the catalog is a live query with each user's own portal token.",
|
|
398
678
|
inputSchema: GetFlowInput,
|
|
399
679
|
annotations: {
|
|
400
680
|
title: "List what a flow needs",
|
|
@@ -417,7 +697,7 @@ export async function handleMcp(request, deps) {
|
|
|
417
697
|
},
|
|
418
698
|
}, async (input) => text(await deps.flows.getRun(flowActor, input.runId)));
|
|
419
699
|
// ⚠️ Model context like every other tool result: what a run did, never what it produced. The run
|
|
420
|
-
// input and output are absent because a run reaches
|
|
700
|
+
// input and output are absent because a run reaches nodes and tools with the rights of
|
|
421
701
|
// whoever started it, and only their own runs are the calling user's to read out that way.
|
|
422
702
|
server.registerTool("flow_runs_list", {
|
|
423
703
|
title: "List flow runs",
|
|
@@ -445,7 +725,7 @@ export async function handleMcp(request, deps) {
|
|
|
445
725
|
}, async (input) => text(await deps.flows.listRunSteps(flowActor, input.runId)));
|
|
446
726
|
}
|
|
447
727
|
// A flow has no share tool of its own. Sharing happens on the folder a flow is filed in, through
|
|
448
|
-
//
|
|
728
|
+
// node_share, so a narrower grant cannot sit beside the folder grant (ADR-0004 §2).
|
|
449
729
|
if (permits(deps.authorization, "flows", "create")) {
|
|
450
730
|
server.registerTool("flow_create", {
|
|
451
731
|
title: "Create flow",
|
|
@@ -463,7 +743,7 @@ export async function handleMcp(request, deps) {
|
|
|
463
743
|
if (permits(deps.authorization, "flows", "write")) {
|
|
464
744
|
server.registerTool("flow_update", {
|
|
465
745
|
title: "Rename or move flow",
|
|
466
|
-
description: "Rename a flow, change its description, or move it to another folder of the shared
|
|
746
|
+
description: "Rename a flow, change its description, or move it to another folder of the shared tree. Never changes what the flow does.",
|
|
467
747
|
inputSchema: UpdateFlowInput,
|
|
468
748
|
annotations: {
|
|
469
749
|
title: "Rename or move flow",
|
|
@@ -518,7 +798,7 @@ export async function handleMcp(request, deps) {
|
|
|
518
798
|
}, async (input) => text(await deps.flows.previewPublish(flowActor, input)));
|
|
519
799
|
server.registerTool("flow_publish", {
|
|
520
800
|
title: "Publish flow",
|
|
521
|
-
description: "Publish a version after resolving its authorized
|
|
801
|
+
description: "Publish a version after resolving its authorized node and tool references.",
|
|
522
802
|
inputSchema: PublishFlowInput,
|
|
523
803
|
annotations: {
|
|
524
804
|
title: "Publish flow",
|
|
@@ -528,6 +808,20 @@ export async function handleMcp(request, deps) {
|
|
|
528
808
|
openWorldHint: false,
|
|
529
809
|
},
|
|
530
810
|
}, async (input) => text(await deps.flows.publish(flowActor, input)));
|
|
811
|
+
server.registerTool("flow_unpublish", {
|
|
812
|
+
title: "Unpublish flow",
|
|
813
|
+
description: "Withdraw a flow's published version. Versions are kept and can be republished; new runs and calls from other flows stop resolving, runs in flight finish on the version they took.",
|
|
814
|
+
inputSchema: UnpublishFlowInput,
|
|
815
|
+
annotations: {
|
|
816
|
+
title: "Unpublish flow",
|
|
817
|
+
readOnlyHint: false,
|
|
818
|
+
// Nothing is deleted and republishing is one publish away — destructive all the same,
|
|
819
|
+
// because a flow another flow calls stops resolving, like flow_archive.
|
|
820
|
+
destructiveHint: true,
|
|
821
|
+
idempotentHint: true,
|
|
822
|
+
openWorldHint: false,
|
|
823
|
+
},
|
|
824
|
+
}, async (input) => text(await deps.flows.unpublish(flowActor, input)));
|
|
531
825
|
}
|
|
532
826
|
if (permits(deps.authorization, "flows", "run")) {
|
|
533
827
|
server.registerTool("flow_run_start", {
|
|
@@ -554,6 +848,92 @@ export async function handleMcp(request, deps) {
|
|
|
554
848
|
openWorldHint: true,
|
|
555
849
|
},
|
|
556
850
|
}, async (input) => text(await deps.flows.completeStep(flowActor, input)));
|
|
851
|
+
server.registerTool("flow_run_cancel", {
|
|
852
|
+
title: "Cancel flow run",
|
|
853
|
+
description: "Cancel a running flow run without recording a step failure. Running child runs it called are cancelled with it; a run that is already terminal is refused unchanged.",
|
|
854
|
+
inputSchema: CancelFlowRunInput,
|
|
855
|
+
annotations: {
|
|
856
|
+
title: "Cancel flow run",
|
|
857
|
+
readOnlyHint: false,
|
|
858
|
+
// Nothing is deleted, but work in flight is ended for the run and its children — a
|
|
859
|
+
// client deciding whether to ask first has to be told that.
|
|
860
|
+
destructiveHint: true,
|
|
861
|
+
idempotentHint: true,
|
|
862
|
+
openWorldHint: false,
|
|
863
|
+
},
|
|
864
|
+
}, async (input) => text(await deps.flows.cancelRun(flowActor, input)));
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Driving an agent, over the same door the screen uses.
|
|
868
|
+
*
|
|
869
|
+
* ⚠️ The three below are the MCP half of the two head actions on the agent page — the repository's
|
|
870
|
+
* "one business layer, three thin surfaces" rule, kept in the same delivery. They call the runtime
|
|
871
|
+
* through `deps.agents`, which resolves the resource ACL and forwards the CALLER's bearer; a tool
|
|
872
|
+
* that reached the runtime with anything else would be Intel acting as somebody it is not.
|
|
873
|
+
*/
|
|
874
|
+
if (permits(deps.authorization, "agents", "run")) {
|
|
875
|
+
server.registerTool("agent_pause", {
|
|
876
|
+
title: "Pause agent",
|
|
877
|
+
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.",
|
|
878
|
+
inputSchema: PauseAgentInput,
|
|
879
|
+
annotations: {
|
|
880
|
+
title: "Pause agent",
|
|
881
|
+
readOnlyHint: false,
|
|
882
|
+
// Nothing is deleted, and yet work that would have happened does not — a client deciding
|
|
883
|
+
// whether to ask first has to be told that.
|
|
884
|
+
destructiveHint: true,
|
|
885
|
+
idempotentHint: true,
|
|
886
|
+
openWorldHint: false,
|
|
887
|
+
},
|
|
888
|
+
}, async (input) => text(await deps.agents.request({
|
|
889
|
+
actor: agentActor,
|
|
890
|
+
token: deps.bearer,
|
|
891
|
+
agentId: input.nodeId,
|
|
892
|
+
method: "POST",
|
|
893
|
+
path: "/pause",
|
|
894
|
+
body: "{}",
|
|
895
|
+
})));
|
|
896
|
+
server.registerTool("agent_resume", {
|
|
897
|
+
title: "Resume agent",
|
|
898
|
+
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.",
|
|
899
|
+
inputSchema: PauseAgentInput,
|
|
900
|
+
annotations: {
|
|
901
|
+
title: "Resume agent",
|
|
902
|
+
readOnlyHint: false,
|
|
903
|
+
destructiveHint: false,
|
|
904
|
+
idempotentHint: true,
|
|
905
|
+
openWorldHint: false,
|
|
906
|
+
},
|
|
907
|
+
}, async (input) => text(await deps.agents.request({
|
|
908
|
+
actor: agentActor,
|
|
909
|
+
token: deps.bearer,
|
|
910
|
+
agentId: input.nodeId,
|
|
911
|
+
method: "POST",
|
|
912
|
+
path: "/resume",
|
|
913
|
+
body: "{}",
|
|
914
|
+
})));
|
|
915
|
+
server.registerTool("agent_run_now", {
|
|
916
|
+
title: "Run agent now",
|
|
917
|
+
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.",
|
|
918
|
+
inputSchema: RunAgentNowInput,
|
|
919
|
+
annotations: {
|
|
920
|
+
title: "Run agent now",
|
|
921
|
+
readOnlyHint: false,
|
|
922
|
+
destructiveHint: false,
|
|
923
|
+
// Every run is a new run, so asking twice runs it twice.
|
|
924
|
+
idempotentHint: false,
|
|
925
|
+
// The agent may reach whatever its own definition grants it, so what a run touches is not
|
|
926
|
+
// knowable from this schema.
|
|
927
|
+
openWorldHint: true,
|
|
928
|
+
},
|
|
929
|
+
}, async (input) => text(await deps.agents.request({
|
|
930
|
+
actor: agentActor,
|
|
931
|
+
token: deps.bearer,
|
|
932
|
+
agentId: input.nodeId,
|
|
933
|
+
method: "POST",
|
|
934
|
+
path: "/run",
|
|
935
|
+
body: JSON.stringify({ target: input.target }),
|
|
936
|
+
})));
|
|
557
937
|
}
|
|
558
938
|
if (permits(deps.authorization, "tools", "read")) {
|
|
559
939
|
server.registerTool("tools_catalog_list", {
|
|
@@ -568,6 +948,21 @@ export async function handleMcp(request, deps) {
|
|
|
568
948
|
openWorldHint: false,
|
|
569
949
|
},
|
|
570
950
|
}, async () => text(await deps.tools.catalog(toolActor)));
|
|
951
|
+
// The MCP twin of the picker in the agent profile (D30): whole servers, so an agent can be
|
|
952
|
+
// given tools from a client instead of from the screen. Assigning and removing is
|
|
953
|
+
// `agent_update` — a delegation is a field of the definition, not a surface of its own.
|
|
954
|
+
server.registerTool("tools_servers_list", {
|
|
955
|
+
title: "List tool servers",
|
|
956
|
+
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.",
|
|
957
|
+
inputSchema: EmptyInput,
|
|
958
|
+
annotations: {
|
|
959
|
+
title: "List tool servers",
|
|
960
|
+
readOnlyHint: true,
|
|
961
|
+
destructiveHint: false,
|
|
962
|
+
idempotentHint: true,
|
|
963
|
+
openWorldHint: false,
|
|
964
|
+
},
|
|
965
|
+
}, async () => text(await deps.tools.servers(toolActor)));
|
|
571
966
|
}
|
|
572
967
|
if (permits(deps.authorization, "tools", "test")) {
|
|
573
968
|
server.registerTool("tools_test", {
|