@topolo/mcp 0.13.2 → 0.14.1

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 CHANGED
@@ -145,6 +145,7 @@ deletion, encryption, and audit storage for the data they own.
145
145
  | `topolo_prepare_action` | credential-scoped | no |
146
146
  | `topolo_read_action` | catalog or target policy | no |
147
147
  | `topolo_call_action` | catalog or target policy | yes |
148
+ | `topolo_run_agent` | Agent runtime policy | yes |
148
149
  | `topolo_upload_action` | catalog or target policy | yes |
149
150
  | `topolo_list_workspaces` | target app runtime policy | no |
150
151
  | `topolo_create_workspace` | target app runtime policy | yes |
@@ -194,6 +195,13 @@ personal or organization context. This lets agents address many workspaces
194
195
  independently; providing only one of the pair is rejected before the request is
195
196
  sent.
196
197
 
198
+ `topolo_run_agent` is the bounded external-harness path for Codex, Claude, and
199
+ other MCP clients. It creates or replays one invocation using the caller's
200
+ stable `idempotencyKey`, polls the same invocation, and returns either its
201
+ terminal result or `state: "approval_required"` with the exact Topolo
202
+ verification URL. Calling it again with the same input resumes the durable
203
+ invocation instead of starting duplicate work.
204
+
197
205
  ## Safety rails
198
206
 
199
207
  - **No `orgId` parameter** on any tool. The organization is derived entirely
