@topolo/mcp 0.14.1 → 0.14.3

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
@@ -55,6 +55,19 @@ entry point resolved from the installed CLI dependency graph. Advanced hosts
55
55
  may execute the exported `@topolo/mcp/stdio` entry directly, but customer setup
56
56
  must not use an `npx` launcher because that makes startup network-dependent.
57
57
 
58
+ ## Protected action handoff
59
+
60
+ Local stdio action results return `topolo-secret:<id>` references instead of
61
+ bearer credentials. Pass these references unchanged to subsequent published
62
+ actions; SDK 0.21.3 resolves them internally using the OS protected store and
63
+ masks them in plans and diagnostics. Entries are bound to the verified
64
+ principal, organization, API key identity when present, and environment.
65
+ References expire after 30 minutes; accessing an expired entry deletes it.
66
+ Release finished workflow references with `topolo_release_action_secret`
67
+ and `confirm: true`. This deletes local storage only, not the upstream grant.
68
+ HTTP/Worker hosts remain redacted unless they explicitly configure a protected
69
+ SDK store; local references cannot be transferred to a different host.
70
+
58
71
  ## Supported env vars
59
72
 
60
73
  | Var | Purpose |
package/dist/dispatch.js CHANGED
@@ -63,6 +63,28 @@ function publicMcpErrorMessage(error, fallback) {
63
63
  return error instanceof TopoloMcpPublicError ? error.message : fallback;
64
64
  }
65
65
 
66
+ // src/action-secret-tools.ts
67
+ var actionSecretTools = [{
68
+ name: "topolo_release_action_secret",
69
+ title: "Release a protected action reference",
70
+ description: "Deletes only a local protected handoff reference for the verified identity and environment. Does not revoke the upstream credential. Requires confirm:true.",
71
+ requiredScopes: [],
72
+ destructive: true,
73
+ inputSchema: {
74
+ type: "object",
75
+ properties: { reference: { type: "string", minLength: 1 }, confirm: { type: "boolean" } },
76
+ required: ["reference", "confirm"],
77
+ additionalProperties: false
78
+ },
79
+ handler: async (topolo, args) => {
80
+ const reference = typeof args.reference === "string" ? args.reference.trim() : "";
81
+ if (!reference) throw new TopoloMcpPublicError("reference is required.");
82
+ if (args.confirm !== true) throw new TopoloMcpPublicError("Releasing a protected action reference requires confirm:true.");
83
+ await topolo.client.releaseActionSecret(reference);
84
+ return { released: true };
85
+ }
86
+ }];
87
+
66
88
  // src/tools.ts
67
89
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
68
90
  var AGENT_INVOCATIONS_CREATE = "app_topolo_agent.agent_invocations.create";
@@ -125,6 +147,11 @@ function agentRunResult(invocation) {
125
147
  state: "approval_required",
126
148
  invocationId: invocation["invocationId"],
127
149
  verificationUrl: approval["verificationUrl"],
150
+ continuation: {
151
+ tool: "topolo_run_agent",
152
+ reuseSameInvocation: true,
153
+ arguments: { waitForApproval: true, waitSeconds: 120 }
154
+ },
128
155
  approval,
129
156
  invocation
130
157
  };
@@ -136,6 +163,7 @@ function agentRunResult(invocation) {
136
163
  };
137
164
  }
