@topolo/mcp 0.14.2 → 0.14.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/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.4 resolves nonempty string credentials 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";
@@ -141,6 +163,7 @@ function agentRunResult(invocation) {
141
163
  };
142
164
  }
143
165
  var TOOLS = [
166
+ ...actionSecretTools,
144
167
  {
145
168
  name: "topolo_whoami",
146
169
  title: "Describe the current Topolo credential",
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";
@@ -17868,6 +17890,7 @@ function agentRunResult(invocation) {
17868
17890
  };
17869
17891
  }
17870
17892
  var TOOLS = [
17893
+ ...actionSecretTools,
17871
17894
  {
17872
17895
  name: "topolo_whoami",
17873
17896
  title: "Describe the current Topolo credential",
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.4/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,132 @@ 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) throw new TopoloSdkError("action_secret_unsupported_type", "Protected action handoff requires a nonempty string credential.");
376
+ secrets.set(resolved, value);
377
+ return resolved;
378
+ }
379
+ if (Array.isArray(value)) return Promise.all(value.map((item) => resolveActionSecrets(item, store, secrets)));
380
+ if (!isRecord(value)) return value;
381
+ return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await resolveActionSecrets(item, store, secrets)])));
382
+ }
383
+ async function protectActionResult(value, store) {
384
+ const secrets = /* @__PURE__ */ new Map();
385
+ async function protect(item, key = "") {
386
+ if (isActionSecretReference(item)) return item;
387
+ if (sensitive(key, item)) {
388
+ const reference = store ? await store.save(item) : "[REDACTED]";
389
+ if (typeof item === "string" && item) secrets.set(item, reference);
390
+ return reference;
391
+ }
392
+ if (Array.isArray(item)) return Promise.all(item.map((child) => protect(child)));
393
+ if (!isRecord(item)) return item;
394
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([name, child]) => [name, await protect(child, name)])));
395
+ }
396
+ const protectedValue = await protect(value);
397
+ async function protectEmbedded(item) {
398
+ if (typeof item === "string" && !isActionSecretReference(item) && maskActionSecrets(item, secrets) !== item) {
399
+ return store ? store.save(item) : maskActionSecrets(item, secrets);
400
+ }
401
+ if (Array.isArray(item)) return Promise.all(item.map(protectEmbedded));
402
+ if (!isRecord(item)) return item;
403
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([key, child]) => [key, await protectEmbedded(child)])));
404
+ }
405
+ return protectEmbedded(protectedValue);
406
+ }
407
+ function maskActionSecretValue(value, secrets) {
408
+ if (value instanceof Error) return maskActionSecretError(value, secrets);
409
+ if (typeof value === "string") return maskActionSecrets(value, secrets);
410
+ if (Array.isArray(value)) return value.map((item) => maskActionSecretValue(item, secrets));
411
+ if (!isRecord(value)) return value;
412
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, maskActionSecretValue(item, secrets)]));
413
+ }
414
+ function maskActionSecretError(error, secrets) {
415
+ if (!(error instanceof Error)) return maskActionSecretValue(error, secrets);
416
+ const masked = Object.create(Object.getPrototypeOf(error));
417
+ for (const key of Object.getOwnPropertyNames(error)) {
418
+ const descriptor = Object.getOwnPropertyDescriptor(error, key);
419
+ if ("value" in descriptor) descriptor.value = maskActionSecretValue(descriptor.value, secrets);
420
+ Object.defineProperty(masked, key, descriptor);
421
+ }
422
+ return masked;
423
+ }
424
+ var encoder = new TextEncoder();
425
+ function encodeBase64Url(value) {
426
+ const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
427
+ let binary = "";
428
+ for (const byte of bytes) binary += String.fromCharCode(byte);
429
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
430
+ }
431
+ function encodeJson(value) {
432
+ return encodeBase64Url(encoder.encode(JSON.stringify(value)));
433
+ }
434
+ async function sha256Base64Url(value) {
435
+ return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
436
+ }
437
+ function publicJwk(privateJwk) {
438
+ const { crv, kty, x, y } = privateJwk;
439
+ if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
440
+ throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
441
+ }
442
+ return { crv, kty, x, y };
443
+ }
444
+ function canonicalTargetUrl(value) {
445
+ const url = new URL(value);
446
+ return `${url.origin}${url.pathname}`;
447
+ }
448
+ async function createDpopProof(input) {
449
+ const jwk = publicJwk(input.privateJwk);
450
+ const key = await crypto.subtle.importKey(
451
+ "jwk",
452
+ input.privateJwk,
453
+ { name: "ECDSA", namedCurve: "P-256" },
454
+ false,
455
+ ["sign"]
456
+ );
457
+ const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
458
+ const claims = encodeJson({
459
+ jti: crypto.randomUUID(),
460
+ iat: Math.floor(Date.now() / 1e3),
461
+ htm: input.method.toUpperCase(),
462
+ htu: canonicalTargetUrl(input.targetUrl),
463
+ ath: await sha256Base64Url(input.accessToken)
464
+ });
465
+ const signature = await crypto.subtle.sign(
466
+ { name: "ECDSA", hash: "SHA-256" },
467
+ key,
468
+ encoder.encode(`${header}.${claims}`)
469
+ );
470
+ return `${header}.${claims}.${encodeBase64Url(signature)}`;
471
+ }
394
472
  function hostnameHasSuffix(hostname, suffix) {
395
473
  return hostname === suffix || hostname.endsWith(`.${suffix}`);
396
474
  }
