@frockbot/plugin-mcp 0.1.2 → 0.1.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/plugin-mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -25,13 +25,13 @@
25
25
  "typecheck": "tsc --noEmit -p tsconfig.json"
26
26
  },
27
27
  "dependencies": {
28
- "@frockbot/configuration-core": "0.1.2",
29
- "@frockbot/connection-core": "0.1.2",
30
- "@frockbot/kernel-contracts": "0.1.2",
31
- "@frockbot/plugin-credentials": "0.1.2",
32
- "@frockbot/plugin-settings": "0.1.2",
33
- "@frockbot/plugin-tools": "0.1.2",
34
- "@frockbot/plugin-web": "0.1.2",
28
+ "@frockbot/configuration-core": "0.1.4",
29
+ "@frockbot/connection-core": "0.1.4",
30
+ "@frockbot/kernel-contracts": "0.1.4",
31
+ "@frockbot/plugin-credentials": "0.1.4",
32
+ "@frockbot/plugin-settings": "0.1.4",
33
+ "@frockbot/plugin-tools": "0.1.4",
34
+ "@frockbot/plugin-web": "0.1.4",
35
35
  "cordis": "4.0.0-rc.8"
36
36
  },
37
37
  "devDependencies": {
package/src/agent.test.ts CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  createConfiguredMcpRuntimeContribution,
7
7
  decodeMcpConnectionSettingsV1,
8
8
  MAX_MCP_SERVERS_PER_USER_V1,
9
- mcpAssignmentResolutionV1,
9
+ mcpCapabilityResolutionV1,
10
10
  mcpConnectionLifecycleV1,
11
11
  mcpServerSlugV1,
12
12
  mcpToolNameV1,
@@ -96,20 +96,19 @@ function server(options: {
96
96
  async function mount(config: {
97
97
  fetch: typeof fetch;
98
98
  connection?: ConnectionView;
99
- assignmentIndex?: number;
99
+ capabilityIndex?: number;
100
100
  failures?: string[];
101
101
  outcomes?: unknown[];
102
102
  }): Promise<{ root: Context; mounted: boolean }> {
103
103
  const plugin = await createConfiguredMcpRuntimeContribution({
104
- assignment: {
104
+ capability: {
105
105
  packageId: "mcp",
106
106
  capabilityId: "mcp-tools",
107
107
  connectionId: "mcp-1",
108
- state: "enabled",
109
108
  },
110
- ...(config.assignmentIndex === undefined
109
+ ...(config.capabilityIndex === undefined
111
110
  ? {}
112
- : { assignmentIndex: config.assignmentIndex }),
111
+ : { capabilityIndex: config.capabilityIndex }),
113
112
  userId: "user-1",
114
113
  readSecret: () => undefined,
115
114
  authorizeConnection: () =>
@@ -170,7 +169,7 @@ describe("Connection-scoped settings", () => {
170
169
  });
171
170
  });
172
171
 
173
- describe("mounting one Assignment", () => {
172
+ describe("mounting one enabled Capability", () => {
174
173
  test("registers every listed tool with the server's own schema", async () => {
175
174
  const { root } = await mount({ fetch: server({}) });
176
175
 
@@ -284,7 +283,7 @@ describe("mounting one Assignment", () => {
284
283
  const { mounted } = await mount({
285
284
  failures,
286
285
  fetch: server({}),
287
- assignmentIndex: MAX_MCP_SERVERS_PER_USER_V1,
286
+ capabilityIndex: MAX_MCP_SERVERS_PER_USER_V1,
288
287
  });
289
288
 
290
289
  expect(mounted).toBe(false);
@@ -385,13 +384,13 @@ describe("the durable lifecycle a mount participates in", () => {
385
384
  });
386
385
  });
387
386
 
388
- test("the Assignment's resolution key moves with the server epoch", () => {
389
- const before = mcpAssignmentResolutionV1({
390
- assignment: { connectionId: "mcp-1" },
387
+ test("the Capability's resolution key moves with the server epoch", () => {
388
+ const before = mcpCapabilityResolutionV1({
389
+ capability: { connectionId: "mcp-1" },
391
390
  connection: connection({ safeMetadata: { serverEpoch: 1 } }),
392
391
  });
393
- const restarted = mcpAssignmentResolutionV1({
394
- assignment: { connectionId: "mcp-1" },
392
+ const restarted = mcpCapabilityResolutionV1({
393
+ capability: { connectionId: "mcp-1" },
395
394
  connection: connection({ safeMetadata: { serverEpoch: 2 } }),
396
395
  });
397
396
  expect(restarted).not.toBe(before);
package/src/agent.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The runtime Contribution: one enabled Assignment of `mcp-tools` becomes one
2
+ * The runtime Contribution: one enabled `mcp-tools` Capability becomes one
3
3
  * remote MCP server's tools in the Bot's tool registry.
4
4
  *
5
5
  * Everything the Bot ever sees of a server passes through here, and every byte
@@ -27,7 +27,7 @@ import {
27
27
  import { mcpAuthorizationRequiredV1 } from "./oauth.js";
28
28
  import { decodeOutboundMcpUrlV1 } from "./ssrf.js";
29
29
  import {
30
- mcpAssignmentResolutionKeyV1,
30
+ mcpCapabilityResolutionKeyV1,
31
31
  mcpFailureCodeV1,
32
32
  MAX_MCP_INSTRUCTIONS_BYTES_V1,
33
33
  type McpFailureCodeV1,
@@ -62,8 +62,8 @@ export const MCP_TOOL_SUBAGENT_ROLES: readonly string[] = ["executor"];
62
62
 
63
63
  /**
64
64
  * The durable per-User ceiling on remote MCP servers. Counted over the enabled
65
- * Assignments of this Package, so a Bot cannot be handed a seventeenth server
66
- * by adding one more Assignment.
65
+ * Capabilities of this Package, so a Bot cannot be handed a seventeenth server
66
+ * by enabling one more Connection.
67
67
  */
68
68
  export const MAX_MCP_SERVERS_PER_USER_V1 = 16;
69
69
 
@@ -200,18 +200,17 @@ function isObject(value: unknown): value is Record<string, unknown> {
200
200
  }
201
201
 
202
202
  export interface McpRuntimeContributionConfig {
203
- assignment: {
203
+ capability: {
204
204
  packageId: string;
205
205
  capabilityId: string;
206
206
  connectionId?: string;
207
- state: string;
208
207
  };
209
208
  /**
210
- * This Assignment's ordinal among the enabled Assignments of this Package,
209
+ * This Capability's ordinal among the enabled ones of this Package,
211
210
  * which is what makes the per-User server ceiling countable from inside a
212
- * per-Assignment factory.
211
+ * per-Capability factory.
213
212
  */
214
- assignmentIndex?: number;
213
+ capabilityIndex?: number;
215
214
  userId: string;
216
215
  readSecret(name: string): string | undefined;
217
216
  authorizeConnection(): Promise<ConnectionView>;
@@ -240,7 +239,7 @@ export interface McpRuntimeContributionConfig {
240
239
  }
241
240
 
242
241
  /**
243
- * Resolve one Assignment into a mounted runtime Plugin. The handshake and
242
+ * Resolve one enabled Capability into a mounted runtime Plugin. The handshake and
244
243
  * `tools/list` happen here, before the Plugin mounts, so a server that cannot
245
244
  * be reached contributes nothing instead of half-registering.
246
245
  */
@@ -248,23 +247,22 @@ export async function createConfiguredMcpRuntimeContribution(
248
247
  config: McpRuntimeContributionConfig,
249
248
  ): Promise<Plugin.Function | undefined> {
250
249
  if (
251
- config.assignment.packageId !== MCP_PACKAGE_ID ||
252
- config.assignment.capabilityId !== MCP_CAPABILITY_ID ||
253
- config.assignment.state !== "enabled" ||
254
- !config.assignment.connectionId
250
+ config.capability.packageId !== MCP_PACKAGE_ID ||
251
+ config.capability.capabilityId !== MCP_CAPABILITY_ID ||
252
+ !config.capability.connectionId
255
253
  ) {
256
254
  return undefined;
257
255
  }
258
- if ((config.assignmentIndex ?? 0) >= MAX_MCP_SERVERS_PER_USER_V1) {
256
+ if ((config.capabilityIndex ?? 0) >= MAX_MCP_SERVERS_PER_USER_V1) {
259
257
  config.onFailure?.(
260
- `A User may assign at most ${MAX_MCP_SERVERS_PER_USER_V1} MCP servers`,
258
+ `A User may enable at most ${MAX_MCP_SERVERS_PER_USER_V1} MCP servers`,
261
259
  );
262
260
  return undefined;
263
261
  }
264
262
  const fetchImpl = config.fetch ?? boundFetch;
265
263
  let client: McpClient | undefined;
266
264
  let lifecycle: { serverEpoch?: number; instructions?: string } = {};
267
- const connectionId = config.assignment.connectionId;
265
+ const connectionId = config.capability.connectionId;
268
266
  try {
269
267
  const connection = await config.authorizeConnection();
270
268
  lifecycle = mcpConnectionLifecycleV1(connection);
@@ -275,7 +273,7 @@ export async function createConfiguredMcpRuntimeContribution(
275
273
  const apiKey =
276
274
  connection.connectionTypeId === MCP_KEYED_CONNECTION_TYPE_ID ||
277
275
  connection.connectionTypeId === MCP_OAUTH_CONNECTION_TYPE_ID
278
- ? await openAssignedCredential(config, connection)
276
+ ? await openEnabledCredential(config, connection)
279
277
  : undefined;
280
278
  client = new McpClient({
281
279
  url: settings.url,
@@ -372,18 +370,18 @@ export async function createConfiguredMcpRuntimeContribution(
372
370
  }
373
371
 
374
372
  /**
375
- * What one Assignment of `mcp-tools` resolves to, including the server's
373
+ * What one enabled `mcp-tools` Capability resolves to, including the server's
376
374
  * `serverEpoch`. A restart bumps the epoch, so this key changes, so the next
377
375
  * admitted Turn resolves a different mount and re-handshakes — while the
378
376
  * in-flight Turn, holding the client it already mounted, is untouched.
379
377
  */
380
- export function mcpAssignmentResolutionV1(config: {
381
- assignment: { connectionId?: string };
378
+ export function mcpCapabilityResolutionV1(config: {
379
+ capability: { connectionId?: string };
382
380
  connection: { generation?: string; safeMetadata?: Record<string, unknown> };
383
381
  }): string {
384
382
  const lifecycle = mcpConnectionLifecycleV1(config.connection);
385
- return mcpAssignmentResolutionKeyV1({
386
- connectionId: config.assignment.connectionId ?? "",
383
+ return mcpCapabilityResolutionKeyV1({
384
+ connectionId: config.capability.connectionId ?? "",
387
385
  ...(config.connection.generation === undefined
388
386
  ? {}
389
387
  : { connectionGeneration: config.connection.generation }),
@@ -393,7 +391,7 @@ export function mcpAssignmentResolutionV1(config: {
393
391
  });
394
392
  }
395
393
 
396
- async function openAssignedCredential(
394
+ async function openEnabledCredential(
397
395
  config: McpRuntimeContributionConfig,
398
396
  connection: ConnectionView,
399
397
  ): Promise<string> {
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * These tools need no Connection: they read and write the User's own MCP
14
14
  * records through the host the Bot's Durable Object supplies, which is why
15
- * this Contribution mounts for a Turn rather than for an Assignment.
15
+ * this Contribution mounts for a Turn rather than for one Capability.
16
16
  */
17
17
  import type {
18
18
  Session,
@@ -7,7 +7,7 @@ import {
7
7
  decodeMcpRefusalRecordV1,
8
8
  decodeMcpServerRecordV1,
9
9
  decodeMcpServerStatusViewV1,
10
- mcpAssignmentResolutionKeyV1,
10
+ mcpCapabilityResolutionKeyV1,
11
11
  mcpConnectionMetadataV1,
12
12
  mcpFailureCodeV1,
13
13
  type McpServerRecordV1,
@@ -263,19 +263,19 @@ describe("the mount outcome that crosses back from a Bot", () => {
263
263
  });
264
264
  });
265
265
 
266
- describe("the Assignment resolution key", () => {
266
+ describe("the Capability resolution key", () => {
267
267
  test("changes when the server epoch changes, and only then", () => {
268
- const before = mcpAssignmentResolutionKeyV1({
268
+ const before = mcpCapabilityResolutionKeyV1({
269
269
  connectionId: "mcp-1",
270
270
  connectionGeneration: "gen-1",
271
271
  serverEpoch: 1,
272
272
  });
273
- const restarted = mcpAssignmentResolutionKeyV1({
273
+ const restarted = mcpCapabilityResolutionKeyV1({
274
274
  connectionId: "mcp-1",
275
275
  connectionGeneration: "gen-1",
276
276
  serverEpoch: 2,
277
277
  });
278
- const again = mcpAssignmentResolutionKeyV1({
278
+ const again = mcpCapabilityResolutionKeyV1({
279
279
  connectionId: "mcp-1",
280
280
  connectionGeneration: "gen-1",
281
281
  serverEpoch: 1,
@@ -286,13 +286,13 @@ describe("the Assignment resolution key", () => {
286
286
 
287
287
  test("changes when the Connection generation changes", () => {
288
288
  expect(
289
- mcpAssignmentResolutionKeyV1({
289
+ mcpCapabilityResolutionKeyV1({
290
290
  connectionId: "mcp-1",
291
291
  connectionGeneration: "gen-2",
292
292
  serverEpoch: 1,
293
293
  }),
294
294
  ).not.toBe(
295
- mcpAssignmentResolutionKeyV1({
295
+ mcpCapabilityResolutionKeyV1({
296
296
  connectionId: "mcp-1",
297
297
  connectionGeneration: "gen-1",
298
298
  serverEpoch: 1,
package/src/records.ts CHANGED
@@ -96,7 +96,7 @@ export interface McpServerRecordV1 {
96
96
  transport: "streamable-http" | "sse";
97
97
  instructions?: string;
98
98
  /**
99
- * Bumped by `mcp/restart`. It participates in the Assignment's resolution
99
+ * Bumped by `mcp/restart`. It participates in the Capability's resolution
100
100
  * key, so the next admitted Turn resolves a different mount and
101
101
  * re-handshakes; the in-flight Turn keeps the client it already has.
102
102
  */
@@ -653,12 +653,12 @@ export function decodeMcpMountOutcomeV1(
653
653
  }
654
654
 
655
655
  /**
656
- * What one Assignment of `mcp-tools` resolves to. The `serverEpoch` is in it,
656
+ * What one enabled `mcp-tools` Capability resolves to. The `serverEpoch` is in it,
657
657
  * which is the whole of restart semantics: a restart changes this key, the
658
658
  * next admitted Turn resolves a different mount and re-handshakes, and the
659
659
  * in-flight Turn — already holding its client — is untouched.
660
660
  */
661
- export function mcpAssignmentResolutionKeyV1(input: {
661
+ export function mcpCapabilityResolutionKeyV1(input: {
662
662
  connectionId: string;
663
663
  connectionGeneration?: string;
664
664
  serverEpoch?: number;
package/src/user.ts CHANGED
@@ -531,7 +531,7 @@ export class McpUserBackendContribution {
531
531
  if (connection.state === "revoked" || connection.state === "revoking") {
532
532
  throw new Error("MCP Connection is revoked");
533
533
  }
534
- // The epoch bump is the whole of restart: it is in the Assignment's
534
+ // The epoch bump is the whole of restart: it is in the Capability's
535
535
  // resolution key, so the next admitted Turn resolves a different
536
536
  // mount and re-handshakes, while the in-flight Turn keeps the client
537
537
  // it already holds. The handshake here refreshes the status the User
@@ -704,7 +704,7 @@ export class McpUserBackendContribution {
704
704
  // The Connection generation and the credential generation are the same
705
705
  // thing for a keyed server, and deliberately are not for an OAuth one: a
706
706
  // refresh rotates the sealed generation without disturbing the
707
- // Assignment resolution key a Turn is pinned to.
707
+ // Capability resolution key a Turn is pinned to.
708
708
  expectedGeneration: await this.currentCredentialGeneration(
709
709
  input.accountId,
710
710
  connection,
@@ -1518,8 +1518,8 @@ export class McpUserBackendContribution {
1518
1518
  { ...connection, state, failure: undefined },
1519
1519
  );
1520
1520
  // GrokBot's `RemoveMcpAccount`: the server is gone, so its record is
1521
- // gone with it. The Assignments that named it become unavailable
1522
- // tombstones through the ordinary Connection dependency path.
1521
+ // gone with it. Any Bot that was using the Connection fails closed
1522
+ // when its next Turn resolves the User's enabled capability set.
1523
1523
  await this.removeServer(connection.connectionId);
1524
1524
  return this.receipt(command.commandId, connection.connectionId);
1525
1525
  }