@anchrd/intel-api 0.6.6 → 0.7.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 +44 -3
- package/dist/adapters/cloudflare/cloudflare.js +50 -16
- package/dist/adapters/cloudflare/cloudflare.types.d.ts +11 -0
- package/dist/adapters/content/content.d.ts +1 -1
- package/dist/adapters/db/db-flows.js +161 -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 +448 -119
- package/dist/adapters/gate-applications/gate-applications.d.ts +23 -0
- package/dist/adapters/gate-applications/gate-applications.js +66 -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/agent-runtime/agent-runtime.d.ts +16 -0
- package/dist/agent-runtime/agent-runtime.js +76 -0
- package/dist/agent-runtime/agent-runtime.types.d.ts +57 -0
- package/dist/bundle/bundle.d.ts +4 -0
- package/dist/bundle/bundle.js +1035 -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 +329 -63
- 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 +483 -124
- package/dist/mcp/mcp.types.d.ts +11 -2
- package/dist/nodes/nodes.d.ts +2 -0
- package/dist/nodes/nodes.js +1337 -0
- package/dist/nodes/nodes.types.d.ts +314 -0
- package/dist/nodes/nodes.types.js +1 -0
- 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/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, 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,258 @@ 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
|
+
* ⚠️ The ONE tool result in Intel that contains a secret, and it is a decision rather than an
|
|
346
|
+
* oversight (#182, D27). Everywhere else in this file a credential in a result would be a leak
|
|
347
|
+
* into model context; here the result IS the credential — Gate issues an Application key once
|
|
348
|
+
* and keeps only its hash, so a redacted answer would mean "an agent you can create but never
|
|
349
|
+
* run", and the same administrator would get less through MCP than through the browser. Gate
|
|
350
|
+
* made the identical exception for `application_create` (`anchrd/gate` GATE-75).
|
|
351
|
+
*
|
|
352
|
+
* The exception is this tool and nothing beside it: `agent_get`, `agent_list` and every node
|
|
353
|
+
* read answer with `applicationId` alone, which names the principal without authenticating it.
|
|
354
|
+
*/
|
|
355
|
+
server.registerTool("agent_create", {
|
|
356
|
+
title: "Create agent",
|
|
357
|
+
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 response carries the application key ONCE, in plain text: put it into the agent runtime's AGENT_APPLICATION_KEYS secret and store it nowhere else. A repeated idempotency key returns the existing agent with no key.",
|
|
358
|
+
inputSchema: CreateAgentInput,
|
|
359
|
+
annotations: {
|
|
360
|
+
title: "Create agent",
|
|
361
|
+
readOnlyHint: false,
|
|
362
|
+
destructiveHint: false,
|
|
363
|
+
idempotentHint: true,
|
|
364
|
+
// Creating an agent creates a principal in Gate, which is a system outside Intel.
|
|
365
|
+
openWorldHint: true,
|
|
366
|
+
},
|
|
367
|
+
}, async (input) => text(await deps.nodes.createAgent(actor, input, { token: deps.bearer })));
|
|
214
368
|
}
|
|
215
369
|
if (permits(deps.authorization, "knowledge", "write")) {
|
|
216
|
-
server.registerTool("
|
|
217
|
-
title: "
|
|
370
|
+
server.registerTool("agent_update", {
|
|
371
|
+
title: "Update agent definition",
|
|
372
|
+
description: "Append an immutable definition version to an agent using an optimistic base version.",
|
|
373
|
+
inputSchema: SaveAgentDefinitionInput,
|
|
374
|
+
annotations: {
|
|
375
|
+
title: "Update agent definition",
|
|
376
|
+
readOnlyHint: false,
|
|
377
|
+
destructiveHint: false,
|
|
378
|
+
idempotentHint: true,
|
|
379
|
+
openWorldHint: false,
|
|
380
|
+
},
|
|
381
|
+
}, async (input) => text(await deps.nodes.saveAgentDefinition(actor, input)));
|
|
382
|
+
registerWithAlias(server, { name: "node_save", deprecated: "knowledge_save" }, {
|
|
383
|
+
title: "Save node version",
|
|
218
384
|
description: "Append an immutable content version using an optimistic base version.",
|
|
219
|
-
inputSchema:
|
|
385
|
+
inputSchema: SaveNodeVersionInput,
|
|
220
386
|
annotations: {
|
|
221
|
-
title: "Save
|
|
387
|
+
title: "Save node version",
|
|
222
388
|
readOnlyHint: false,
|
|
223
389
|
destructiveHint: false,
|
|
224
390
|
idempotentHint: true,
|
|
225
391
|
openWorldHint: false,
|
|
226
392
|
},
|
|
227
|
-
}, async (input) => text(await deps.
|
|
228
|
-
server
|
|
229
|
-
title: "Save
|
|
393
|
+
}, async (input) => text(await deps.nodes.save(actor, input)));
|
|
394
|
+
registerWithAlias(server, { name: "node_attachment_save", deprecated: "knowledge_attachment_save" }, {
|
|
395
|
+
title: "Save node attachment",
|
|
230
396
|
description: "Append immutable base64 file bytes to an attachment node.",
|
|
231
|
-
inputSchema:
|
|
397
|
+
inputSchema: SaveAttachmentInput,
|
|
232
398
|
annotations: {
|
|
233
|
-
title: "Save
|
|
399
|
+
title: "Save node attachment",
|
|
234
400
|
readOnlyHint: false,
|
|
235
401
|
destructiveHint: false,
|
|
236
402
|
idempotentHint: true,
|
|
237
403
|
openWorldHint: false,
|
|
238
404
|
},
|
|
239
|
-
}, async (input) => text(await deps.
|
|
240
|
-
|
|
241
|
-
|
|
405
|
+
}, async (input) => text(await deps.nodes.saveAttachment(actor, input)));
|
|
406
|
+
// The import half of the bundle round trip (#137). At minimum the manifest way: a zip made by
|
|
407
|
+
// the export — or a naked zipped folder — lands as a new subtree under the target. Same
|
|
408
|
+
// service as HTTP, so authorization, limits, remapping, and the all-or-nothing write cannot
|
|
409
|
+
// differ by surface; only the transport differs (inline base64 here, a streamed body there).
|
|
410
|
+
server.registerTool("node_import_bundle", {
|
|
411
|
+
title: "Import bundle",
|
|
412
|
+
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.",
|
|
413
|
+
inputSchema: ImportBundleInput,
|
|
414
|
+
annotations: {
|
|
415
|
+
title: "Import bundle",
|
|
416
|
+
readOnlyHint: false,
|
|
417
|
+
destructiveHint: false,
|
|
418
|
+
idempotentHint: true,
|
|
419
|
+
openWorldHint: false,
|
|
420
|
+
},
|
|
421
|
+
}, async (input) => text(await deps.bundle.importBundle(bundleActor, {
|
|
422
|
+
targetNodeId: input.nodeId,
|
|
423
|
+
zip: decodeZipBase64(input.zipBase64),
|
|
424
|
+
idempotencyKey: input.idempotencyKey,
|
|
425
|
+
})));
|
|
426
|
+
registerWithAlias(server, { name: "node_table_define", deprecated: "knowledge_table_define" }, {
|
|
427
|
+
title: "Define node table columns",
|
|
242
428
|
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:
|
|
429
|
+
inputSchema: DefineTableInput,
|
|
244
430
|
annotations: {
|
|
245
|
-
title: "Define
|
|
431
|
+
title: "Define node table columns",
|
|
246
432
|
readOnlyHint: false,
|
|
247
433
|
destructiveHint: false,
|
|
248
434
|
idempotentHint: true,
|
|
249
435
|
openWorldHint: false,
|
|
250
436
|
},
|
|
251
|
-
}, async (input) => text(await deps.
|
|
437
|
+
}, async (input) => text(await deps.nodes.defineTable(actor, input)));
|
|
252
438
|
// The tool #40 exists for: an agent collecting findings on a schedule appends them without
|
|
253
439
|
// reading or resending what is already there, and two agents appending at once lose nothing.
|
|
254
|
-
server
|
|
255
|
-
title: "Append
|
|
440
|
+
registerWithAlias(server, { name: "node_table_append", deprecated: "knowledge_table_append" }, {
|
|
441
|
+
title: "Append node table rows",
|
|
256
442
|
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:
|
|
443
|
+
inputSchema: AppendTableRowsInput,
|
|
258
444
|
annotations: {
|
|
259
|
-
title: "Append
|
|
445
|
+
title: "Append node table rows",
|
|
260
446
|
readOnlyHint: false,
|
|
261
447
|
destructiveHint: false,
|
|
262
448
|
// The idempotency key makes a repeat of the same call a no-op; without one, appending
|
|
@@ -264,74 +450,120 @@ export async function handleMcp(request, deps) {
|
|
|
264
450
|
idempotentHint: true,
|
|
265
451
|
openWorldHint: false,
|
|
266
452
|
},
|
|
267
|
-
}, async (input) => text(await deps.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
453
|
+
}, async (input) => text(await deps.nodes.appendTableRows(actor, input)));
|
|
454
|
+
// The other half of #135: an agent that wrote a wrong finding can correct it. Positions address
|
|
455
|
+
// rows — they carry no IDs on purpose — so every mutation names the version the positions were
|
|
456
|
+
// read from and is refused with `version_conflict` when the table moved on. No `knowledge_*`
|
|
457
|
+
// alias: these tools are new under the post-#125 naming and never carried the old prefix.
|
|
458
|
+
server.registerTool("node_table_update_rows", {
|
|
459
|
+
title: "Update node table rows",
|
|
460
|
+
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.",
|
|
461
|
+
inputSchema: UpdateTableRowsInput,
|
|
272
462
|
annotations: {
|
|
273
|
-
title: "Update
|
|
463
|
+
title: "Update node table rows",
|
|
274
464
|
readOnlyHint: false,
|
|
275
|
-
|
|
465
|
+
// Replacing a cell discards what stood there; the history keeps it, the table does not.
|
|
466
|
+
destructiveHint: true,
|
|
276
467
|
idempotentHint: true,
|
|
277
468
|
openWorldHint: false,
|
|
278
469
|
},
|
|
279
|
-
}, async (input) => text(await deps.
|
|
280
|
-
server.registerTool("
|
|
281
|
-
title: "
|
|
282
|
-
description: "
|
|
283
|
-
inputSchema:
|
|
470
|
+
}, async (input) => text(await deps.nodes.updateTableRows(actor, input)));
|
|
471
|
+
server.registerTool("node_table_delete_rows", {
|
|
472
|
+
title: "Delete node table rows",
|
|
473
|
+
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.",
|
|
474
|
+
inputSchema: DeleteTableRowsInput,
|
|
284
475
|
annotations: {
|
|
285
|
-
title: "
|
|
476
|
+
title: "Delete node table rows",
|
|
286
477
|
readOnlyHint: false,
|
|
287
478
|
destructiveHint: true,
|
|
288
479
|
idempotentHint: true,
|
|
289
480
|
openWorldHint: false,
|
|
290
481
|
},
|
|
291
|
-
}, async (input) => text(await deps.
|
|
482
|
+
}, async (input) => text(await deps.nodes.deleteTableRows(actor, input)));
|
|
483
|
+
server.registerTool("node_table_redefine", {
|
|
484
|
+
title: "Redefine node table columns",
|
|
485
|
+
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.",
|
|
486
|
+
inputSchema: RedefineTableInput,
|
|
487
|
+
annotations: {
|
|
488
|
+
title: "Redefine node table columns",
|
|
489
|
+
readOnlyHint: false,
|
|
490
|
+
destructiveHint: true,
|
|
491
|
+
idempotentHint: true,
|
|
492
|
+
openWorldHint: false,
|
|
493
|
+
},
|
|
494
|
+
}, async (input) => text(await deps.nodes.redefineTable(actor, input)));
|
|
495
|
+
registerWithAlias(server, { name: "node_update", deprecated: "knowledge_update" }, {
|
|
496
|
+
title: "Update node",
|
|
497
|
+
description: "Rename, move, or describe a node.",
|
|
498
|
+
inputSchema: UpdateNodeInput,
|
|
499
|
+
annotations: {
|
|
500
|
+
title: "Update node",
|
|
501
|
+
readOnlyHint: false,
|
|
502
|
+
destructiveHint: false,
|
|
503
|
+
idempotentHint: true,
|
|
504
|
+
openWorldHint: false,
|
|
505
|
+
},
|
|
506
|
+
}, async (input) => text(await deps.nodes.update(actor, input)));
|
|
507
|
+
registerWithAlias(server, { name: "node_archive", deprecated: "knowledge_archive" }, {
|
|
508
|
+
title: "Archive node",
|
|
509
|
+
description: "Archive or restore one node using optimistic concurrency. Archiving an agent also switches off its Gate application; restoring switches it back on.",
|
|
510
|
+
inputSchema: ArchiveNodeInput,
|
|
511
|
+
annotations: {
|
|
512
|
+
title: "Archive node",
|
|
513
|
+
readOnlyHint: false,
|
|
514
|
+
destructiveHint: true,
|
|
515
|
+
idempotentHint: true,
|
|
516
|
+
// The one node mutation that can reach a system outside Intel: archiving an agent
|
|
517
|
+
// switches its Gate Application (#182). Nothing else here leaves the installation.
|
|
518
|
+
openWorldHint: true,
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
// The caller's own bearer, exactly as the runtime proxy uses it and for the same reason:
|
|
522
|
+
// switching a machine principal is an act in Gate, performed by the person asking.
|
|
523
|
+
async (input) => text(await deps.nodes.archive(actor, input, { token: deps.bearer })));
|
|
292
524
|
}
|
|
293
525
|
if (permits(deps.authorization, "knowledge", "share")) {
|
|
294
|
-
server
|
|
295
|
-
title: "List
|
|
296
|
-
description: "List direct grants for
|
|
297
|
-
inputSchema:
|
|
526
|
+
registerWithAlias(server, { name: "node_shares_list", deprecated: "knowledge_shares_list" }, {
|
|
527
|
+
title: "List node shares",
|
|
528
|
+
description: "List direct grants for nodes the caller is allowed to manage.",
|
|
529
|
+
inputSchema: ListGrantsInput,
|
|
298
530
|
annotations: {
|
|
299
|
-
title: "List
|
|
531
|
+
title: "List node shares",
|
|
300
532
|
readOnlyHint: true,
|
|
301
533
|
destructiveHint: false,
|
|
302
534
|
idempotentHint: true,
|
|
303
535
|
openWorldHint: false,
|
|
304
536
|
},
|
|
305
|
-
}, async (input) => text(await deps.
|
|
306
|
-
server
|
|
307
|
-
title: "Share
|
|
308
|
-
description: "Grant inherited
|
|
309
|
-
inputSchema:
|
|
537
|
+
}, async (input) => text(await deps.nodes.listGrants(actor, input.resourceId)));
|
|
538
|
+
registerWithAlias(server, { name: "node_share", deprecated: "knowledge_share" }, {
|
|
539
|
+
title: "Share node",
|
|
540
|
+
description: "Grant inherited node access to a Gate user, verified email, or the organization.",
|
|
541
|
+
inputSchema: ShareInput,
|
|
310
542
|
annotations: {
|
|
311
|
-
title: "Share
|
|
543
|
+
title: "Share node",
|
|
312
544
|
readOnlyHint: false,
|
|
313
545
|
destructiveHint: false,
|
|
314
546
|
idempotentHint: true,
|
|
315
547
|
openWorldHint: false,
|
|
316
548
|
},
|
|
317
|
-
}, async (input) => text(await deps.
|
|
318
|
-
server
|
|
319
|
-
title: "Revoke
|
|
320
|
-
description: "Revoke one direct
|
|
321
|
-
inputSchema:
|
|
549
|
+
}, async (input) => text(await deps.nodes.share(actor, input)));
|
|
550
|
+
registerWithAlias(server, { name: "node_revoke_share", deprecated: "knowledge_revoke_share" }, {
|
|
551
|
+
title: "Revoke node share",
|
|
552
|
+
description: "Revoke one direct node grant by ID.",
|
|
553
|
+
inputSchema: RevokeGrantInput,
|
|
322
554
|
annotations: {
|
|
323
|
-
title: "Revoke
|
|
555
|
+
title: "Revoke node share",
|
|
324
556
|
readOnlyHint: false,
|
|
325
557
|
destructiveHint: true,
|
|
326
558
|
idempotentHint: true,
|
|
327
559
|
openWorldHint: false,
|
|
328
560
|
},
|
|
329
|
-
}, async (input) => text(await deps.
|
|
561
|
+
}, async (input) => text(await deps.nodes.revokeGrant(actor, input)));
|
|
330
562
|
}
|
|
331
563
|
if (permits(deps.authorization, "flows", "read")) {
|
|
332
564
|
server.registerTool("flows_list", {
|
|
333
565
|
title: "List flows",
|
|
334
|
-
description: "List authorized flows, either all of them or the ones filed in one folder of the shared
|
|
566
|
+
description: "List authorized flows, either all of them or the ones filed in one folder of the shared tree.",
|
|
335
567
|
inputSchema: ListFlowsInput,
|
|
336
568
|
annotations: {
|
|
337
569
|
title: "List flows",
|
|
@@ -353,6 +585,33 @@ export async function handleMcp(request, deps) {
|
|
|
353
585
|
openWorldHint: false,
|
|
354
586
|
},
|
|
355
587
|
}, async (input) => text(await deps.flows.get(flowActor, input.flowId)));
|
|
588
|
+
// Named like `knowledge_versions_list`, because it is the same question about the other kind of
|
|
589
|
+
// thing in the tree. This pair is what turns the `versionId` that `flow_publish` and
|
|
590
|
+
// `flow_publish_preview` demand into something an MCP client can actually obtain (#144).
|
|
591
|
+
server.registerTool("flow_versions_list", {
|
|
592
|
+
title: "List flow versions",
|
|
593
|
+
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.",
|
|
594
|
+
inputSchema: GetFlowInput,
|
|
595
|
+
annotations: {
|
|
596
|
+
title: "List flow versions",
|
|
597
|
+
readOnlyHint: true,
|
|
598
|
+
destructiveHint: false,
|
|
599
|
+
idempotentHint: true,
|
|
600
|
+
openWorldHint: false,
|
|
601
|
+
},
|
|
602
|
+
}, async (input) => text(await deps.flows.listVersions(flowActor, input.flowId)));
|
|
603
|
+
server.registerTool("flow_version_get", {
|
|
604
|
+
title: "Get flow version",
|
|
605
|
+
description: "Get one immutable version of an authorized flow, including its graph.",
|
|
606
|
+
inputSchema: GetFlowVersionInput,
|
|
607
|
+
annotations: {
|
|
608
|
+
title: "Get flow version",
|
|
609
|
+
readOnlyHint: true,
|
|
610
|
+
destructiveHint: false,
|
|
611
|
+
idempotentHint: true,
|
|
612
|
+
openWorldHint: false,
|
|
613
|
+
},
|
|
614
|
+
}, async (input) => text(await deps.flows.getVersion(flowActor, input)));
|
|
356
615
|
server.registerTool("flow_calls_list", {
|
|
357
616
|
title: "List called flows",
|
|
358
617
|
description: "List the flows one flow calls, read out of its graph rather than out of where it is filed.",
|
|
@@ -394,7 +653,7 @@ export async function handleMcp(request, deps) {
|
|
|
394
653
|
}, async (input) => text(await deps.flows.validate(flowActor, input.flowId)));
|
|
395
654
|
server.registerTool("flow_requirements_list", {
|
|
396
655
|
title: "List what a flow needs",
|
|
397
|
-
description: "List the
|
|
656
|
+
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
657
|
inputSchema: GetFlowInput,
|
|
399
658
|
annotations: {
|
|
400
659
|
title: "List what a flow needs",
|
|
@@ -417,7 +676,7 @@ export async function handleMcp(request, deps) {
|
|
|
417
676
|
},
|
|
418
677
|
}, async (input) => text(await deps.flows.getRun(flowActor, input.runId)));
|
|
419
678
|
// ⚠️ 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
|
|
679
|
+
// input and output are absent because a run reaches nodes and tools with the rights of
|
|
421
680
|
// whoever started it, and only their own runs are the calling user's to read out that way.
|
|
422
681
|
server.registerTool("flow_runs_list", {
|
|
423
682
|
title: "List flow runs",
|
|
@@ -445,7 +704,7 @@ export async function handleMcp(request, deps) {
|
|
|
445
704
|
}, async (input) => text(await deps.flows.listRunSteps(flowActor, input.runId)));
|
|
446
705
|
}
|
|
447
706
|
// A flow has no share tool of its own. Sharing happens on the folder a flow is filed in, through
|
|
448
|
-
//
|
|
707
|
+
// node_share, so a narrower grant cannot sit beside the folder grant (ADR-0004 §2).
|
|
449
708
|
if (permits(deps.authorization, "flows", "create")) {
|
|
450
709
|
server.registerTool("flow_create", {
|
|
451
710
|
title: "Create flow",
|
|
@@ -463,7 +722,7 @@ export async function handleMcp(request, deps) {
|
|
|
463
722
|
if (permits(deps.authorization, "flows", "write")) {
|
|
464
723
|
server.registerTool("flow_update", {
|
|
465
724
|
title: "Rename or move flow",
|
|
466
|
-
description: "Rename a flow, change its description, or move it to another folder of the shared
|
|
725
|
+
description: "Rename a flow, change its description, or move it to another folder of the shared tree. Never changes what the flow does.",
|
|
467
726
|
inputSchema: UpdateFlowInput,
|
|
468
727
|
annotations: {
|
|
469
728
|
title: "Rename or move flow",
|
|
@@ -518,7 +777,7 @@ export async function handleMcp(request, deps) {
|
|
|
518
777
|
}, async (input) => text(await deps.flows.previewPublish(flowActor, input)));
|
|
519
778
|
server.registerTool("flow_publish", {
|
|
520
779
|
title: "Publish flow",
|
|
521
|
-
description: "Publish a version after resolving its authorized
|
|
780
|
+
description: "Publish a version after resolving its authorized node and tool references.",
|
|
522
781
|
inputSchema: PublishFlowInput,
|
|
523
782
|
annotations: {
|
|
524
783
|
title: "Publish flow",
|
|
@@ -528,6 +787,20 @@ export async function handleMcp(request, deps) {
|
|
|
528
787
|
openWorldHint: false,
|
|
529
788
|
},
|
|
530
789
|
}, async (input) => text(await deps.flows.publish(flowActor, input)));
|
|
790
|
+
server.registerTool("flow_unpublish", {
|
|
791
|
+
title: "Unpublish flow",
|
|
792
|
+
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.",
|
|
793
|
+
inputSchema: UnpublishFlowInput,
|
|
794
|
+
annotations: {
|
|
795
|
+
title: "Unpublish flow",
|
|
796
|
+
readOnlyHint: false,
|
|
797
|
+
// Nothing is deleted and republishing is one publish away — destructive all the same,
|
|
798
|
+
// because a flow another flow calls stops resolving, like flow_archive.
|
|
799
|
+
destructiveHint: true,
|
|
800
|
+
idempotentHint: true,
|
|
801
|
+
openWorldHint: false,
|
|
802
|
+
},
|
|
803
|
+
}, async (input) => text(await deps.flows.unpublish(flowActor, input)));
|
|
531
804
|
}
|
|
532
805
|
if (permits(deps.authorization, "flows", "run")) {
|
|
533
806
|
server.registerTool("flow_run_start", {
|
|
@@ -554,6 +827,92 @@ export async function handleMcp(request, deps) {
|
|
|
554
827
|
openWorldHint: true,
|
|
555
828
|
},
|
|
556
829
|
}, async (input) => text(await deps.flows.completeStep(flowActor, input)));
|
|
830
|
+
server.registerTool("flow_run_cancel", {
|
|
831
|
+
title: "Cancel flow run",
|
|
832
|
+
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.",
|
|
833
|
+
inputSchema: CancelFlowRunInput,
|
|
834
|
+
annotations: {
|
|
835
|
+
title: "Cancel flow run",
|
|
836
|
+
readOnlyHint: false,
|
|
837
|
+
// Nothing is deleted, but work in flight is ended for the run and its children — a
|
|
838
|
+
// client deciding whether to ask first has to be told that.
|
|
839
|
+
destructiveHint: true,
|
|
840
|
+
idempotentHint: true,
|
|
841
|
+
openWorldHint: false,
|
|
842
|
+
},
|
|
843
|
+
}, async (input) => text(await deps.flows.cancelRun(flowActor, input)));
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Driving an agent, over the same door the screen uses.
|
|
847
|
+
*
|
|
848
|
+
* ⚠️ The three below are the MCP half of the two head actions on the agent page — the repository's
|
|
849
|
+
* "one business layer, three thin surfaces" rule, kept in the same delivery. They call the runtime
|
|
850
|
+
* through `deps.agents`, which resolves the resource ACL and forwards the CALLER's bearer; a tool
|
|
851
|
+
* that reached the runtime with anything else would be Intel acting as somebody it is not.
|
|
852
|
+
*/
|
|
853
|
+
if (permits(deps.authorization, "agents", "run")) {
|
|
854
|
+
server.registerTool("agent_pause", {
|
|
855
|
+
title: "Pause agent",
|
|
856
|
+
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.",
|
|
857
|
+
inputSchema: PauseAgentInput,
|
|
858
|
+
annotations: {
|
|
859
|
+
title: "Pause agent",
|
|
860
|
+
readOnlyHint: false,
|
|
861
|
+
// Nothing is deleted, and yet work that would have happened does not — a client deciding
|
|
862
|
+
// whether to ask first has to be told that.
|
|
863
|
+
destructiveHint: true,
|
|
864
|
+
idempotentHint: true,
|
|
865
|
+
openWorldHint: false,
|
|
866
|
+
},
|
|
867
|
+
}, async (input) => text(await deps.agents.request({
|
|
868
|
+
actor: agentActor,
|
|
869
|
+
token: deps.bearer,
|
|
870
|
+
agentId: input.nodeId,
|
|
871
|
+
method: "POST",
|
|
872
|
+
path: "/pause",
|
|
873
|
+
body: "{}",
|
|
874
|
+
})));
|
|
875
|
+
server.registerTool("agent_resume", {
|
|
876
|
+
title: "Resume agent",
|
|
877
|
+
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.",
|
|
878
|
+
inputSchema: PauseAgentInput,
|
|
879
|
+
annotations: {
|
|
880
|
+
title: "Resume agent",
|
|
881
|
+
readOnlyHint: false,
|
|
882
|
+
destructiveHint: false,
|
|
883
|
+
idempotentHint: true,
|
|
884
|
+
openWorldHint: false,
|
|
885
|
+
},
|
|
886
|
+
}, async (input) => text(await deps.agents.request({
|
|
887
|
+
actor: agentActor,
|
|
888
|
+
token: deps.bearer,
|
|
889
|
+
agentId: input.nodeId,
|
|
890
|
+
method: "POST",
|
|
891
|
+
path: "/resume",
|
|
892
|
+
body: "{}",
|
|
893
|
+
})));
|
|
894
|
+
server.registerTool("agent_run_now", {
|
|
895
|
+
title: "Run agent now",
|
|
896
|
+
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.",
|
|
897
|
+
inputSchema: RunAgentNowInput,
|
|
898
|
+
annotations: {
|
|
899
|
+
title: "Run agent now",
|
|
900
|
+
readOnlyHint: false,
|
|
901
|
+
destructiveHint: false,
|
|
902
|
+
// Every run is a new run, so asking twice runs it twice.
|
|
903
|
+
idempotentHint: false,
|
|
904
|
+
// The agent may reach whatever its own definition grants it, so what a run touches is not
|
|
905
|
+
// knowable from this schema.
|
|
906
|
+
openWorldHint: true,
|
|
907
|
+
},
|
|
908
|
+
}, async (input) => text(await deps.agents.request({
|
|
909
|
+
actor: agentActor,
|
|
910
|
+
token: deps.bearer,
|
|
911
|
+
agentId: input.nodeId,
|
|
912
|
+
method: "POST",
|
|
913
|
+
path: "/run",
|
|
914
|
+
body: JSON.stringify({ target: input.target }),
|
|
915
|
+
})));
|
|
557
916
|
}
|
|
558
917
|
if (permits(deps.authorization, "tools", "read")) {
|
|
559
918
|
server.registerTool("tools_catalog_list", {
|