@@ -564,9 +642,9 @@ function inspectSchema(schema, path, root, depth) {
564
642
  return { path, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
565
643
  }
566
644
  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));
645
+ if (isRecord2(schema.properties)) children.push(...Object.entries(schema.properties));
646
+ if (isRecord2(schema.$defs)) children.push(...Object.entries(schema.$defs));
647
+ if (isRecord2(schema.definitions)) children.push(...Object.entries(schema.definitions));
570
648
  children.push(
571
649
  ["additionalProperties", schema.additionalProperties],
572
650
  ["propertyNames", schema.propertyNames],
@@ -576,7 +654,7 @@ function inspectSchema(schema, path, root, depth) {
576
654
  ...arraySchemaEntries("oneOf", schema.oneOf)
577
655
  );
578
656
  for (const [key, child] of children) {
579
- if (!isRecord(child)) continue;
657
+ if (!isRecord2(child)) continue;
580
658
  const issue = inspectSchema(child, `${path}.${key}`, root, depth + 1);
581
659
  if (issue) return issue;
582
660
  }
@@ -600,7 +678,7 @@ function validateNode(schema, value, path, root, errors, depth) {
600
678
  }
601
679
  if (Array.isArray(schema.allOf)) {
602
680
  for (const candidate of schema.allOf) {
603
- if (isRecord(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
681
+ if (isRecord2(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
604
682
  }
605
683
  }
606
684
  if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
@@ -630,7 +708,7 @@ function validateNode(schema, value, path, root, errors, depth) {
630
708
  if (typeof value === "string") validateString(schema, value, path, errors);
631
709
  if (typeof value === "number") validateNumber(schema, value, path, errors);
632
710
  if (Array.isArray(value)) validateArray(schema, value, path, root, errors, depth);
633
- if (isRecord(value)) validateObject(schema, value, path, root, errors, depth);
711
+ if (isRecord2(value)) validateObject(schema, value, path, root, errors, depth);
634
712
  }
635
713
  function validateObject(schema, value, path, root, errors, depth) {
636
714
  if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
@@ -639,25 +717,25 @@ function validateObject(schema, value, path, root, errors, depth) {
639
717
  if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
640
718
  errors.push({ path, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
641
719
  }
642
- const properties = isRecord(schema.properties) ? schema.properties : {};
720
+ const properties = isRecord2(schema.properties) ? schema.properties : {};
643
721
  const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
644
722
  for (const key of required) {
645
723
  if (!Object.prototype.hasOwnProperty.call(value, key)) {
646
724
  errors.push({ path: `${path}.${key}`, code: "required", message: "Required property is missing." });
647
725
  }
648
726
  }
649
- if (isRecord(schema.propertyNames)) {
727
+ if (isRecord2(schema.propertyNames)) {
650
728
  for (const key of Object.keys(value)) {
651
729
  validateNode(schema.propertyNames, key, `${path}.${key}`, root, errors, depth + 1);
652
730
  }
653
731
  }
654
732
  for (const [key, child] of Object.entries(value)) {
655
733
  const childSchema = properties[key];
656
- if (isRecord(childSchema)) {
734
+ if (isRecord2(childSchema)) {
657
735
  validateNode(childSchema, child, `${path}.${key}`, root, errors, depth + 1);
658
736
  } else if (schema.additionalProperties === false) {
659
737
  errors.push({ path: `${path}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
660
- } else if (isRecord(schema.additionalProperties)) {
738
+ } else if (isRecord2(schema.additionalProperties)) {
661
739
  validateNode(schema.additionalProperties, child, `${path}.${key}`, root, errors, depth + 1);
662
740
  }
663
741
  }
@@ -673,7 +751,7 @@ function validateArray(schema, value, path, root, errors, depth) {
673
751
  const unique = new Set(value.map((item) => JSON.stringify(item)));
674
752
  if (unique.size !== value.length) errors.push({ path, code: "unique_items", message: "Expected every item to be unique." });
675
753
  }
676
- if (isRecord(schema.items)) {
754
+ if (isRecord2(schema.items)) {
677
755
  value.forEach((item, index) => validateNode(schema.items, item, `${path}[${index}]`, root, errors, depth + 1));
678
756
  }
679
757
  }
@@ -711,7 +789,7 @@ function validateNumber(schema, value, path, errors) {
711
789
  }
712
790
  }
713
791
  function candidateMatches(candidate, value, root, depth) {
714
- if (!isRecord(candidate)) return false;
792
+ if (!isRecord2(candidate)) return false;
715
793
  const issues = [];
716
794
  validateNode(candidate, value, "$", root, issues, depth + 1);
717
795
  return issues.length === 0;
@@ -725,24 +803,24 @@ function resolveLocalReference(root, reference) {
725
803
  current = current[Number(token)];
726
804
  continue;
727
805
  }
728
- if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
806
+ if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
729
807
  current = current[token];
730
808
  }
731
- return isRecord(current) ? current : null;
809
+ return isRecord2(current) ? current : null;
732
810
  }
733
811
  function matchesType(type, value) {
734
812
  if (type === void 0) return true;
735
813
  if (Array.isArray(type)) return type.some((candidate) => matchesType(candidate, value));
736
814
  if (type === "null") return value === null;
737
815
  if (type === "array") return Array.isArray(value);
738
- if (type === "object") return isRecord(value);
816
+ if (type === "object") return isRecord2(value);
739
817
  if (type === "integer") return typeof value === "number" && Number.isInteger(value);
740
818
  return typeof value === type;
741
819
  }
742
820
  function sameJsonValue(left, right) {
743
821
  return JSON.stringify(left) === JSON.stringify(right);
744
822
  }
745
- function isRecord(value) {
823
+ function isRecord2(value) {
746
824
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
747
825
  }
748
826
  function isUrl(value) {
@@ -977,7 +1055,7 @@ function fail(path, message) {
977
1055
  throw new ActorEnvelopeValidationError(path, message);
978
1056
  }
979
1057
  function parseTopoloActionUploadContract(value) {
980
- if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
1058
+ if (!isRecord3(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
981
1059
  throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
982
1060
  }
983
1061
  const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
@@ -1056,17 +1134,17 @@ function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input, op
1056
1134
  };
1057
1135
  }
1058
1136
  function topoloUploadResponseValue(response, path) {
1059
- return path.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
1137
+ return path.split(".").reduce((value, segment) => isRecord3(value) ? value[segment] : void 0, response);
1060
1138
  }
1061
1139
  function redactTopoloUrlSessionResult(response, uploadUrlPath) {
1062
1140
  const redacted = cloneUploadValue(response);
1063
1141
  let target = redacted;
1064
1142
  const segments = uploadUrlPath.split(".");
1065
1143
  for (const segment of segments.slice(0, -1)) {
1066
- target = isRecord2(target) ? target[segment] : void 0;
1144
+ target = isRecord3(target) ? target[segment] : void 0;
1067
1145
  }
1068
1146
  const finalSegment = segments.at(-1);
1069
- if (finalSegment && isRecord2(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1147
+ if (finalSegment && isRecord3(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1070
1148
  redactUploadHeaders(redacted);
1071
1149
  return redacted;
1072
1150
  }
@@ -1090,7 +1168,7 @@ function mapUploadInput(contract, contentBase64, input, options) {
1090
1168
  }
1091
1169
  function cloneUploadValue(value) {
1092
1170
  if (Array.isArray(value)) return value.map(cloneUploadValue);
1093
- if (!isRecord2(value)) return value;
1171
+ if (!isRecord3(value)) return value;
1094
1172
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneUploadValue(item)]));
1095
1173
  }
1096
1174
  function redactUploadHeaders(value) {
@@ -1098,9 +1176,9 @@ function redactUploadHeaders(value) {
1098
1176
  value.forEach(redactUploadHeaders);
1099
1177
  return;
1100
1178
  }
1101
- if (!isRecord2(value)) return;
1179
+ if (!isRecord3(value)) return;
1102
1180
  for (const [key, item] of Object.entries(value)) {
1103
- if (key.toLowerCase() === "headers" && isRecord2(item)) {
1181
+ if (key.toLowerCase() === "headers" && isRecord3(item)) {
1104
1182
  for (const header of Object.keys(item)) {
1105
1183
  if (["authorization", "cookie", "set-cookie", "proxy-authorization", "x-api-key"].includes(header.toLowerCase())) {
1106
1184
  item[header] = "[REDACTED]";
@@ -1129,7 +1207,7 @@ function decodedBase64Size(contentBase64) {
1129
1207
  return contentBase64.length / 4 * 3 - padding;
1130
1208
  }
1131
1209
  function parseCompleteContract(value) {
1132
- if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1210
+ if (!isRecord3(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1133
1211
  return {
1134
1212
  actionId: requiredPath(value.actionId, "complete.actionId"),
1135
1213
  inputField: requiredField(value.inputField, "complete.inputField"),
@@ -1167,7 +1245,7 @@ function encodeTopoloActionBase64(bytes) {
1167
1245
  }
1168
1246
  return btoa(binary);
1169
1247
  }
1170
- function isRecord2(value) {
1248
+ function isRecord3(value) {
1171
1249
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1172
1250
  }
1173
1251
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
@@ -1178,6 +1256,8 @@ var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
1178
1256
  var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
1179
1257
  var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
1180
1258
  var TopoloClient = class _TopoloClient {
1259
+ actionSecretStore;
1260
+ actionSecrets = /* @__PURE__ */ new Map();
1181
1261
  credential;
1182
1262
  agent;
1183
1263
  actorContext;
@@ -1207,6 +1287,7 @@ var TopoloClient = class _TopoloClient {
1207
1287
  _TopoloClient.appIdCache.clear();
1208
1288
  }
1209
1289
  constructor(options) {
1290
+ this.actionSecretStore = options.actionSecretStore;
1210
1291
  if (!options.credential) throw new TopoloAuthError("credential is required");
1211
1292
  if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
1212
1293
  this.credential = options.credential;
@@ -1228,6 +1309,7 @@ var TopoloClient = class _TopoloClient {
1228
1309
  }
1229
1310
  emit(event) {
1230
1311
  if (!this.debug) return;
1312
+ event = maskActionSecretValue(event, this.actionSecrets);
1231
1313
  try {
1232
1314
  this.debug(event);
1233
1315
  } catch {
@@ -1613,7 +1695,7 @@ var TopoloClient = class _TopoloClient {
1613
1695
  }
1614
1696
  const entries = [];
1615
1697
  for (const item of raw) {
1616
- if (!isRecord3(item)) continue;
1698
+ if (!isRecord4(item)) continue;
1617
1699
  const s = item;
1618
1700
  const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
1619
1701
  if (!appId) continue;
@@ -1688,8 +1770,8 @@ var TopoloClient = class _TopoloClient {
1688
1770
  "app_topolo_auth.service_api_key_resources.list",
1689
1771
  { appId: service.appId }
1690
1772
  );
1691
- const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
1692
- if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
1773
+ const payload = isRecord4(response) && isRecord4(response.data) ? response.data : response;
1774
+ if (!isRecord4(payload) || !Array.isArray(payload.resources)) {
1693
1775
  throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
1694
1776
  }
1695
1777
  return {
@@ -1870,17 +1952,20 @@ var TopoloClient = class _TopoloClient {
1870
1952
  }
1871
1953
  async validateActionInput(action, input = {}) {
1872
1954
  const entry = await this.getAction(action);
1873
- return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input) };
1955
+ const resolved = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
1956
+ return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, resolved) };
1874
1957
  }
1875
1958
  async planAction(action, input = {}, options = {}) {
1876
1959
  const entry = await this.getAction(action);
1877
- return this.planResolvedAction(entry, input, options);
1960
+ const resolved = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
1961
+ return this.planResolvedAction(entry, resolved, options);
1878
1962
  }
1879
1963
  async prepareAction(action, input, options = {}) {
1880
1964
  const entry = await this.getAction(action);
1881
1965
  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
1966
  const selectedInput = input ?? example?.input ?? {};
1883
- const plan = this.planResolvedAction(entry, selectedInput, options);
1967
+ const resolved = await resolveActionSecrets(selectedInput, this.actionSecretStore, this.actionSecrets);
1968
+ const plan = this.planResolvedAction(entry, resolved, options);
1884
1969
  return {
1885
1970
  action: entry,
1886
1971
  example,
@@ -1917,7 +2002,7 @@ var TopoloClient = class _TopoloClient {
1917
2002
  confirmationRequired,
1918
2003
  confirmationProvided: options.confirm === true,
1919
2004
  executable: validation.valid && agentAccess !== "off" && (!confirmationRequired || options.confirm === true),
1920
- request,
2005
+ request: maskActionSecretValue(request, this.actionSecrets),
1921
2006
  verification: stringArray(entry.agentContract?.verify),
1922
2007
  rollbackActionId: pickActionString(entry.agentContract?.rollbackActionId),
1923
2008
  nextActionIds: stringArray(entry.agentContract?.nextActionIds)
@@ -1947,6 +2032,7 @@ var TopoloClient = class _TopoloClient {
1947
2032
  `Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
1948
2033
  );
1949
2034
  }
2035
+ input = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
1950
2036
  const validation = validateTopoloActionInput(entry.inputSchema, input);
1951
2037
  if (!validation.valid) {
1952
2038
  throw new TopoloSdkError(
@@ -1977,22 +2063,31 @@ var TopoloClient = class _TopoloClient {
1977
2063
  }
1978
2064
  }
1979
2065
  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);
2066
+ let result;
2067
+ try {
2068
+ result = await this.request({
2069
+ service,
2070
+ ...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
2071
+ method: entry.method,
2072
+ path: mapped.path,
2073
+ ...mapped.query ? { query: mapped.query } : {},
2074
+ ...mapped.body !== void 0 ? { body: mapped.body } : {},
2075
+ ...options.resource ? { resource: options.resource } : {},
2076
+ ...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
2077
+ headers: {
2078
+ [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
2079
+ [TOPOLO_ACTION_NAME_HEADER]: entry.name
2080
+ },
2081
+ confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
2082
+ });
2083
+ } catch (error) {
2084
+ throw maskActionSecretError(error, this.actionSecrets);
2085
+ }
2086
+ return preserveSensitiveResult ? result : await protectActionResult(result, this.actionSecretStore);
2087
+ }
2088
+ async releaseActionSecret(reference) {
2089
+ if (!this.actionSecretStore) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
2090
+ await this.actionSecretStore.delete(reference);
1996
2091
  }
1997
2092
  async buildVerifiedAgentActorEnvelope(actionId, input, resource) {
1998
2093
  if (!this.actorContext) return void 0;
@@ -2139,7 +2234,7 @@ function resolveConfiguredAppId(slug, appIds) {
2139
2234
  return null;
2140
2235
  }
2141
2236
  function normalizeApiKeyResourceMetadata(input) {
2142
- if (!isRecord3(input)) return null;
2237
+ if (!isRecord4(input)) return null;
2143
2238
  const resourceType = readString(input.resourceType ?? input.resource_type);
2144
2239
  const label = readString(input.label);
2145
2240
  const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
@@ -2173,7 +2268,7 @@ function normalizeApiKeyResourceMetadataList(input) {
2173
2268
  return resources;
2174
2269
  }
2175
2270
  function normalizeBindableResource(input) {
2176
- if (!isRecord3(input)) return null;
2271
+ if (!isRecord4(input)) return null;
2177
2272
  const resourceType = readString(input.resourceType ?? input.resource_type);
2178
2273
  const resourceId = readString(input.resourceId ?? input.resource_id);
2179
2274
  if (!resourceType || !resourceId) return null;
@@ -2184,13 +2279,13 @@ function normalizeBindableResource(input) {
2184
2279
  slug: readOptionalString(input.slug),
2185
2280
  status: readOptionalString(input.status),
2186
2281
  organizationId: readOptionalString(input.organizationId ?? input.organization_id),
2187
- metadata: isRecord3(input.metadata) ? input.metadata : {}
2282
+ metadata: isRecord4(input.metadata) ? input.metadata : {}
2188
2283
  };
2189
2284
  }
2190
2285
  function normalizeResourceBindings(input) {
2191
2286
  if (!Array.isArray(input)) return [];
2192
2287
  return input.flatMap((binding) => {
2193
- if (!isRecord3(binding)) return [];
2288
+ if (!isRecord4(binding)) return [];
2194
2289
  const resourceType = readString(binding.resourceType ?? binding.resource_type);
2195
2290
  const resourceId = readString(binding.resourceId ?? binding.resource_id);
2196
2291
  return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
@@ -2203,30 +2298,9 @@ function readOptionalString(value) {
2203
2298
  const normalized = readString(value);
2204
2299
  return normalized || null;
2205
2300
  }
2206
- function isRecord3(value) {
2301
+ function isRecord4(value) {
2207
2302
  return typeof value === "object" && value !== null && !Array.isArray(value);
2208
2303
  }
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
2304
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
2231
2305
  for (const serviceKey of serviceKeyCandidates2(slug)) {
2232
2306
  const configured = serviceUrls?.[serviceKey];
@@ -2296,14 +2370,14 @@ function catalogSearchQuery(options) {
2296
2370
  return query;
2297
2371
  }
2298
2372
  function normalizeCatalogPage(value) {
2299
- if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
2373
+ if (!isRecord4(value)) return { nextCursor: null, hasMore: false };
2300
2374
  return {
2301
2375
  nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
2302
2376
  hasMore: value.hasMore === true
2303
2377
  };
2304
2378
  }
2305
2379
  function normalizeServiceCatalogEntry(item) {
2306
- if (!isRecord3(item)) return null;
2380
+ if (!isRecord4(item)) return null;
2307
2381
  const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
2308
2382
  if (!appId) return null;
2309
2383
  const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
@@ -3254,11 +3328,13 @@ function createTopolo(options) {
3254
3328
  };
3255
3329
  }
3256
3330
 
3257
- // node_modules/.pnpm/@topolo+sdk@0.21.2/node_modules/@topolo/sdk/dist/node/credential-store.js
3331
+ // node_modules/.pnpm/@topolo+sdk@0.21.4/node_modules/@topolo/sdk/dist/node/credential-store.js
3258
3332
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, rm as rm2, stat } from "fs/promises";
3259
3333
  import { existsSync as existsSync2 } from "fs";
3260
3334
  import { homedir as homedir2, platform } from "os";
3261
- import { dirname as dirname2, join as join2 } from "path";
3335
+ import { dirname as dirname2, join as join3 } from "path";
3336
+ import { createHash, randomUUID } from "crypto";
3337
+ import { join as join2 } from "path";
3262
3338
  import { spawnSync } from "child_process";
3263
3339
  import { mkdir, readFile, readdir, rename, rm, writeFile, chmod } from "fs/promises";
3264
3340
  import { existsSync } from "fs";
@@ -3610,66 +3686,7 @@ var TopoloOAuth = class {
3610
3686
  return parsed;
3611
3687
  }
3612
3688
  };
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
- }
3689
+ var ACTION_SECRET_PREFIX2 = "topolo-secret:";
3673
3690
  var KEYCHAIN_SERVICE = "topolo-external-agent-installation";
3674
3691
  var MACOS_KEYCHAIN_CHUNK_SIZE = 96;
3675
3692
  var MACOS_KEYCHAIN_CHUNK_PREFIX = "topolo-keychain-chunks:v1:";
@@ -3712,6 +3729,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3712
3729
  const platform2 = options.platform ?? hostPlatform();
3713
3730
  const spawn = options.spawn ?? ((command, args, spawnOptions) => spawnSync(command, args, spawnOptions));
3714
3731
  const root = options.root ?? installationStorePath();
3732
+ const service = options.service ?? KEYCHAIN_SERVICE;
3715
3733
  if (platform2 === "darwin") {
3716
3734
  const write = (account, value) => runOrThrow(spawn("/usr/bin/expect", ["-c", MACOS_KEYCHAIN_WRITE_SCRIPT], {
3717
3735
  input: `${value}
@@ -3720,7 +3738,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3720
3738
  env: {
3721
3739
  ...process.env,
3722
3740
  TOPOLO_KEYCHAIN_ACCOUNT: account,
3723
- TOPOLO_KEYCHAIN_SERVICE: KEYCHAIN_SERVICE
3741
+ TOPOLO_KEYCHAIN_SERVICE: service
3724
3742
  }
3725
3743
  }), "macOS Keychain write");
3726
3744
  const read = (account) => {
@@ -3729,7 +3747,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3729
3747
  "-a",
3730
3748
  account,
3731
3749
  "-s",
3732
- KEYCHAIN_SERVICE,
3750
+ service,
3733
3751
  "-w"
3734
3752
  ], { encoding: "utf8" });
3735
3753
  if (result.status === 44) return null;
@@ -3741,7 +3759,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3741
3759
  "-a",
3742
3760
  account,
3743
3761
  "-s",
3744
- KEYCHAIN_SERVICE
3762
+ service
3745
3763
  ], { encoding: "utf8" });
3746
3764
  if (result.status !== 44) runOrThrow(result, "macOS Keychain delete");
3747
3765
  };
@@ -3788,9 +3806,9 @@ function createPlatformInstallationSecretBackend(options = {}) {
3788
3806
  async set(account, value) {
3789
3807
  runOrThrow(spawn("secret-tool", [
3790
3808
  "store",
3791
- "--label=Topolo external agent installation",
3809
+ "--label=Topolo protected credential",
3792
3810
  "service",
3793
- KEYCHAIN_SERVICE,
3811
+ service,
3794
3812
  "account",
3795
3813
  account
3796
3814
  ], { input: value, encoding: "utf8" }), "Linux Secret Service write");
@@ -3799,7 +3817,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3799
3817
  const result = spawn("secret-tool", [
3800
3818
  "lookup",
3801
3819
  "service",
3802
- KEYCHAIN_SERVICE,
3820
+ service,
3803
3821
  "account",
3804
3822
  account
3805
3823
  ], { encoding: "utf8" });
@@ -3810,7 +3828,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3810
3828
  const result = spawn("secret-tool", [
3811
3829
  "clear",
3812
3830
  "service",
3813
- KEYCHAIN_SERVICE,
3831
+ service,
3814
3832
  "account",
3815
3833
  account
3816
3834
  ], { encoding: "utf8" });
@@ -3853,12 +3871,119 @@ async function readExternalAgentInstallation(environment, id, backend = createPl
3853
3871
  if (!encodedSecret) throw new Error(`Protected secret is missing for installation ${id}.`);
3854
3872
  return { installation, secret: JSON.parse(encodedSecret) };
3855
3873
  }
3874
+ function createProtectedActionSecretStore(options) {
3875
+ const backend = options.backend ?? createPlatformInstallationSecretBackend({
3876
+ service: "topolo-action-handoff",
3877
+ root: join2(installationStorePath(), "action-handoff")
3878
+ });
3879
+ const now = options.now ?? Date.now;
3880
+ async function account(reference) {
3881
+ if (!/^topolo-secret:[0-9a-f-]{36}$/.test(reference)) throw new TopoloSdkError2("invalid_action_secret_reference", "Invalid protected action reference.");
3882
+ const identity = await options.identity();
3883
+ if (!identity.user.id) throw new TopoloSdkError2("action_secret_identity_required", "A verified identity is required for protected action handoff.");
3884
+ 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.");
3885
+ const scope = createHash("sha256").update(JSON.stringify([
3886
+ options.environment,
3887
+ identity.user.principalId ?? identity.user.id,
3888
+ identity.organization?.id ?? null,
3889
+ identity.apiKey?.id ?? null
3890
+ ])).digest("hex");
3891
+ return `${scope}-${reference.slice(ACTION_SECRET_PREFIX2.length)}`;
3892
+ }
3893
+ return {
3894
+ async save(value) {
3895
+ const reference = `${ACTION_SECRET_PREFIX2}${randomUUID()}`;
3896
+ await backend.set(await account(reference), JSON.stringify({ value, expiresAt: now() + 30 * 60 * 1e3 }));
3897
+ return reference;
3898
+ },
3899
+ async load(reference) {
3900
+ const key = await account(reference);
3901
+ const encoded = await backend.get(key);
3902
+ if (!encoded) throw new TopoloSdkError2("action_secret_unavailable", "Protected action reference is missing or belongs to another identity or environment.");
3903
+ let record2;
3904
+ try {
3905
+ record2 = JSON.parse(encoded);
3906
+ } catch {
3907
+ throw new TopoloSdkError2("action_secret_invalid", "Protected action reference is corrupt.");
3908
+ }
3909
+ if (!Number.isFinite(record2?.expiresAt) || !Object.hasOwn(record2, "value")) throw new TopoloSdkError2("action_secret_invalid", "Protected action reference is corrupt.");
3910
+ if (record2.expiresAt <= now()) {
3911
+ await backend.delete(key);
3912
+ throw new TopoloSdkError2("action_secret_expired", "Protected action reference expired. Repeat the originating action if its contract permits.");
3913
+ }
3914
+ return record2.value;
3915
+ },
3916
+ async delete(reference) {
3917
+ await backend.delete(await account(reference));
3918
+ }
3919
+ };
3920
+ }
3921
+ var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
3922
+ production: {
3923
+ auth: "https://auth.topolo.app",
3924
+ developers: "https://developers.topolo.app"
3925
+ },
3926
+ staging: {
3927
+ auth: "https://auth.stg.topolo.us",
3928
+ developers: "https://developers.stg.topolo.us"
3929
+ },
3930
+ development: {
3931
+ auth: "https://auth.topolo.dev",
3932
+ developers: "https://developers.topolo.dev"
3933
+ },
3934
+ local: {}
3935
+ };
3936
+ function hostnameHasSuffix2(hostname, suffix) {
3937
+ return hostname === suffix || hostname.endsWith(`.${suffix}`);
3938
+ }
3939
+ function workersDevAccountMatches2(hostname, account) {
3940
+ if (!hostname.endsWith(".workers.dev")) return false;
3941
+ const labels = hostname.slice(0, -".workers.dev".length).split(".");
3942
+ return labels.length >= 2 && labels.at(-1) === account;
3943
+ }
3944
+ function originMatchesEnvironment2(url, environment) {
3945
+ const hostname = url.hostname.toLowerCase();
3946
+ if (environment === "local") {
3947
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "[::1]";
3948
+ }
3949
+ if (url.protocol !== "https:") return false;
3950
+ if (environment === "production") {
3951
+ return hostnameHasSuffix2(hostname, "topolo.app") || hostnameHasSuffix2(hostname, "topolo.io") || hostname === "api.ol0.me" || workersDevAccountMatches2(hostname, "topolo");
3952
+ }
3953
+ if (environment === "staging") {
3954
+ return hostnameHasSuffix2(hostname, "stg.topolo.us") || workersDevAccountMatches2(hostname, "topolo-staging");
3955
+ }
3956
+ return hostnameHasSuffix2(hostname, "topolo.dev") || workersDevAccountMatches2(hostname, "topolo-development");
3957
+ }
3958
+ function assertTopoloServiceUrlEnvironment2(service, value, environment) {
3959
+ let url;
3960
+ try {
3961
+ url = new URL(value);
3962
+ } catch {
3963
+ throw new TopoloSdkError2("invalid_service_url", `Invalid URL configured for Topolo service "${service}".`);
3964
+ }
3965
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !originMatchesEnvironment2(url, environment)) {
3966
+ throw new TopoloSdkError2(
3967
+ "environment_mismatch",
3968
+ `Refusing Topolo service "${service}": ${url.origin} does not belong to ${environment}.`
3969
+ );
3970
+ }
3971
+ return url.origin;
3972
+ }
3973
+ function topoloServiceOrigins2(serviceUrls, environment) {
3974
+ return Object.fromEntries(
3975
+ Object.entries(serviceUrls ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([service, value]) => [
3976
+ service,
3977
+ assertTopoloServiceUrlEnvironment2(service, value, environment)
3978
+ ])
3979
+ );
3980
+ }
3856
3981
  var ENVIRONMENT_DEFAULT_SERVICE_URLS = TOPOLO_ENVIRONMENT_SERVICE_URLS;
3857
3982
  function configPath() {
3858
3983
  const override = process.env.TOPOLO_CONFIG_PATH;
3859
3984
  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");
3985
+ const base = process.env.XDG_CONFIG_HOME || (platform() === "win32" ? join3(homedir2(), "AppData", "Roaming") : join3(homedir2(), ".config"));
3986
+ return join3(base, "topolo", "config.json");
3862
3987
  }
3863
3988
  async function readConfig() {
3864
3989
  const path = configPath();
@@ -4152,6 +4277,28 @@ function publicMcpErrorMessage(error, fallback) {
4152
4277
  return error instanceof TopoloMcpPublicError ? error.message : fallback;
4153
4278
  }
4154
4279
 
4280
+ // src/action-secret-tools.ts
4281
+ var actionSecretTools = [{
4282
+ name: "topolo_release_action_secret",
4283
+ title: "Release a protected action reference",
4284
+ description: "Deletes only a local protected handoff reference for the verified identity and environment. Does not revoke the upstream credential. Requires confirm:true.",
4285
+ requiredScopes: [],
4286
+ destructive: true,
4287
+ inputSchema: {
4288
+ type: "object",
4289
+ properties: { reference: { type: "string", minLength: 1 }, confirm: { type: "boolean" } },
4290
+ required: ["reference", "confirm"],
4291
+ additionalProperties: false
4292
+ },
4293
+ handler: async (topolo, args) => {
4294
+ const reference = typeof args.reference === "string" ? args.reference.trim() : "";
4295
+ if (!reference) throw new TopoloMcpPublicError("reference is required.");
4296
+ if (args.confirm !== true) throw new TopoloMcpPublicError("Releasing a protected action reference requires confirm:true.");
4297
+ await topolo.client.releaseActionSecret(reference);
4298
+ return { released: true };
4299
+ }
4300
+ }];
4301
+
4155
4302
  // src/tools.ts
4156
4303
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
4157
4304
  var AGENT_INVOCATIONS_CREATE = "app_topolo_agent.agent_invocations.create";
@@ -4230,6 +4377,7 @@ function agentRunResult(invocation) {
4230
4377
  };
4231
4378
  }
4232
4379
  var TOOLS = [
4380
+ ...actionSecretTools,
4233
4381
  {
4234
4382
  name: "topolo_whoami",
4235
4383
  title: "Describe the current Topolo credential",
@@ -4912,14 +5060,14 @@ function displayAppId(entry) {
4912
5060
 
4913
5061
  // src/version.ts
4914
5062
  import { readFileSync } from "fs";
4915
- import { dirname as dirname3, join as join3 } from "path";
5063
+ import { dirname as dirname3, join as join4 } from "path";
4916
5064
  import { fileURLToPath } from "url";
4917
5065
  function readMcpVersion() {
4918
5066
  try {
4919
5067
  const here = dirname3(fileURLToPath(import.meta.url));
4920
5068
  const candidates = [
4921
- join3(here, "package.json"),
4922
- join3(here, "..", "package.json")
5069
+ join4(here, "package.json"),
5070
+ join4(here, "..", "package.json")
4923
5071
  ];
4924
5072
  for (const path of candidates) {
4925
5073
  try {
@@ -5042,7 +5190,7 @@ function toolError(context, error) {
5042
5190
  }
5043
5191
 
5044
5192
  // src/context-credential.ts
5045
- import { randomUUID } from "crypto";
5193
+ import { randomUUID as randomUUID2 } from "crypto";
5046
5194
 
5047
5195
  // src/auth.ts
5048
5196
  function resolveCredentialFromEnv(environment = "production") {
@@ -5186,7 +5334,7 @@ async function resolveMcpCredential(environment, selector = process.env.TOPOLO_C
5186
5334
  "TOPOLO_AGENT_INSTALLATION_ID cannot be combined with TOPOLO_CONTEXT or an environment credential"
5187
5335
  );
5188
5336
  }
5189
- const taskId = taskSelector?.trim() || `task_${randomUUID()}`;
5337
+ const taskId = taskSelector?.trim() || `task_${randomUUID2()}`;
5190
5338
  const config2 = await dependencies.readConfig();
5191
5339
  const serviceUrls = resolveServiceUrls(
5192
5340
  profileForEnvironment(config2, environment).serviceUrls,
@@ -5285,6 +5433,7 @@ var DEFAULT_DEPENDENCIES2 = {
5285
5433
  resolveAgentName,
5286
5434
  resolveServiceUrls,
5287
5435
  createTopolo,
5436
+ createProtectedActionSecretStore,
5288
5437
  buildServer,
5289
5438
  createTransport: createStdioTransport,
5290
5439
  advertisedToolNames,
@@ -5307,6 +5456,7 @@ async function runStdioMcp(dependencies = DEFAULT_DEPENDENCIES2) {
5307
5456
  const topolo = dependencies.createTopolo({
5308
5457
  credential,
5309
5458
  environment,
5459
+ actionSecretStore: dependencies.createProtectedActionSecretStore({ environment, identity: () => topolo.client.introspect() }),
5310
5460
  actorContext: {
5311
5461
  channel: "mcp",
5312
5462
  ...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.2",
5
- "topoloPublishedVersion": "0.14.2",
4
+ "version": "0.14.4",
5
+ "topoloPublishedVersion": "0.14.4",
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.4"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^20.12.0",