package/dist/dispatch.js CHANGED
@@ -65,6 +65,8 @@ function publicMcpErrorMessage(error, fallback) {
65
65
 
66
66
  // src/tools.ts
67
67
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
68
+ var AGENT_INVOCATIONS_CREATE = "app_topolo_agent.agent_invocations.create";
69
+ var AGENT_INVOCATIONS_GET = "app_topolo_agent.agent_invocations.get";
68
70
  function requiredString(args, name) {
69
71
  const value = typeof args[name] === "string" ? args[name].trim() : "";
70
72
  if (!value) throw new TopoloMcpPublicError(`${name} is required.`);
@@ -104,6 +106,35 @@ function coerceObjectParam(value, name) {
104
106
  }
105
107
  return candidate;
106
108
  }
109
+ function recordValue(value) {
110
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
111
+ }
112
+ function invocationFrom(value) {
113
+ const invocation = recordValue(recordValue(value)?.["invocation"]);
114
+ if (!invocation || typeof invocation["invocationId"] !== "string") {
115
+ throw new TopoloMcpPublicError("Topolo Agent returned an invalid invocation response.");
116
+ }
117
+ return invocation;
118
+ }
119
+ function agentRunResult(invocation) {
120
+ const status = typeof invocation["status"] === "string" ? invocation["status"] : "running";
121
+ const terminal = invocation["terminal"] === true;
122
+ const approval = recordValue(invocation["approval"]);
123
+ if (status === "awaiting_approval" && approval) {
124
+ return {
125
+ state: "approval_required",
126
+ invocationId: invocation["invocationId"],
127
+ verificationUrl: approval["verificationUrl"],
128
+ approval,
129
+ invocation
130
+ };
131
+ }
132
+ return {
133
+ state: terminal ? status : "running",
134
+ invocationId: invocation["invocationId"],
135
+ invocation
136
+ };
137
+ }
107
138
  var TOOLS = [
108
139
  {
109
140
  name: "topolo_whoami",
@@ -351,6 +382,64 @@ var TOOLS = [
351
382
  );
352
383
  }
353
384
  },
385
+ {
386
+ name: "topolo_run_agent",
387
+ title: "Run a Topolo agent to a durable checkpoint",
388
+ description: "Creates or replays one idempotent Topolo Agent invocation, polls its published status, and returns either the terminal result or an approval_required handoff with the exact Topolo verification URL. Reusing the same idempotencyKey never starts a duplicate invocation.",
389
+ requiredScopes: [],
390
+ destructive: true,
391
+ inputSchema: {
392
+ type: "object",
393
+ properties: {
394
+ invocation: {
395
+ type: "object",
396
+ description: "Input for app_topolo_agent.agent_invocations.create, including a stable idempotencyKey.",
397
+ additionalProperties: true
398
+ },
399
+ resourceType: { type: "string", description: "Optional resource-scoping alias for the Agent workspace." },
400
+ resourceId: { type: "string", description: "Resource id. Required when resourceType is provided." },
401
+ waitSeconds: {
402
+ type: "integer",
403
+ minimum: 0,
404
+ maximum: 120,
405
+ default: 30,
406
+ description: "Maximum time to poll. Returns state=running when the bounded wait elapses."
407
+ }
408
+ },
409
+ required: ["invocation"],
410
+ additionalProperties: false
411
+ },
412
+ handler: async (topolo, args) => {
413
+ const invocationInput = coerceObjectParam(args["invocation"], "invocation");
414
+ const idempotencyKey = typeof invocationInput["idempotencyKey"] === "string" ? invocationInput["idempotencyKey"].trim() : "";
415
+ if (!idempotencyKey) {
416
+ throw new TopoloMcpPublicError("invocation.idempotencyKey is required.");
417
+ }
418
+ const resource = optionalResourceContext(args);
419
+ const waitSeconds = typeof args["waitSeconds"] === "number" ? Math.max(0, Math.min(120, Math.floor(args["waitSeconds"]))) : 30;
420
+ const createResult = await topolo.client.callAction(
421
+ AGENT_INVOCATIONS_CREATE,
422
+ invocationInput,
423
+ { confirm: true, ...resource ? { resource } : {} }
424
+ );
425
+ let invocation = invocationFrom(createResult);
426
+ const deadline = Date.now() + waitSeconds * 1e3;
427
+ while (invocation["terminal"] !== true && invocation["status"] !== "awaiting_approval" && Date.now() < deadline) {
428
+ const recommended = typeof invocation["recommendedPollAfterSeconds"] === "number" ? invocation["recommendedPollAfterSeconds"] : 2;
429
+ const delayMs = Math.min(
430
+ Math.max(250, recommended * 1e3),
431
+ Math.max(0, deadline - Date.now())
432
+ );
433
+ if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
434
+ invocation = invocationFrom(await topolo.client.readAction(
435
+ AGENT_INVOCATIONS_GET,
436
+ { invocationId: invocation["invocationId"] },
437
+ resource ? { resource } : {}
438
+ ));
439
+ }
440
+ return agentRunResult(invocation);
441
+ }
442
+ },
354
443
  {
355
444
  name: "topolo_validate_action",
356
445
  title: "Validate Topolo action input",
package/dist/http.js CHANGED
@@ -17792,6 +17792,8 @@ function splitService(servicePart) {
17792
17792
 
17793
17793
  // src/tools.ts
17794
17794
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
17795
+ var AGENT_INVOCATIONS_CREATE = "app_topolo_agent.agent_invocations.create";
17796
+ var AGENT_INVOCATIONS_GET = "app_topolo_agent.agent_invocations.get";
17795
17797
  function requiredString(args, name) {
17796
17798
  const value = typeof args[name] === "string" ? args[name].trim() : "";
17797
17799
  if (!value) throw new TopoloMcpPublicError(`${name} is required.`);
@@ -17831,6 +17833,35 @@ function coerceObjectParam(value, name) {
17831
17833
  }
17832
17834
  return candidate;
17833
17835
  }
17836
+ function recordValue(value) {
17837
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
17838
+ }
17839
+ function invocationFrom(value) {
17840
+ const invocation = recordValue(recordValue(value)?.["invocation"]);
17841
+ if (!invocation || typeof invocation["invocationId"] !== "string") {
17842
+ throw new TopoloMcpPublicError("Topolo Agent returned an invalid invocation response.");
17843
+ }
17844
+ return invocation;
17845
+ }
17846
+ function agentRunResult(invocation) {
17847
+ const status = typeof invocation["status"] === "string" ? invocation["status"] : "running";
17848
+ const terminal = invocation["terminal"] === true;
17849
+ const approval = recordValue(invocation["approval"]);
17850
+ if (status === "awaiting_approval" && approval) {
17851
+ return {
17852
+ state: "approval_required",
17853
+ invocationId: invocation["invocationId"],
17854
+ verificationUrl: approval["verificationUrl"],
17855
+ approval,
17856
+ invocation
17857
+ };
17858
+ }
17859
+ return {
17860
+ state: terminal ? status : "running",
17861
+ invocationId: invocation["invocationId"],
17862
+ invocation
17863
+ };
17864
+ }
17834
17865
  var TOOLS = [
17835
17866
  {
17836
17867
  name: "topolo_whoami",
@@ -18078,6 +18109,64 @@ var TOOLS = [
18078
18109
  );
18079
18110
  }
18080
18111
  },
18112
+ {
18113
+ name: "topolo_run_agent",
18114
+ title: "Run a Topolo agent to a durable checkpoint",
18115
+ description: "Creates or replays one idempotent Topolo Agent invocation, polls its published status, and returns either the terminal result or an approval_required handoff with the exact Topolo verification URL. Reusing the same idempotencyKey never starts a duplicate invocation.",
18116
+ requiredScopes: [],
18117
+ destructive: true,
18118
+ inputSchema: {
18119
+ type: "object",
18120
+ properties: {
18121
+ invocation: {
18122
+ type: "object",
18123
+ description: "Input for app_topolo_agent.agent_invocations.create, including a stable idempotencyKey.",
18124
+ additionalProperties: true
18125
+ },
18126
+ resourceType: { type: "string", description: "Optional resource-scoping alias for the Agent workspace." },
18127
+ resourceId: { type: "string", description: "Resource id. Required when resourceType is provided." },
18128
+ waitSeconds: {
18129
+ type: "integer",
18130
+ minimum: 0,
18131
+ maximum: 120,
18132
+ default: 30,
18133
+ description: "Maximum time to poll. Returns state=running when the bounded wait elapses."
18134
+ }
18135
+ },
18136
+ required: ["invocation"],
18137
+ additionalProperties: false
18138
+ },
18139
+ handler: async (topolo, args) => {
18140
+ const invocationInput = coerceObjectParam(args["invocation"], "invocation");
18141
+ const idempotencyKey = typeof invocationInput["idempotencyKey"] === "string" ? invocationInput["idempotencyKey"].trim() : "";
18142
+ if (!idempotencyKey) {
18143
+ throw new TopoloMcpPublicError("invocation.idempotencyKey is required.");
18144
+ }
18145
+ const resource = optionalResourceContext(args);
18146
+ const waitSeconds = typeof args["waitSeconds"] === "number" ? Math.max(0, Math.min(120, Math.floor(args["waitSeconds"]))) : 30;
18147
+ const createResult = await topolo.client.callAction(
18148
+ AGENT_INVOCATIONS_CREATE,
18149
+ invocationInput,
18150
+ { confirm: true, ...resource ? { resource } : {} }
18151
+ );
18152
+ let invocation = invocationFrom(createResult);
18153
+ const deadline = Date.now() + waitSeconds * 1e3;
18154
+ while (invocation["terminal"] !== true && invocation["status"] !== "awaiting_approval" && Date.now() < deadline) {
18155
+ const recommended = typeof invocation["recommendedPollAfterSeconds"] === "number" ? invocation["recommendedPollAfterSeconds"] : 2;
18156
+ const delayMs = Math.min(
18157
+ Math.max(250, recommended * 1e3),
18158
+ Math.max(0, deadline - Date.now())
18159
+ );
18160
+ if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
18161
+ invocation = invocationFrom(await topolo.client.readAction(
18162
+ AGENT_INVOCATIONS_GET,
18163
+ { invocationId: invocation["invocationId"] },
18164
+ resource ? { resource } : {}
18165
+ ));
18166
+ }
18167
+ return agentRunResult(invocation);
18168
+ }
18169
+ },
18081
18170
  {
18082
18171
  name: "topolo_validate_action",
18083
18172
  title: "Validate Topolo action input",
package/dist/index.js CHANGED
@@ -4154,6 +4154,8 @@ function publicMcpErrorMessage(error, fallback) {
4154
4154
 
4155
4155
  // src/tools.ts
4156
4156
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
4157
+ var AGENT_INVOCATIONS_CREATE = "app_topolo_agent.agent_invocations.create";
4158
+ var AGENT_INVOCATIONS_GET = "app_topolo_agent.agent_invocations.get";
4157
4159
  function requiredString(args, name) {
4158
4160
  const value = typeof args[name] === "string" ? args[name].trim() : "";
4159
4161
  if (!value) throw new TopoloMcpPublicError(`${name} is required.`);
@@ -4193,6 +4195,35 @@ function coerceObjectParam(value, name) {
4193
4195
  }
4194
4196
  return candidate;
4195
4197
  }
4198
+ function recordValue(value) {
4199
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
4200
+ }
4201
+ function invocationFrom(value) {
4202
+ const invocation = recordValue(recordValue(value)?.["invocation"]);
4203
+ if (!invocation || typeof invocation["invocationId"] !== "string") {
4204
+ throw new TopoloMcpPublicError("Topolo Agent returned an invalid invocation response.");
4205
+ }
4206
+ return invocation;
4207
+ }
4208
+ function agentRunResult(invocation) {
4209
+ const status = typeof invocation["status"] === "string" ? invocation["status"] : "running";
4210
+ const terminal = invocation["terminal"] === true;
4211
+ const approval = recordValue(invocation["approval"]);
4212
+ if (status === "awaiting_approval" && approval) {
4213
+ return {
4214
+ state: "approval_required",
4215
+ invocationId: invocation["invocationId"],
4216
+ verificationUrl: approval["verificationUrl"],
4217
+ approval,
4218
+ invocation
4219
+ };
4220
+ }
4221
+ return {
4222
+ state: terminal ? status : "running",
4223
+ invocationId: invocation["invocationId"],
4224
+ invocation
4225
+ };
4226
+ }
4196
4227
  var TOOLS = [
4197
4228
  {
4198
4229
  name: "topolo_whoami",
@@ -4440,6 +4471,64 @@ var TOOLS = [
4440
4471
  );
4441
4472
  }
4442
4473
  },
4474
+ {
4475
+ name: "topolo_run_agent",
4476
+ title: "Run a Topolo agent to a durable checkpoint",
4477
+ description: "Creates or replays one idempotent Topolo Agent invocation, polls its published status, and returns either the terminal result or an approval_required handoff with the exact Topolo verification URL. Reusing the same idempotencyKey never starts a duplicate invocation.",
4478
+ requiredScopes: [],
4479
+ destructive: true,
4480
+ inputSchema: {
4481
+ type: "object",
4482
+ properties: {
4483
+ invocation: {
4484
+ type: "object",
4485
+ description: "Input for app_topolo_agent.agent_invocations.create, including a stable idempotencyKey.",
4486
+ additionalProperties: true
4487
+ },
4488
+ resourceType: { type: "string", description: "Optional resource-scoping alias for the Agent workspace." },
4489
+ resourceId: { type: "string", description: "Resource id. Required when resourceType is provided." },
4490
+ waitSeconds: {
4491
+ type: "integer",
4492
+ minimum: 0,
4493
+ maximum: 120,
4494
+ default: 30,
4495
+ description: "Maximum time to poll. Returns state=running when the bounded wait elapses."
4496
+ }
4497
+ },
4498
+ required: ["invocation"],
4499
+ additionalProperties: false
4500
+ },
4501
+ handler: async (topolo, args) => {
4502
+ const invocationInput = coerceObjectParam(args["invocation"], "invocation");
4503
+ const idempotencyKey = typeof invocationInput["idempotencyKey"] === "string" ? invocationInput["idempotencyKey"].trim() : "";
4504
+ if (!idempotencyKey) {
4505
+ throw new TopoloMcpPublicError("invocation.idempotencyKey is required.");
4506
+ }
4507
+ const resource = optionalResourceContext(args);
4508
+ const waitSeconds = typeof args["waitSeconds"] === "number" ? Math.max(0, Math.min(120, Math.floor(args["waitSeconds"]))) : 30;
4509
+ const createResult = await topolo.client.callAction(
4510
+ AGENT_INVOCATIONS_CREATE,
4511
+ invocationInput,
4512
+ { confirm: true, ...resource ? { resource } : {} }
4513
+ );
4514
+ let invocation = invocationFrom(createResult);
4515
+ const deadline = Date.now() + waitSeconds * 1e3;
4516
+ while (invocation["terminal"] !== true && invocation["status"] !== "awaiting_approval" && Date.now() < deadline) {
4517
+ const recommended = typeof invocation["recommendedPollAfterSeconds"] === "number" ? invocation["recommendedPollAfterSeconds"] : 2;
4518
+ const delayMs = Math.min(
4519
+ Math.max(250, recommended * 1e3),
4520
+ Math.max(0, deadline - Date.now())
4521
+ );
4522
+ if (delayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, delayMs));
4523
+ invocation = invocationFrom(await topolo.client.readAction(
4524
+ AGENT_INVOCATIONS_GET,
4525
+ { invocationId: invocation["invocationId"] },
4526
+ resource ? { resource } : {}
4527
+ ));
4528
+ }
4529
+ return agentRunResult(invocation);
4530
+ }
4531
+ },
4443
4532
  {
4444
4533
  name: "topolo_validate_action",
4445
4534
  title: "Validate Topolo action input",
@@ -4968,6 +5057,10 @@ function audienceFor(context) {
4968
5057
  }
4969
5058
  return `app_topolo_${service.replace(/-/g, "_")}`;
4970
5059
  }
5060
+ function scopeFor(context, audience) {
5061
+ if (context.service === "auth" && context.path === "/api/auth/me") return "app_topolo_auth:identity";
5062
+ return `${audience}:actions:execute`;
5063
+ }
4971
5064
  function responseData(value) {
4972
5065
  if (!value || typeof value !== "object") return {};
4973
5066
  const record2 = value;
@@ -4985,22 +5078,24 @@ async function resolveExternalAgentInstallationCredential(environment, installat
4985
5078
  const fetchImpl = options.fetch ?? fetch;
4986
5079
  const now = options.now ?? Date.now;
4987
5080
  const authBaseUrl = resolveServiceUrl("auth", options.serviceUrls);
4988
- const tokenUrl = new URL("/api/developer-oauth/token", authBaseUrl).toString();
5081
+ const tokenUrl = new URL("/oauth/token", authBaseUrl).toString();
4989
5082
  const cache = /* @__PURE__ */ new Map();
4990
5083
  const credentialProvider = async (context) => {
4991
5084
  const audience = audienceFor(context);
5085
+ const scope = scopeFor(context, audience);
4992
5086
  const resource = context.resource ?? {
4993
5087
  resourceType: "organization",
4994
5088
  resourceId: stored.installation.organizationId
4995
5089
  };
4996
5090
  const boundTaskId = context.taskId?.trim() || taskId;
4997
- const key = JSON.stringify({ audience, taskId: boundTaskId, resource });
5091
+ const key = JSON.stringify({ audience, scope, taskId: boundTaskId, resource });
4998
5092
  const cached = cache.get(key);
4999
5093
  if (cached && cached.expiresAt - 15e3 > now()) return cached.credential;
5000
5094
  const body = new URLSearchParams({
5001
5095
  grant_type: "client_credentials",
5002
5096
  client_id: stored.installation.clientId,
5003
5097
  client_secret: stored.secret.clientSecret,
5098
+ scope,
5004
5099
  audience,
5005
5100
  task_id: boundTaskId,
5006
5101
  resource_type: resource.resourceType,
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@topolo/mcp",
3
3
  "packageManager": "pnpm@10.17.1",
4
- "version": "0.13.2",
5
- "topoloPublishedVersion": "0.13.2",
4
+ "version": "0.14.1",
5
+ "topoloPublishedVersion": "0.14.1",
6
6
  "description": "Model Context Protocol server for the Topolo platform. Exposes scope-gated tools that third-party agents (Claude, Codex, etc.) can call natively.",
7
7
  "license": "UNLICENSED",
8
8
  "author": "Topolo",