138
165
  var TOOLS = [
166
+ ...actionSecretTools,
139
167
  {
140
168
  name: "topolo_whoami",
141
169
  title: "Describe the current Topolo credential",
@@ -385,7 +413,7 @@ var TOOLS = [
385
413
  {
386
414
  name: "topolo_run_agent",
387
415
  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.",
416
+ 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. After showing the verification URL, immediately call this tool again with the same invocation and waitForApproval=true so the terminal result returns here.",
389
417
  requiredScopes: [],
390
418
  destructive: true,
391
419
  inputSchema: {
@@ -404,6 +432,11 @@ var TOOLS = [
404
432
  maximum: 120,
405
433
  default: 30,
406
434
  description: "Maximum time to poll. Returns state=running when the bounded wait elapses."
435
+ },
436
+ waitForApproval: {
437
+ type: "boolean",
438
+ default: false,
439
+ description: "Set only when resuming an approval_required handoff already shown to the human. Keeps polling the same idempotent invocation while approval is pending."
407
440
  }
408
441
  },
409
442
  required: ["invocation"],
@@ -417,6 +450,7 @@ var TOOLS = [
417
450
  }
418
451
  const resource = optionalResourceContext(args);
419
452
  const waitSeconds = typeof args["waitSeconds"] === "number" ? Math.max(0, Math.min(120, Math.floor(args["waitSeconds"]))) : 30;
453
+ const waitForApproval = args["waitForApproval"] === true;
420
454
  const createResult = await topolo.client.callAction(
421
455
  AGENT_INVOCATIONS_CREATE,
422
456
  invocationInput,
@@ -424,7 +458,7 @@ var TOOLS = [
424
458
  );
425
459
  let invocation = invocationFrom(createResult);
426
460
  const deadline = Date.now() + waitSeconds * 1e3;
427
- while (invocation["terminal"] !== true && invocation["status"] !== "awaiting_approval" && Date.now() < deadline) {
461
+ while (invocation["terminal"] !== true && (invocation["status"] !== "awaiting_approval" || waitForApproval) && Date.now() < deadline) {
428
462
  const recommended = typeof invocation["recommendedPollAfterSeconds"] === "number" ? invocation["recommendedPollAfterSeconds"] : 2;
429
463
  const delayMs = Math.min(
430
464
  Math.max(250, recommended * 1e3),
package/dist/http.js CHANGED
@@ -17790,6 +17790,28 @@ function splitService(servicePart) {
17790
17790
  return [servicePart.slice(0, idx), servicePart.slice(idx + 1)];
17791
17791
  }
17792
17792
 
17793
+ // src/action-secret-tools.ts
17794
+ var actionSecretTools = [{
17795
+ name: "topolo_release_action_secret",
17796
+ title: "Release a protected action reference",
17797
+ description: "Deletes only a local protected handoff reference for the verified identity and environment. Does not revoke the upstream credential. Requires confirm:true.",
17798
+ requiredScopes: [],
17799
+ destructive: true,
17800
+ inputSchema: {
17801
+ type: "object",
17802
+ properties: { reference: { type: "string", minLength: 1 }, confirm: { type: "boolean" } },
17803
+ required: ["reference", "confirm"],
17804
+ additionalProperties: false
17805
+ },
17806
+ handler: async (topolo, args) => {
17807
+ const reference = typeof args.reference === "string" ? args.reference.trim() : "";
17808
+ if (!reference) throw new TopoloMcpPublicError("reference is required.");
17809
+ if (args.confirm !== true) throw new TopoloMcpPublicError("Releasing a protected action reference requires confirm:true.");
17810
+ await topolo.client.releaseActionSecret(reference);
17811
+ return { released: true };
17812
+ }
17813
+ }];
17814
+
17793
17815
  // src/tools.ts
17794
17816
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
17795
17817
  var AGENT_INVOCATIONS_CREATE = "app_topolo_agent.agent_invocations.create";
@@ -17852,6 +17874,11 @@ function agentRunResult(invocation) {
17852
17874
  state: "approval_required",
17853
17875
  invocationId: invocation["invocationId"],
17854
17876
  verificationUrl: approval["verificationUrl"],
17877
+ continuation: {
17878
+ tool: "topolo_run_agent",
17879
+ reuseSameInvocation: true,
17880
+ arguments: { waitForApproval: true, waitSeconds: 120 }
17881
+ },
17855
17882
  approval,
17856
17883
  invocation
17857
17884
  };
@@ -17863,6 +17890,7 @@ function agentRunResult(invocation) {
17863
17890
  };
17864
17891
  }
17865
17892
  var TOOLS = [
17893
+ ...actionSecretTools,
17866
17894
  {
17867
17895
  name: "topolo_whoami",
17868
17896
  title: "Describe the current Topolo credential",
@@ -18112,7 +18140,7 @@ var TOOLS = [
18112
18140
  {
18113
18141
  name: "topolo_run_agent",
18114
18142
  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.",
18143
+ 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. After showing the verification URL, immediately call this tool again with the same invocation and waitForApproval=true so the terminal result returns here.",
18116
18144
  requiredScopes: [],
18117
18145
  destructive: true,
18118
18146
  inputSchema: {
@@ -18131,6 +18159,11 @@ var TOOLS = [
18131
18159
  maximum: 120,
18132
18160
  default: 30,
18133
18161
  description: "Maximum time to poll. Returns state=running when the bounded wait elapses."
18162
+ },
18163
+ waitForApproval: {
18164
+ type: "boolean",
18165
+ default: false,
18166
+ description: "Set only when resuming an approval_required handoff already shown to the human. Keeps polling the same idempotent invocation while approval is pending."
18134
18167
  }
18135
18168
  },
18136
18169
  required: ["invocation"],
@@ -18144,6 +18177,7 @@ var TOOLS = [
18144
18177
  }
18145
18178
  const resource = optionalResourceContext(args);
18146
18179
  const waitSeconds = typeof args["waitSeconds"] === "number" ? Math.max(0, Math.min(120, Math.floor(args["waitSeconds"]))) : 30;
18180
+ const waitForApproval = args["waitForApproval"] === true;
18147
18181
  const createResult = await topolo.client.callAction(
18148
18182
  AGENT_INVOCATIONS_CREATE,
18149
18183
  invocationInput,
@@ -18151,7 +18185,7 @@ var TOOLS = [
18151
18185
  );
18152
18186
  let invocation = invocationFrom(createResult);
18153
18187
  const deadline = Date.now() + waitSeconds * 1e3;
18154
- while (invocation["terminal"] !== true && invocation["status"] !== "awaiting_approval" && Date.now() < deadline) {
18188
+ while (invocation["terminal"] !== true && (invocation["status"] !== "awaiting_approval" || waitForApproval) && Date.now() < deadline) {
18155
18189
  const recommended = typeof invocation["recommendedPollAfterSeconds"] === "number" ? invocation["recommendedPollAfterSeconds"] : 2;
18156
18190
  const delayMs = Math.min(
18157
18191
  Math.max(250, recommended * 1e3),
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.ts
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
 
6
- // node_modules/.pnpm/@topolo+sdk@0.21.2/node_modules/@topolo/sdk/dist/index.js
6
+ // node_modules/.pnpm/@topolo+sdk@0.21.3/node_modules/@topolo/sdk/dist/index.js
7
7
  function applyAuthHeaders(headers, credential) {
8
8
  if (credential.kind === "api_key") {
9
9
  headers.set("X-Api-Key", credential.apiKey);
@@ -22,54 +22,6 @@ function generateRequestId() {
22
22
  }
23
23
  return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
24
24
  }
25
- var encoder = new TextEncoder();
26
- function encodeBase64Url(value) {
27
- const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
28
- let binary = "";
29
- for (const byte of bytes) binary += String.fromCharCode(byte);
30
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
31
- }
32
- function encodeJson(value) {
33
- return encodeBase64Url(encoder.encode(JSON.stringify(value)));
34
- }
35
- async function sha256Base64Url(value) {
36
- return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
37
- }
38
- function publicJwk(privateJwk) {
39
- const { crv, kty, x, y } = privateJwk;
40
- if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
41
- throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
42
- }
43
- return { crv, kty, x, y };
44
- }
45
- function canonicalTargetUrl(value) {
46
- const url = new URL(value);
47
- return `${url.origin}${url.pathname}`;
48
- }
49
- async function createDpopProof(input) {
50
- const jwk = publicJwk(input.privateJwk);
51
- const key = await crypto.subtle.importKey(
52
- "jwk",
53
- input.privateJwk,
54
- { name: "ECDSA", namedCurve: "P-256" },
55
- false,
56
- ["sign"]
57
- );
58
- const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
59
- const claims = encodeJson({
60
- jti: crypto.randomUUID(),
61
- iat: Math.floor(Date.now() / 1e3),
62
- htm: input.method.toUpperCase(),
63
- htu: canonicalTargetUrl(input.targetUrl),
64
- ath: await sha256Base64Url(input.accessToken)
65
- });
66
- const signature = await crypto.subtle.sign(
67
- { name: "ECDSA", hash: "SHA-256" },
68
- key,
69
- encoder.encode(`${header}.${claims}`)
70
- );
71
- return `${header}.${claims}.${encodeBase64Url(signature)}`;
72
- }
73
25
  var TopoloSdkError = class extends Error {
74
26
  code;
75
27
  constructor(code, message) {
@@ -391,6 +343,131 @@ var TopoloHttpError = class extends TopoloSdkError {
391
343
  );
392
344
  }
393
345
  };
346
+ var ACTION_SECRET_PREFIX = "topolo-secret:";
347
+ function isActionSecretReference(value) {
348
+ return typeof value === "string" && value.startsWith(ACTION_SECRET_PREFIX);
349
+ }
350
+ function isRecord(value) {
351
+ return typeof value === "object" && value !== null && !Array.isArray(value);
352
+ }
353
+ function sensitive(key, value) {
354
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
355
+ if (normalized === "capability") {
356
+ if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
357
+ const text2 = value.trim();
358
+ return Boolean(text2) && !/^[a-z][a-z0-9_-]{0,31}$/.test(text2);
359
+ }
360
+ return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
361
+ }
362
+ function maskActionSecrets(value, secrets) {
363
+ let masked = value;
364
+ for (const [secret, reference] of secrets) {
365
+ const encoded = encodeURIComponent(secret);
366
+ const variants = [.../* @__PURE__ */ new Set([secret, encoded])].map((item) => item.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
367
+ masked = masked.replace(new RegExp(variants.join("|"), "g"), (match) => match === secret ? reference : encodeURIComponent(reference));
368
+ }
369
+ return masked;
370
+ }
371
+ async function resolveActionSecrets(value, store, secrets) {
372
+ if (isActionSecretReference(value)) {
373
+ if (!store) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
374
+ const resolved = await store.load(value);
375
+ if (typeof resolved === "string" && resolved) secrets.set(resolved, value);
376
+ return resolved;
377
+ }
378
+ if (Array.isArray(value)) return Promise.all(value.map((item) => resolveActionSecrets(item, store, secrets)));
379
+ if (!isRecord(value)) return value;
380
+ return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await resolveActionSecrets(item, store, secrets)])));
381
+ }
382
+ async function protectActionResult(value, store) {
383
+ const secrets = /* @__PURE__ */ new Map();
384
+ async function protect(item, key = "") {
385
+ if (isActionSecretReference(item)) return item;
386
+ if (sensitive(key, item)) {
387
+ const reference = store ? await store.save(item) : "[REDACTED]";
388
+ if (typeof item === "string" && item) secrets.set(item, reference);
389
+ return reference;
390
+ }
391
+ if (Array.isArray(item)) return Promise.all(item.map((child) => protect(child)));
392
+ if (!isRecord(item)) return item;
393
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([name, child]) => [name, await protect(child, name)])));
394
+ }
395
+ const protectedValue = await protect(value);
396
+ async function protectEmbedded(item) {
397
+ if (typeof item === "string" && !isActionSecretReference(item) && maskActionSecrets(item, secrets) !== item) {
398
+ return store ? store.save(item) : maskActionSecrets(item, secrets);
399
+ }
400
+ if (Array.isArray(item)) return Promise.all(item.map(protectEmbedded));
401
+ if (!isRecord(item)) return item;
402
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([key, child]) => [key, await protectEmbedded(child)])));
403
+ }
404
+ return protectEmbedded(protectedValue);
405
+ }
406
+ function maskActionSecretValue(value, secrets) {
407
+ if (value instanceof Error) return maskActionSecretError(value, secrets);
408
+ if (typeof value === "string") return maskActionSecrets(value, secrets);
409
+ if (Array.isArray(value)) return value.map((item) => maskActionSecretValue(item, secrets));
410
+ if (!isRecord(value)) return value;
411
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, maskActionSecretValue(item, secrets)]));
412
+ }
413
+ function maskActionSecretError(error, secrets) {
414
+ if (!(error instanceof Error)) return maskActionSecretValue(error, secrets);
415
+ const masked = Object.create(Object.getPrototypeOf(error));
416
+ for (const key of Object.getOwnPropertyNames(error)) {
417
+ const descriptor = Object.getOwnPropertyDescriptor(error, key);
418
+ if ("value" in descriptor) descriptor.value = maskActionSecretValue(descriptor.value, secrets);
419
+ Object.defineProperty(masked, key, descriptor);
420
+ }
421
+ return masked;
422
+ }
423
+ var encoder = new TextEncoder();
424
+ function encodeBase64Url(value) {
425
+ const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
426
+ let binary = "";
427
+ for (const byte of bytes) binary += String.fromCharCode(byte);
428
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
429
+ }
430
+ function encodeJson(value) {
431
+ return encodeBase64Url(encoder.encode(JSON.stringify(value)));
432
+ }
433
+ async function sha256Base64Url(value) {
434
+ return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
435
+ }
436
+ function publicJwk(privateJwk) {
437
+ const { crv, kty, x, y } = privateJwk;
438
+ if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
439
+ throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
440
+ }
441
+ return { crv, kty, x, y };
442
+ }
443
+ function canonicalTargetUrl(value) {
444
+ const url = new URL(value);
445
+ return `${url.origin}${url.pathname}`;
446
+ }
447
+ async function createDpopProof(input) {
448
+ const jwk = publicJwk(input.privateJwk);
449
+ const key = await crypto.subtle.importKey(
450
+ "jwk",
451
+ input.privateJwk,
452
+ { name: "ECDSA", namedCurve: "P-256" },
453
+ false,
454
+ ["sign"]
455
+ );
456
+ const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
457
+ const claims = encodeJson({
458
+ jti: crypto.randomUUID(),
459
+ iat: Math.floor(Date.now() / 1e3),
460
+ htm: input.method.toUpperCase(),
461
+ htu: canonicalTargetUrl(input.targetUrl),
462
+ ath: await sha256Base64Url(input.accessToken)
463
+ });
464
+ const signature = await crypto.subtle.sign(
465
+ { name: "ECDSA", hash: "SHA-256" },
466
+ key,
467
+ encoder.encode(`${header}.${claims}`)
468
+ );
469
+ return `${header}.${claims}.${encodeBase64Url(signature)}`;
470
+ }
394
471
  function hostnameHasSuffix(hostname, suffix) {
395
472
  return hostname === suffix || hostname.endsWith(`.${suffix}`);
396
473
  }
@@ -564,9 +641,9 @@ function inspectSchema(schema, path, root, depth) {
564
641
  return { path, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
565
642
  }
566
643
  const children = [];
567
- if (isRecord(schema.properties)) children.push(...Object.entries(schema.properties));
568
- if (isRecord(schema.$defs)) children.push(...Object.entries(schema.$defs));
569
- if (isRecord(schema.definitions)) children.push(...Object.entries(schema.definitions));
644
+ if (isRecord2(schema.properties)) children.push(...Object.entries(schema.properties));
645
+ if (isRecord2(schema.$defs)) children.push(...Object.entries(schema.$defs));
646
+ if (isRecord2(schema.definitions)) children.push(...Object.entries(schema.definitions));
570
647
  children.push(
571
648
  ["additionalProperties", schema.additionalProperties],
572
649
  ["propertyNames", schema.propertyNames],
@@ -576,7 +653,7 @@ function inspectSchema(schema, path, root, depth) {
576
653
  ...arraySchemaEntries("oneOf", schema.oneOf)
577
654
  );
578
655
  for (const [key, child] of children) {
579
- if (!isRecord(child)) continue;
656
+ if (!isRecord2(child)) continue;
580
657
  const issue = inspectSchema(child, `${path}.${key}`, root, depth + 1);
581
658
  if (issue) return issue;
582
659
  }
@@ -600,7 +677,7 @@ function validateNode(schema, value, path, root, errors, depth) {
600
677
  }
601
678
  if (Array.isArray(schema.allOf)) {
602
679
  for (const candidate of schema.allOf) {
603
- if (isRecord(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
680
+ if (isRecord2(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
604
681
  }
605
682
  }
606
683
  if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
@@ -630,7 +707,7 @@ function validateNode(schema, value, path, root, errors, depth) {
630
707
  if (typeof value === "string") validateString(schema, value, path, errors);
631
708
  if (typeof value === "number") validateNumber(schema, value, path, errors);
632
709
  if (Array.isArray(value)) validateArray(schema, value, path, root, errors, depth);
633
- if (isRecord(value)) validateObject(schema, value, path, root, errors, depth);
710
+ if (isRecord2(value)) validateObject(schema, value, path, root, errors, depth);
634
711
  }
635
712
  function validateObject(schema, value, path, root, errors, depth) {
636
713
  if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
@@ -639,25 +716,25 @@ function validateObject(schema, value, path, root, errors, depth) {
639
716
  if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
640
717
  errors.push({ path, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
641
718
  }
642
- const properties = isRecord(schema.properties) ? schema.properties : {};
719
+ const properties = isRecord2(schema.properties) ? schema.properties : {};
643
720
  const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
644
721
  for (const key of required) {
645
722
  if (!Object.prototype.hasOwnProperty.call(value, key)) {
646
723
  errors.push({ path: `${path}.${key}`, code: "required", message: "Required property is missing." });
647
724
  }
648
725
  }
649
- if (isRecord(schema.propertyNames)) {
726
+ if (isRecord2(schema.propertyNames)) {
650
727
  for (const key of Object.keys(value)) {
651
728
  validateNode(schema.propertyNames, key, `${path}.${key}`, root, errors, depth + 1);
652
729
  }
653
730
  }
654
731
  for (const [key, child] of Object.entries(value)) {
655
732
  const childSchema = properties[key];
656
- if (isRecord(childSchema)) {
733
+ if (isRecord2(childSchema)) {
657
734
  validateNode(childSchema, child, `${path}.${key}`, root, errors, depth + 1);
658
735
  } else if (schema.additionalProperties === false) {
659
736
  errors.push({ path: `${path}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
660
- } else if (isRecord(schema.additionalProperties)) {
737
+ } else if (isRecord2(schema.additionalProperties)) {
661
738
  validateNode(schema.additionalProperties, child, `${path}.${key}`, root, errors, depth + 1);
662
739
  }
663
740
  }
@@ -673,7 +750,7 @@ function validateArray(schema, value, path, root, errors, depth) {
673
750
  const unique = new Set(value.map((item) => JSON.stringify(item)));
674
751
  if (unique.size !== value.length) errors.push({ path, code: "unique_items", message: "Expected every item to be unique." });
675
752
  }
676
- if (isRecord(schema.items)) {
753
+ if (isRecord2(schema.items)) {
677
754
  value.forEach((item, index) => validateNode(schema.items, item, `${path}[${index}]`, root, errors, depth + 1));
678
755
  }
679
756
  }
@@ -711,7 +788,7 @@ function validateNumber(schema, value, path, errors) {
711
788
  }
712
789
  }
713
790
  function candidateMatches(candidate, value, root, depth) {
714
- if (!isRecord(candidate)) return false;
791
+ if (!isRecord2(candidate)) return false;
715
792
  const issues = [];
716
793
  validateNode(candidate, value, "$", root, issues, depth + 1);
717
794
  return issues.length === 0;
@@ -725,24 +802,24 @@ function resolveLocalReference(root, reference) {
725
802
  current = current[Number(token)];
726
803
  continue;
727
804
  }
728
- if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
805
+ if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
729
806
  current = current[token];
730
807
  }
731
- return isRecord(current) ? current : null;
808
+ return isRecord2(current) ? current : null;
732
809
  }
733
810
  function matchesType(type, value) {
734
811
  if (type === void 0) return true;
735
812
  if (Array.isArray(type)) return type.some((candidate) => matchesType(candidate, value));
736
813
  if (type === "null") return value === null;
737
814
  if (type === "array") return Array.isArray(value);
738
- if (type === "object") return isRecord(value);
815
+ if (type === "object") return isRecord2(value);
739
816
  if (type === "integer") return typeof value === "number" && Number.isInteger(value);
740
817
  return typeof value === type;
741
818
  }
742
819
  function sameJsonValue(left, right) {
743
820
  return JSON.stringify(left) === JSON.stringify(right);
744
821
  }
745
- function isRecord(value) {
822
+ function isRecord2(value) {
746
823
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
747
824
  }
748
825
  function isUrl(value) {
@@ -977,7 +1054,7 @@ function fail(path, message) {
977
1054
  throw new ActorEnvelopeValidationError(path, message);
978
1055
  }
979
1056
  function parseTopoloActionUploadContract(value) {
980
- if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
1057
+ if (!isRecord3(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
981
1058
  throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
982
1059
  }
983
1060
  const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
@@ -1056,17 +1133,17 @@ function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input, op
1056
1133
  };
1057
1134
  }
1058
1135
  function topoloUploadResponseValue(response, path) {
1059
- return path.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
1136
+ return path.split(".").reduce((value, segment) => isRecord3(value) ? value[segment] : void 0, response);
1060
1137
  }
1061
1138
  function redactTopoloUrlSessionResult(response, uploadUrlPath) {
1062
1139
  const redacted = cloneUploadValue(response);
1063
1140
  let target = redacted;
1064
1141
  const segments = uploadUrlPath.split(".");
1065
1142
  for (const segment of segments.slice(0, -1)) {
1066
- target = isRecord2(target) ? target[segment] : void 0;
1143
+ target = isRecord3(target) ? target[segment] : void 0;
1067
1144
  }
1068
1145
  const finalSegment = segments.at(-1);
1069
- if (finalSegment && isRecord2(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1146
+ if (finalSegment && isRecord3(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1070
1147
  redactUploadHeaders(redacted);
1071
1148
  return redacted;
1072
1149
  }
@@ -1090,7 +1167,7 @@ function mapUploadInput(contract, contentBase64, input, options) {
1090
1167
  }
1091
1168
  function cloneUploadValue(value) {
1092
1169
  if (Array.isArray(value)) return value.map(cloneUploadValue);
1093
- if (!isRecord2(value)) return value;
1170
+ if (!isRecord3(value)) return value;
1094
1171
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneUploadValue(item)]));
1095
1172
  }
1096
1173
  function redactUploadHeaders(value) {
@@ -1098,9 +1175,9 @@ function redactUploadHeaders(value) {
1098
1175
  value.forEach(redactUploadHeaders);
1099
1176
  return;
1100
1177
  }
1101
- if (!isRecord2(value)) return;
1178
+ if (!isRecord3(value)) return;
1102
1179
  for (const [key, item] of Object.entries(value)) {
1103
- if (key.toLowerCase() === "headers" && isRecord2(item)) {
1180
+ if (key.toLowerCase() === "headers" && isRecord3(item)) {
1104
1181
  for (const header of Object.keys(item)) {
1105
1182
  if (["authorization", "cookie", "set-cookie", "proxy-authorization", "x-api-key"].includes(header.toLowerCase())) {
1106
1183
  item[header] = "[REDACTED]";
@@ -1129,7 +1206,7 @@ function decodedBase64Size(contentBase64) {
1129
1206
  return contentBase64.length / 4 * 3 - padding;
1130
1207
  }
1131
1208
  function parseCompleteContract(value) {
1132
- if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1209
+ if (!isRecord3(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1133
1210
  return {
1134
1211
  actionId: requiredPath(value.actionId, "complete.actionId"),
1135
1212
  inputField: requiredField(value.inputField, "complete.inputField"),
@@ -1167,7 +1244,7 @@ function encodeTopoloActionBase64(bytes) {
1167
1244
  }
1168
1245
  return btoa(binary);
1169
1246
  }
1170
- function isRecord2(value) {
1247
+ function isRecord3(value) {
1171
1248
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1172
1249
  }
1173
1250
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
@@ -1178,6 +1255,8 @@ var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
1178
1255
  var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
1179
1256
  var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
1180
1257
  var TopoloClient = class _TopoloClient {
1258
+ actionSecretStore;
1259
+ actionSecrets = /* @__PURE__ */ new Map();
1181
1260
  credential;
1182
1261
  agent;
1183
1262
  actorContext;
@@ -1207,6 +1286,7 @@ var TopoloClient = class _TopoloClient {
1207
1286
  _TopoloClient.appIdCache.clear();
1208
1287
  }
1209
1288
  constructor(options) {
1289
+ this.actionSecretStore = options.actionSecretStore;
1210
1290
  if (!options.credential) throw new TopoloAuthError("credential is required");
1211
1291
  if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
1212
1292
  this.credential = options.credential;
@@ -1228,6 +1308,7 @@ var TopoloClient = class _TopoloClient {
1228
1308
  }
1229
1309
  emit(event) {
1230
1310
  if (!this.debug) return;
1311
+ event = maskActionSecretValue(event, this.actionSecrets);
1231
1312
  try {
1232
1313
  this.debug(event);
1233
1314
  } catch {
@@ -1613,7 +1694,7 @@ var TopoloClient = class _TopoloClient {
1613
1694
  }
1614
1695
  const entries = [];
1615
1696
  for (const item of raw) {
1616
- if (!isRecord3(item)) continue;
1697
+ if (!isRecord4(item)) continue;
1617
1698
  const s = item;
1618
1699
  const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
1619
1700
  if (!appId) continue;
@@ -1688,8 +1769,8 @@ var TopoloClient = class _TopoloClient {
1688
1769
  "app_topolo_auth.service_api_key_resources.list",
1689
1770
  { appId: service.appId }
1690
1771
  );
1691
- const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
1692
- if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
1772
+ const payload = isRecord4(response) && isRecord4(response.data) ? response.data : response;
1773
+ if (!isRecord4(payload) || !Array.isArray(payload.resources)) {
1693
1774
  throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
1694
1775
  }
1695
1776
  return {
@@ -1870,17 +1951,20 @@ var TopoloClient = class _TopoloClient {
1870
1951
  }
1871
1952
  async validateActionInput(action, input = {}) {
1872
1953
  const entry = await this.getAction(action);
1873
- return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input) };
1954
+ const resolved = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
1955
+ return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, resolved) };
1874
1956
  }
1875
1957
  async planAction(action, input = {}, options = {}) {
1876
1958
  const entry = await this.getAction(action);
1877
- return this.planResolvedAction(entry, input, options);
1959
+ const resolved = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
1960
+ return this.planResolvedAction(entry, resolved, options);
1878
1961
  }
1879
1962
  async prepareAction(action, input, options = {}) {
1880
1963
  const entry = await this.getAction(action);
1881
1964
  const example = (entry.agentContract?.examples ?? []).find((candidate) => Boolean(candidate?.input) && typeof candidate.input === "object" && !Array.isArray(candidate.input) && validateTopoloActionInput(entry.inputSchema, candidate.input).valid) ?? null;
1882
1965
  const selectedInput = input ?? example?.input ?? {};
1883
- const plan = this.planResolvedAction(entry, selectedInput, options);
1966
+ const resolved = await resolveActionSecrets(selectedInput, this.actionSecretStore, this.actionSecrets);
1967
+ const plan = this.planResolvedAction(entry, resolved, options);
1884
1968
  return {
1885
1969
  action: entry,
1886
1970
  example,
@@ -1917,7 +2001,7 @@ var TopoloClient = class _TopoloClient {
1917
2001
  confirmationRequired,
1918
2002
  confirmationProvided: options.confirm === true,
1919
2003
  executable: validation.valid && agentAccess !== "off" && (!confirmationRequired || options.confirm === true),
1920
- request,
2004
+ request: maskActionSecretValue(request, this.actionSecrets),
1921
2005
  verification: stringArray(entry.agentContract?.verify),
1922
2006
  rollbackActionId: pickActionString(entry.agentContract?.rollbackActionId),
1923
2007
  nextActionIds: stringArray(entry.agentContract?.nextActionIds)
@@ -1947,6 +2031,7 @@ var TopoloClient = class _TopoloClient {
1947
2031
  `Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
1948
2032
  );
1949
2033
  }
2034
+ input = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
1950
2035
  const validation = validateTopoloActionInput(entry.inputSchema, input);
1951
2036
  if (!validation.valid) {
1952
2037
  throw new TopoloSdkError(
@@ -1977,22 +2062,31 @@ var TopoloClient = class _TopoloClient {
1977
2062
  }
1978
2063
  }
1979
2064
  const service = entry.appSlug || entry.appId;
1980
- const result = await this.request({
1981
- service,
1982
- ...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
1983
- method: entry.method,
1984
- path: mapped.path,
1985
- ...mapped.query ? { query: mapped.query } : {},
1986
- ...mapped.body !== void 0 ? { body: mapped.body } : {},
1987
- ...options.resource ? { resource: options.resource } : {},
1988
- ...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
1989
- headers: {
1990
- [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
1991
- [TOPOLO_ACTION_NAME_HEADER]: entry.name
1992
- },
1993
- confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
1994
- });
1995
- return preserveSensitiveResult ? result : redactActionResult(result);
2065
+ let result;
2066
+ try {
2067
+ result = await this.request({
2068
+ service,
2069
+ ...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
2070
+ method: entry.method,
2071
+ path: mapped.path,
2072
+ ...mapped.query ? { query: mapped.query } : {},
2073
+ ...mapped.body !== void 0 ? { body: mapped.body } : {},
2074
+ ...options.resource ? { resource: options.resource } : {},
2075
+ ...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
2076
+ headers: {
2077
+ [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
2078
+ [TOPOLO_ACTION_NAME_HEADER]: entry.name
2079
+ },
2080
+ confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
2081
+ });
2082
+ } catch (error) {
2083
+ throw maskActionSecretError(error, this.actionSecrets);
2084
+ }
2085
+ return preserveSensitiveResult ? result : await protectActionResult(result, this.actionSecretStore);
2086
+ }
2087
+ async releaseActionSecret(reference) {
2088
+ if (!this.actionSecretStore) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
2089
+ await this.actionSecretStore.delete(reference);
1996
2090
  }
1997
2091
  async buildVerifiedAgentActorEnvelope(actionId, input, resource) {
1998
2092
  if (!this.actorContext) return void 0;
@@ -2139,7 +2233,7 @@ function resolveConfiguredAppId(slug, appIds) {
2139
2233
  return null;
2140
2234
  }
2141
2235
  function normalizeApiKeyResourceMetadata(input) {
2142
- if (!isRecord3(input)) return null;
2236
+ if (!isRecord4(input)) return null;
2143
2237
  const resourceType = readString(input.resourceType ?? input.resource_type);
2144
2238
  const label = readString(input.label);
2145
2239
  const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
@@ -2173,7 +2267,7 @@ function normalizeApiKeyResourceMetadataList(input) {
2173
2267
  return resources;
2174
2268
  }
2175
2269
  function normalizeBindableResource(input) {
2176
- if (!isRecord3(input)) return null;
2270
+ if (!isRecord4(input)) return null;
2177
2271
  const resourceType = readString(input.resourceType ?? input.resource_type);
2178
2272
  const resourceId = readString(input.resourceId ?? input.resource_id);
2179
2273
  if (!resourceType || !resourceId) return null;
@@ -2184,13 +2278,13 @@ function normalizeBindableResource(input) {
2184
2278
  slug: readOptionalString(input.slug),
2185
2279
  status: readOptionalString(input.status),
2186
2280
  organizationId: readOptionalString(input.organizationId ?? input.organization_id),
2187
- metadata: isRecord3(input.metadata) ? input.metadata : {}
2281
+ metadata: isRecord4(input.metadata) ? input.metadata : {}
2188
2282
  };
2189
2283
  }
2190
2284
  function normalizeResourceBindings(input) {
2191
2285
  if (!Array.isArray(input)) return [];
2192
2286
  return input.flatMap((binding) => {
2193
- if (!isRecord3(binding)) return [];
2287
+ if (!isRecord4(binding)) return [];
2194
2288
  const resourceType = readString(binding.resourceType ?? binding.resource_type);
2195
2289
  const resourceId = readString(binding.resourceId ?? binding.resource_id);
2196
2290
  return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
@@ -2203,30 +2297,9 @@ function readOptionalString(value) {
2203
2297
  const normalized = readString(value);
2204
2298
  return normalized || null;
2205
2299
  }
2206
- function isRecord3(value) {
2300
+ function isRecord4(value) {
2207
2301
  return typeof value === "object" && value !== null && !Array.isArray(value);
2208
2302
  }
2209
- function redactActionResult(value) {
2210
- if (Array.isArray(value)) return value.map(redactActionResult);
2211
- if (!isRecord3(value)) return value;
2212
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [
2213
- key,
2214
- isSensitiveActionResultKey(key, item) ? "[REDACTED]" : redactActionResult(item)
2215
- ]));
2216
- }
2217
- function isSensitiveActionResultKey(key, value) {
2218
- const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
2219
- if (normalized === "capability") return isCredentialShaped(value);
2220
- return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
2221
- }
2222
- var PLAIN_ENUM_VALUE = /^[a-z][a-z0-9_-]{0,31}$/;
2223
- function isCredentialShaped(value) {
2224
- if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
2225
- const trimmed = value.trim();
2226
- if (!trimmed) return false;
2227
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) return true;
2228
- return !PLAIN_ENUM_VALUE.test(trimmed);
2229
- }
2230
2303
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
2231
2304
  for (const serviceKey of serviceKeyCandidates2(slug)) {
2232
2305
  const configured = serviceUrls?.[serviceKey];
@@ -2296,14 +2369,14 @@ function catalogSearchQuery(options) {
2296
2369
  return query;
2297
2370
  }
2298
2371
  function normalizeCatalogPage(value) {
2299
- if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
2372
+ if (!isRecord4(value)) return { nextCursor: null, hasMore: false };
2300
2373
  return {
2301
2374
  nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
2302
2375
  hasMore: value.hasMore === true
2303
2376
  };
2304
2377
  }
2305
2378
  function normalizeServiceCatalogEntry(item) {
2306
- if (!isRecord3(item)) return null;
2379
+ if (!isRecord4(item)) return null;
2307
2380
  const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
2308
2381
  if (!appId) return null;
2309
2382
  const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
@@ -3254,11 +3327,13 @@ function createTopolo(options) {
3254
3327
  };
3255
3328
  }
3256
3329
 
3257
- // node_modules/.pnpm/@topolo+sdk@0.21.2/node_modules/@topolo/sdk/dist/node/credential-store.js
3330
+ // node_modules/.pnpm/@topolo+sdk@0.21.3/node_modules/@topolo/sdk/dist/node/credential-store.js
3258
3331
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, rm as rm2, stat } from "fs/promises";
3259
3332
  import { existsSync as existsSync2 } from "fs";
3260
3333
  import { homedir as homedir2, platform } from "os";
3261
- import { dirname as dirname2, join as join2 } from "path";
3334
+ import { dirname as dirname2, join as join3 } from "path";
3335
+ import { createHash, randomUUID } from "crypto";
3336
+ import { join as join2 } from "path";
3262
3337
  import { spawnSync } from "child_process";
3263
3338
  import { mkdir, readFile, readdir, rename, rm, writeFile, chmod } from "fs/promises";
3264
3339
  import { existsSync } from "fs";
@@ -3610,66 +3685,7 @@ var TopoloOAuth = class {
3610
3685
  return parsed;
3611
3686
  }
3612
3687
  };
3613
- var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
3614
- production: {
3615
- auth: "https://auth.topolo.app",
3616
- developers: "https://developers.topolo.app"
3617
- },
3618
- staging: {
3619
- auth: "https://auth.stg.topolo.us",
3620
- developers: "https://developers.stg.topolo.us"
3621
- },
3622
- development: {
3623
- auth: "https://auth.topolo.dev",
3624
- developers: "https://developers.topolo.dev"
3625
- },
3626
- local: {}
3627
- };
3628
- function hostnameHasSuffix2(hostname, suffix) {
3629
- return hostname === suffix || hostname.endsWith(`.${suffix}`);
3630
- }
3631
- function workersDevAccountMatches2(hostname, account) {
3632
- if (!hostname.endsWith(".workers.dev")) return false;
3633
- const labels = hostname.slice(0, -".workers.dev".length).split(".");
3634
- return labels.length >= 2 && labels.at(-1) === account;
3635
- }
3636
- function originMatchesEnvironment2(url, environment) {
3637
- const hostname = url.hostname.toLowerCase();
3638
- if (environment === "local") {
3639
- return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "[::1]";
3640
- }
3641
- if (url.protocol !== "https:") return false;
3642
- if (environment === "production") {
3643
- return hostnameHasSuffix2(hostname, "topolo.app") || hostnameHasSuffix2(hostname, "topolo.io") || hostname === "api.ol0.me" || workersDevAccountMatches2(hostname, "topolo");
3644
- }
3645
- if (environment === "staging") {
3646
- return hostnameHasSuffix2(hostname, "stg.topolo.us") || workersDevAccountMatches2(hostname, "topolo-staging");
3647
- }
3648
- return hostnameHasSuffix2(hostname, "topolo.dev") || workersDevAccountMatches2(hostname, "topolo-development");
3649
- }
3650
- function assertTopoloServiceUrlEnvironment2(service, value, environment) {
3651
- let url;
3652
- try {
3653
- url = new URL(value);
3654
- } catch {
3655
- throw new TopoloSdkError2("invalid_service_url", `Invalid URL configured for Topolo service "${service}".`);
3656
- }
3657
- if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !originMatchesEnvironment2(url, environment)) {
3658
- throw new TopoloSdkError2(
3659
- "environment_mismatch",
3660
- `Refusing Topolo service "${service}": ${url.origin} does not belong to ${environment}.`
3661
- );
3662
- }
3663
- return url.origin;
3664
- }
3665
- function topoloServiceOrigins2(serviceUrls, environment) {
3666
- return Object.fromEntries(
3667
- Object.entries(serviceUrls ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([service, value]) => [
3668
- service,
3669
- assertTopoloServiceUrlEnvironment2(service, value, environment)
3670
- ])
3671
- );
3672
- }
3688
+ var ACTION_SECRET_PREFIX2 = "topolo-secret:";
3673
3689
  var KEYCHAIN_SERVICE = "topolo-external-agent-installation";
3674
3690
  var MACOS_KEYCHAIN_CHUNK_SIZE = 96;
3675
3691
  var MACOS_KEYCHAIN_CHUNK_PREFIX = "topolo-keychain-chunks:v1:";
@@ -3712,6 +3728,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3712
3728
  const platform2 = options.platform ?? hostPlatform();
3713
3729
  const spawn = options.spawn ?? ((command, args, spawnOptions) => spawnSync(command, args, spawnOptions));
3714
3730
  const root = options.root ?? installationStorePath();
3731
+ const service = options.service ?? KEYCHAIN_SERVICE;
3715
3732
  if (platform2 === "darwin") {
3716
3733
  const write = (account, value) => runOrThrow(spawn("/usr/bin/expect", ["-c", MACOS_KEYCHAIN_WRITE_SCRIPT], {
3717
3734
  input: `${value}
@@ -3720,7 +3737,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3720
3737
  env: {
3721
3738
  ...process.env,
3722
3739
  TOPOLO_KEYCHAIN_ACCOUNT: account,
3723
- TOPOLO_KEYCHAIN_SERVICE: KEYCHAIN_SERVICE
3740
+ TOPOLO_KEYCHAIN_SERVICE: service
3724
3741
  }
3725
3742
  }), "macOS Keychain write");
3726
3743
  const read = (account) => {
@@ -3729,7 +3746,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3729
3746
  "-a",
3730
3747
  account,
3731
3748
  "-s",
3732
- KEYCHAIN_SERVICE,
3749
+ service,
3733
3750
  "-w"
3734
3751
  ], { encoding: "utf8" });
3735
3752
  if (result.status === 44) return null;
@@ -3741,7 +3758,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3741
3758
  "-a",
3742
3759
  account,
3743
3760
  "-s",
3744
- KEYCHAIN_SERVICE
3761
+ service
3745
3762
  ], { encoding: "utf8" });
3746
3763
  if (result.status !== 44) runOrThrow(result, "macOS Keychain delete");
3747
3764
  };
@@ -3788,9 +3805,9 @@ function createPlatformInstallationSecretBackend(options = {}) {
3788
3805
  async set(account, value) {
3789
3806
  runOrThrow(spawn("secret-tool", [
3790
3807
  "store",
3791
- "--label=Topolo external agent installation",
3808
+ "--label=Topolo protected credential",
3792
3809
  "service",
3793
- KEYCHAIN_SERVICE,
3810
+ service,
3794
3811
  "account",
3795
3812
  account
3796
3813
  ], { input: value, encoding: "utf8" }), "Linux Secret Service write");
@@ -3799,7 +3816,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3799
3816
  const result = spawn("secret-tool", [
3800
3817
  "lookup",
3801
3818
  "service",
3802
- KEYCHAIN_SERVICE,
3819
+ service,
3803
3820
  "account",
3804
3821
  account
3805
3822
  ], { encoding: "utf8" });
@@ -3810,7 +3827,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3810
3827
  const result = spawn("secret-tool", [
3811
3828
  "clear",
3812
3829
  "service",
3813
- KEYCHAIN_SERVICE,
3830
+ service,
3814
3831
  "account",
3815
3832
  account
3816
3833
  ], { encoding: "utf8" });
@@ -3853,12 +3870,119 @@ async function readExternalAgentInstallation(environment, id, backend = createPl
3853
3870
  if (!encodedSecret) throw new Error(`Protected secret is missing for installation ${id}.`);
3854
3871
  return { installation, secret: JSON.parse(encodedSecret) };
3855
3872
  }
3873
+ function createProtectedActionSecretStore(options) {
3874
+ const backend = options.backend ?? createPlatformInstallationSecretBackend({
3875
+ service: "topolo-action-handoff",
3876
+ root: join2(installationStorePath(), "action-handoff")
3877
+ });
3878
+ const now = options.now ?? Date.now;
3879
+ async function account(reference) {
3880
+ if (!/^topolo-secret:[0-9a-f-]{36}$/.test(reference)) throw new TopoloSdkError2("invalid_action_secret_reference", "Invalid protected action reference.");
3881
+ const identity = await options.identity();
3882
+ if (!identity.user.id) throw new TopoloSdkError2("action_secret_identity_required", "A verified identity is required for protected action handoff.");
3883
+ if (identity.kind === "api_key" && !identity.apiKey?.id) throw new TopoloSdkError2("action_secret_identity_required", "A verified API key identity is required for protected action handoff.");
3884
+ const scope = createHash("sha256").update(JSON.stringify([
3885
+ options.environment,
3886
+ identity.user.principalId ?? identity.user.id,
3887
+ identity.organization?.id ?? null,
3888
+ identity.apiKey?.id ?? null
3889
+ ])).digest("hex");
3890
+ return `${scope}-${reference.slice(ACTION_SECRET_PREFIX2.length)}`;
3891
+ }
3892
+ return {
3893
+ async save(value) {
3894
+ const reference = `${ACTION_SECRET_PREFIX2}${randomUUID()}`;
3895
+ await backend.set(await account(reference), JSON.stringify({ value, expiresAt: now() + 30 * 60 * 1e3 }));
3896
+ return reference;
3897
+ },
3898
+ async load(reference) {
3899
+ const key = await account(reference);
3900
+ const encoded = await backend.get(key);
3901
+ if (!encoded) throw new TopoloSdkError2("action_secret_unavailable", "Protected action reference is missing or belongs to another identity or environment.");
3902
+ let record2;
3903
+ try {
3904
+ record2 = JSON.parse(encoded);
3905
+ } catch {
3906
+ throw new TopoloSdkError2("action_secret_invalid", "Protected action reference is corrupt.");
3907
+ }
3908
+ if (!Number.isFinite(record2?.expiresAt) || !Object.hasOwn(record2, "value")) throw new TopoloSdkError2("action_secret_invalid", "Protected action reference is corrupt.");
3909
+ if (record2.expiresAt <= now()) {
3910
+ await backend.delete(key);
3911
+ throw new TopoloSdkError2("action_secret_expired", "Protected action reference expired. Repeat the originating action if its contract permits.");
3912
+ }
3913
+ return record2.value;
3914
+ },
3915
+ async delete(reference) {
3916
+ await backend.delete(await account(reference));
3917
+ }
3918
+ };
3919
+ }
3920
+ var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
3921
+ production: {
3922
+ auth: "https://auth.topolo.app",
3923
+ developers: "https://developers.topolo.app"
3924
+ },
3925
+ staging: {
3926
+ auth: "https://auth.stg.topolo.us",
3927
+ developers: "https://developers.stg.topolo.us"
3928
+ },
3929
+ development: {
3930
+ auth: "https://auth.topolo.dev",
3931
+ developers: "https://developers.topolo.dev"
3932
+ },
3933
+ local: {}
3934
+ };
3935
+ function hostnameHasSuffix2(hostname, suffix) {
3936
+ return hostname === suffix || hostname.endsWith(`.${suffix}`);
3937
+ }
3938
+ function workersDevAccountMatches2(hostname, account) {
3939
+ if (!hostname.endsWith(".workers.dev")) return false;
3940
+ const labels = hostname.slice(0, -".workers.dev".length).split(".");
3941
+ return labels.length >= 2 && labels.at(-1) === account;
3942
+ }
3943
+ function originMatchesEnvironment2(url, environment) {
3944
+ const hostname = url.hostname.toLowerCase();
3945
+ if (environment === "local") {
3946
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "[::1]";
3947
+ }
3948
+ if (url.protocol !== "https:") return false;
3949
+ if (environment === "production") {
3950
+ return hostnameHasSuffix2(hostname, "topolo.app") || hostnameHasSuffix2(hostname, "topolo.io") || hostname === "api.ol0.me" || workersDevAccountMatches2(hostname, "topolo");
3951
+ }
3952
+ if (environment === "staging") {
3953
+ return hostnameHasSuffix2(hostname, "stg.topolo.us") || workersDevAccountMatches2(hostname, "topolo-staging");
3954
+ }
3955
+ return hostnameHasSuffix2(hostname, "topolo.dev") || workersDevAccountMatches2(hostname, "topolo-development");
3956
+ }
3957
+ function assertTopoloServiceUrlEnvironment2(service, value, environment) {
3958
+ let url;
3959
+ try {
3960
+ url = new URL(value);
3961
+ } catch {
3962
+ throw new TopoloSdkError2("invalid_service_url", `Invalid URL configured for Topolo service "${service}".`);
3963
+ }
3964
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !originMatchesEnvironment2(url, environment)) {
3965
+ throw new TopoloSdkError2(
3966
+ "environment_mismatch",
3967
+ `Refusing Topolo service "${service}": ${url.origin} does not belong to ${environment}.`
3968
+ );
3969
+ }
3970
+ return url.origin;
3971
+ }
3972
+ function topoloServiceOrigins2(serviceUrls, environment) {
3973
+ return Object.fromEntries(
3974
+ Object.entries(serviceUrls ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([service, value]) => [
3975
+ service,
3976
+ assertTopoloServiceUrlEnvironment2(service, value, environment)
3977
+ ])
3978
+ );
3979
+ }
3856
3980
  var ENVIRONMENT_DEFAULT_SERVICE_URLS = TOPOLO_ENVIRONMENT_SERVICE_URLS;
3857
3981
  function configPath() {
3858
3982
  const override = process.env.TOPOLO_CONFIG_PATH;
3859
3983
  if (override) return override;
3860
- const base = process.env.XDG_CONFIG_HOME || (platform() === "win32" ? join2(homedir2(), "AppData", "Roaming") : join2(homedir2(), ".config"));
3861
- return join2(base, "topolo", "config.json");
3984
+ const base = process.env.XDG_CONFIG_HOME || (platform() === "win32" ? join3(homedir2(), "AppData", "Roaming") : join3(homedir2(), ".config"));
3985
+ return join3(base, "topolo", "config.json");
3862
3986
  }
3863
3987
  async function readConfig() {
3864
3988
  const path = configPath();
@@ -4152,6 +4276,28 @@ function publicMcpErrorMessage(error, fallback) {
4152
4276
  return error instanceof TopoloMcpPublicError ? error.message : fallback;
4153
4277
  }
4154
4278
 
4279
+ // src/action-secret-tools.ts
4280
+ var actionSecretTools = [{
4281
+ name: "topolo_release_action_secret",
4282
+ title: "Release a protected action reference",
4283
+ description: "Deletes only a local protected handoff reference for the verified identity and environment. Does not revoke the upstream credential. Requires confirm:true.",
4284
+ requiredScopes: [],
4285
+ destructive: true,
4286
+ inputSchema: {
4287
+ type: "object",
4288
+ properties: { reference: { type: "string", minLength: 1 }, confirm: { type: "boolean" } },
4289
+ required: ["reference", "confirm"],
4290
+ additionalProperties: false
4291
+ },
4292
+ handler: async (topolo, args) => {
4293
+ const reference = typeof args.reference === "string" ? args.reference.trim() : "";
4294
+ if (!reference) throw new TopoloMcpPublicError("reference is required.");
4295
+ if (args.confirm !== true) throw new TopoloMcpPublicError("Releasing a protected action reference requires confirm:true.");
4296
+ await topolo.client.releaseActionSecret(reference);
4297
+ return { released: true };
4298
+ }
4299
+ }];
4300
+
4155
4301
  // src/tools.ts
4156
4302
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
4157
4303
  var AGENT_INVOCATIONS_CREATE = "app_topolo_agent.agent_invocations.create";
@@ -4214,6 +4360,11 @@ function agentRunResult(invocation) {
4214
4360
  state: "approval_required",
4215
4361
  invocationId: invocation["invocationId"],
4216
4362
  verificationUrl: approval["verificationUrl"],
4363
+ continuation: {
4364
+ tool: "topolo_run_agent",
4365
+ reuseSameInvocation: true,
4366
+ arguments: { waitForApproval: true, waitSeconds: 120 }
4367
+ },
4217
4368
  approval,
4218
4369
  invocation
4219
4370
  };
@@ -4225,6 +4376,7 @@ function agentRunResult(invocation) {
4225
4376
  };
4226
4377
  }
4227
4378
  var TOOLS = [
4379
+ ...actionSecretTools,
4228
4380
  {
4229
4381
  name: "topolo_whoami",
4230
4382
  title: "Describe the current Topolo credential",
@@ -4474,7 +4626,7 @@ var TOOLS = [
4474
4626
  {
4475
4627
  name: "topolo_run_agent",
4476
4628
  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.",
4629
+ 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. After showing the verification URL, immediately call this tool again with the same invocation and waitForApproval=true so the terminal result returns here.",
4478
4630
  requiredScopes: [],
4479
4631
  destructive: true,
4480
4632
  inputSchema: {
@@ -4493,6 +4645,11 @@ var TOOLS = [
4493
4645
  maximum: 120,
4494
4646
  default: 30,
4495
4647
  description: "Maximum time to poll. Returns state=running when the bounded wait elapses."
4648
+ },
4649
+ waitForApproval: {
4650
+ type: "boolean",
4651
+ default: false,
4652
+ description: "Set only when resuming an approval_required handoff already shown to the human. Keeps polling the same idempotent invocation while approval is pending."
4496
4653
  }
4497
4654
  },
4498
4655
  required: ["invocation"],
@@ -4506,6 +4663,7 @@ var TOOLS = [
4506
4663
  }
4507
4664
  const resource = optionalResourceContext(args);
4508
4665
  const waitSeconds = typeof args["waitSeconds"] === "number" ? Math.max(0, Math.min(120, Math.floor(args["waitSeconds"]))) : 30;
4666
+ const waitForApproval = args["waitForApproval"] === true;
4509
4667
  const createResult = await topolo.client.callAction(
4510
4668
  AGENT_INVOCATIONS_CREATE,
4511
4669
  invocationInput,
@@ -4513,7 +4671,7 @@ var TOOLS = [
4513
4671
  );
4514
4672
  let invocation = invocationFrom(createResult);
4515
4673
  const deadline = Date.now() + waitSeconds * 1e3;
4516
- while (invocation["terminal"] !== true && invocation["status"] !== "awaiting_approval" && Date.now() < deadline) {
4674
+ while (invocation["terminal"] !== true && (invocation["status"] !== "awaiting_approval" || waitForApproval) && Date.now() < deadline) {
4517
4675
  const recommended = typeof invocation["recommendedPollAfterSeconds"] === "number" ? invocation["recommendedPollAfterSeconds"] : 2;
4518
4676
  const delayMs = Math.min(
4519
4677
  Math.max(250, recommended * 1e3),
@@ -4901,14 +5059,14 @@ function displayAppId(entry) {
4901
5059
 
4902
5060
  // src/version.ts
4903
5061
  import { readFileSync } from "fs";
4904
- import { dirname as dirname3, join as join3 } from "path";
5062
+ import { dirname as dirname3, join as join4 } from "path";
4905
5063
  import { fileURLToPath } from "url";
4906
5064
  function readMcpVersion() {
4907
5065
  try {
4908
5066
  const here = dirname3(fileURLToPath(import.meta.url));
4909
5067
  const candidates = [
4910
- join3(here, "package.json"),
4911
- join3(here, "..", "package.json")
5068
+ join4(here, "package.json"),
5069
+ join4(here, "..", "package.json")
4912
5070
  ];
4913
5071
  for (const path of candidates) {
4914
5072
  try {
@@ -5031,7 +5189,7 @@ function toolError(context, error) {
5031
5189
  }
5032
5190
 
5033
5191
  // src/context-credential.ts
5034
- import { randomUUID } from "crypto";
5192
+ import { randomUUID as randomUUID2 } from "crypto";
5035
5193
 
5036
5194
  // src/auth.ts
5037
5195
  function resolveCredentialFromEnv(environment = "production") {
@@ -5175,7 +5333,7 @@ async function resolveMcpCredential(environment, selector = process.env.TOPOLO_C
5175
5333
  "TOPOLO_AGENT_INSTALLATION_ID cannot be combined with TOPOLO_CONTEXT or an environment credential"
5176
5334
  );
5177
5335
  }
5178
- const taskId = taskSelector?.trim() || `task_${randomUUID()}`;
5336
+ const taskId = taskSelector?.trim() || `task_${randomUUID2()}`;
5179
5337
  const config2 = await dependencies.readConfig();
5180
5338
  const serviceUrls = resolveServiceUrls(
5181
5339
  profileForEnvironment(config2, environment).serviceUrls,
@@ -5274,6 +5432,7 @@ var DEFAULT_DEPENDENCIES2 = {
5274
5432
  resolveAgentName,
5275
5433
  resolveServiceUrls,
5276
5434
  createTopolo,
5435
+ createProtectedActionSecretStore,
5277
5436
  buildServer,
5278
5437
  createTransport: createStdioTransport,
5279
5438
  advertisedToolNames,
@@ -5296,6 +5455,7 @@ async function runStdioMcp(dependencies = DEFAULT_DEPENDENCIES2) {
5296
5455
  const topolo = dependencies.createTopolo({
5297
5456
  credential,
5298
5457
  environment,
5458
+ actionSecretStore: dependencies.createProtectedActionSecretStore({ environment, identity: () => topolo.client.introspect() }),
5299
5459
  actorContext: {
5300
5460
  channel: "mcp",
5301
5461
  ...resolvedCredential.taskId ? { taskId: resolvedCredential.taskId } : {}
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.14.1",
5
- "topoloPublishedVersion": "0.14.1",
4
+ "version": "0.14.3",
5
+ "topoloPublishedVersion": "0.14.3",
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",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@modelcontextprotocol/sdk": "^1.0.0",
39
- "@topolo/sdk": "0.21.2"
39
+ "@topolo/sdk": "0.21.3"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^20.12.0",