@decionis/mcp 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,12 +5,12 @@ import {
5
5
  external_exports,
6
6
  normalizeVerdict,
7
7
  sha256Hex
8
- } from "./chunk-NF5JL6QR.js";
8
+ } from "./chunk-LIVLO4MV.js";
9
9
 
10
10
  // src/Server.ts
11
11
  import { createInterface } from "readline";
12
12
  var SUPPORTED_PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
13
- var MCP_SERVER_VERSION = "0.2.0";
13
+ var MCP_SERVER_VERSION = "0.3.0";
14
14
  var DEFAULT_PROTOCOL_VERSION = "2025-11-25";
15
15
  var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
16
16
  function rpcResult(id, result) {
@@ -633,9 +633,9 @@ async function readBoundedText(response, maxBytes) {
633
633
  throw new DecionisResponseTooLargeError();
634
634
  }
635
635
  if (!response.body || typeof response.body.getReader !== "function") {
636
- const text4 = typeof responseLike.text === "function" ? await responseLike.text() : typeof responseLike.json === "function" ? JSON.stringify(await responseLike.json()) : "";
637
- if (Buffer.byteLength(text4, "utf8") > maxBytes) throw new DecionisResponseTooLargeError();
638
- return text4;
636
+ const text5 = typeof responseLike.text === "function" ? await responseLike.text() : typeof responseLike.json === "function" ? JSON.stringify(await responseLike.json()) : "";
637
+ if (Buffer.byteLength(text5, "utf8") > maxBytes) throw new DecionisResponseTooLargeError();
638
+ return text5;
639
639
  }
640
640
  const reader = response.body.getReader();
641
641
  const chunks = [];
@@ -671,8 +671,8 @@ async function fetchBounded(fetchImpl, input, init, timeoutMs, maxResponseBytes
671
671
  });
672
672
  const operation = (async () => {
673
673
  const response = await fetchImpl(input, { ...init, signal: controller.signal });
674
- const text4 = await readBoundedText(response, maxResponseBytes);
675
- return { response, text: text4 };
674
+ const text5 = await readBoundedText(response, maxResponseBytes);
675
+ return { response, text: text5 };
676
676
  })();
677
677
  try {
678
678
  return await Promise.race([operation, deadline]);
@@ -682,16 +682,16 @@ async function fetchBounded(fetchImpl, input, init, timeoutMs, maxResponseBytes
682
682
  }
683
683
  }
684
684
  async function fetchBoundedJson(fetchImpl, input, init, timeoutMs, maxResponseBytes = DEFAULT_MAX_RESPONSE_BYTES) {
685
- const { response, text: text4 } = await fetchBounded(
685
+ const { response, text: text5 } = await fetchBounded(
686
686
  fetchImpl,
687
687
  input,
688
688
  init,
689
689
  timeoutMs,
690
690
  maxResponseBytes
691
691
  );
692
- if (!text4) return { response, body: null };
692
+ if (!text5) return { response, body: null };
693
693
  try {
694
- return { response, body: JSON.parse(text4) };
694
+ return { response, body: JSON.parse(text5) };
695
695
  } catch {
696
696
  return { response, body: null };
697
697
  }
@@ -733,10 +733,16 @@ var authorityVerificationRequirementsSchema = external_exports.object({
733
733
  });
734
734
  }
735
735
  });
736
+ var authorityApprovalTierSchema = external_exports.object({
737
+ priority: external_exports.number().int().min(1).max(5),
738
+ principal_ids: external_exports.array(external_exports.string().min(1).max(200)).min(1).max(10)
739
+ }).strict();
736
740
  var authorityResolutionPolicySchema = external_exports.object({
737
741
  quorum: external_exports.literal(1),
738
742
  delegation: external_exports.enum(["DISALLOWED", "REQUIREMENT_REFRESH"]),
739
- fallback_role_ids: external_exports.array(external_exports.string().regex(/^[A-Z][A-Z0-9_]{1,63}$/)).max(10).default([])
743
+ fallback_role_ids: external_exports.array(external_exports.string().regex(/^[A-Z][A-Z0-9_]{1,63}$/)).max(10).default([]),
744
+ /** An approval chain: one valid proof from every tier satisfies the requirement. */
745
+ tiers: external_exports.array(authorityApprovalTierSchema).min(1).max(5).optional()
740
746
  }).strict().superRefine((policy, context) => {
741
747
  if (new Set(policy.fallback_role_ids).size !== policy.fallback_role_ids.length) {
742
748
  context.addIssue({
@@ -745,6 +751,24 @@ var authorityResolutionPolicySchema = external_exports.object({
745
751
  message: "fallback role ids must be unique"
746
752
  });
747
753
  }
754
+ if (policy.tiers) {
755
+ const priorities = policy.tiers.map((tier) => tier.priority);
756
+ if (priorities.some((priority, index) => index > 0 && priority <= priorities[index - 1])) {
757
+ context.addIssue({
758
+ code: external_exports.ZodIssueCode.custom,
759
+ path: ["tiers"],
760
+ message: "tier priorities must be unique and ascending"
761
+ });
762
+ }
763
+ const principals = policy.tiers.flatMap((tier) => tier.principal_ids);
764
+ if (new Set(principals).size !== principals.length) {
765
+ context.addIssue({
766
+ code: external_exports.ZodIssueCode.custom,
767
+ path: ["tiers"],
768
+ message: "a principal belongs to one tier only"
769
+ });
770
+ }
771
+ }
748
772
  });
749
773
  var originatorSchema = external_exports.object({
750
774
  organization_id: external_exports.string().min(1).max(200),
@@ -810,6 +834,13 @@ var decionisAuthorityRequirementSchema = external_exports.object({
810
834
  message: "fallback roles must not repeat the primary role"
811
835
  });
812
836
  }
837
+ if (requirement.resolution.tiers && requirement.selector.kind !== "ROLE") {
838
+ context.addIssue({
839
+ code: external_exports.ZodIssueCode.custom,
840
+ path: ["resolution", "tiers"],
841
+ message: "an approval chain names its members' trusted role through a ROLE selector"
842
+ });
843
+ }
813
844
  if (requirement.orchestration?.mode === "MANAGED" && requirement.selector.kind === "PRINCIPAL" && requirement.selector.role_id === void 0) {
814
845
  context.addIssue({
815
846
  code: external_exports.ZodIssueCode.custom,
@@ -992,13 +1023,25 @@ function expectedManagedEscalationOutcome(status) {
992
1023
  if (["EXPIRED", "REJECTED", "BLOCKED", "CANCELLED"].includes(status)) return "BLOCK";
993
1024
  return "ESCALATE_PENDING";
994
1025
  }
1026
+ var managedApprovalTierSchema = external_exports.object({
1027
+ tier: external_exports.number().int().min(1).max(5),
1028
+ tier_count: external_exports.number().int().min(1).max(5),
1029
+ principal_id: external_exports.string().min(1).max(200)
1030
+ }).strict();
1031
+ var managedApprovalChainSchema = external_exports.object({
1032
+ chain_id: external_exports.string().max(200),
1033
+ tier_count: external_exports.number().int().min(1).max(5),
1034
+ tiers_approved: external_exports.number().int().min(0).max(5)
1035
+ }).strict();
995
1036
  var managedEscalationReferenceSchema = external_exports.object({
996
1037
  outcome: managedEscalationOutcomeSchema,
997
1038
  escalation_id: external_exports.string().min(8).max(200),
998
1039
  intent_id: external_exports.string().min(1).max(200),
999
1040
  status: managedEscalationStatusSchema,
1000
1041
  expires_at: external_exports.string().datetime(),
1001
- reason_codes: external_exports.array(external_exports.string().min(1).max(200)).max(50)
1042
+ reason_codes: external_exports.array(external_exports.string().min(1).max(200)).max(50),
1043
+ approval_tier: managedApprovalTierSchema.nullable().optional().default(null),
1044
+ approval_chain: managedApprovalChainSchema.nullable().optional().default(null)
1002
1045
  }).strict().superRefine((reference, context) => {
1003
1046
  if (reference.outcome !== expectedManagedEscalationOutcome(reference.status)) {
1004
1047
  context.addIssue({
@@ -1049,6 +1092,8 @@ var managedEscalationViewSchema = external_exports.object({
1049
1092
  outcome: managedEscalationOutcomeSchema,
1050
1093
  expires_at: external_exports.string().datetime(),
1051
1094
  reason_codes: external_exports.array(external_exports.string().min(1).max(200)).max(50),
1095
+ approval_tier: managedApprovalTierSchema.nullable().optional().default(null),
1096
+ approval_chain: managedApprovalChainSchema.nullable().optional().default(null),
1052
1097
  decision: authorityDecisionSchema.nullable()
1053
1098
  }).strict().superRefine((view, context) => {
1054
1099
  if (view.outcome !== expectedManagedEscalationOutcome(view.status)) {
@@ -1334,6 +1379,7 @@ var DecionisExecutionClient = class {
1334
1379
  claim_token: claimToken,
1335
1380
  claim_validated_at: claimValidatedAt,
1336
1381
  claim_lease_expires_at: claimLeaseExpiresAt,
1382
+ ...typeof body.claim_attestation === "string" && body.claim_attestation.length > 0 ? { claim_attestation: body.claim_attestation } : {},
1337
1383
  ...body.evidence && typeof body.evidence === "object" ? { evidence: body.evidence } : {}
1338
1384
  };
1339
1385
  if (executionAuthorityProtocolSemantics(redemption) === "UNSUPPORTED") {
@@ -2310,7 +2356,7 @@ var KubectlCommandRunner = class {
2310
2356
  // src/kubernetes/KubernetesOperations.ts
2311
2357
  import { createHash as createHash4 } from "crypto";
2312
2358
 
2313
- // ../../packages/dossier-verify/dist/chunk-DF26DHVV.js
2359
+ // ../../packages/dossier-verify/dist/chunk-WNX2CYWD.js
2314
2360
  import { createHash, createPublicKey, verify as verifyEd25519 } from "crypto";
2315
2361
  import { lookup } from "dns/promises";
2316
2362
  import { request as httpsRequest } from "https";
@@ -6951,10 +6997,10 @@ function validEd25519Signature(value) {
6951
6997
  }
6952
6998
  function normalizedPublicJwksUrl(value) {
6953
6999
  if (value === void 0) return void 0;
6954
- const text4 = boundedString(value, 2048);
6955
- if (!text4) return null;
7000
+ const text22 = boundedString(value, 2048);
7001
+ if (!text22) return null;
6956
7002
  try {
6957
- const url = new URL(text4);
7003
+ const url = new URL(text22);
6958
7004
  if (url.protocol !== "https:" || url.username || url.password) return null;
6959
7005
  return url.toString();
6960
7006
  } catch {
@@ -7320,6 +7366,8 @@ function extractDossierPayload(body) {
7320
7366
  const candidate = asRecord2(record2[key]);
7321
7367
  if (asRecord2(candidate.integrity).proof_bundle !== void 0) return candidate;
7322
7368
  }
7369
+ const payload = asRecord2(asRecord2(record2.dossier).dossier_payload);
7370
+ if (asRecord2(payload.integrity).proof_bundle !== void 0) return payload;
7323
7371
  return record2;
7324
7372
  }
7325
7373
  function resolveJwksUrl(payload, dossierUrl) {
@@ -7410,22 +7458,22 @@ async function assertSafeRemoteUrl(urlText) {
7410
7458
  if (url.username || url.password) {
7411
7459
  throw new Error(`Verification URL must not contain credentials: ${urlText}`);
7412
7460
  }
7413
- const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
7414
- if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local") || isIP(hostname) !== 0 && isForbiddenIp(hostname)) {
7461
+ const hostname2 = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
7462
+ if (hostname2 === "localhost" || hostname2.endsWith(".localhost") || hostname2.endsWith(".local") || isIP(hostname2) !== 0 && isForbiddenIp(hostname2)) {
7415
7463
  throw new Error(`Verification URL resolves to a non-public destination: ${urlText}`);
7416
7464
  }
7417
7465
  return url;
7418
7466
  }
7419
7467
  async function pinnedHttpsFetch(url, signal) {
7420
- const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
7468
+ const hostname2 = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
7421
7469
  let addresses;
7422
- if (isIP(hostname) !== 0) {
7423
- addresses = [{ address: hostname, family: isIP(hostname) }];
7470
+ if (isIP(hostname2) !== 0) {
7471
+ addresses = [{ address: hostname2, family: isIP(hostname2) }];
7424
7472
  } else {
7425
7473
  try {
7426
- addresses = await lookup(hostname, { all: true, verbatim: true });
7474
+ addresses = await lookup(hostname2, { all: true, verbatim: true });
7427
7475
  } catch {
7428
- throw new Error(`Unable to resolve verification host: ${hostname}`);
7476
+ throw new Error(`Unable to resolve verification host: ${hostname2}`);
7429
7477
  }
7430
7478
  }
7431
7479
  if (addresses.length === 0 || addresses.some(({ address }) => isForbiddenIp(address))) {
@@ -7441,7 +7489,7 @@ async function pinnedHttpsFetch(url, signal) {
7441
7489
  port: url.port || 443,
7442
7490
  path: `${url.pathname}${url.search}`,
7443
7491
  method: "GET",
7444
- servername: isIP(hostname) === 0 ? hostname : void 0,
7492
+ servername: isIP(hostname2) === 0 ? hostname2 : void 0,
7445
7493
  headers: { accept: "application/json", host: url.host },
7446
7494
  signal
7447
7495
  },
@@ -7577,6 +7625,58 @@ async function verifyDossierFromUrls(input) {
7577
7625
  payload
7578
7626
  };
7579
7627
  }
7628
+ var AGENT_SAFE_INTENT_PROTOCOL = "agent-safe.intent/1";
7629
+ var MAX_BYTES = 100 * 1024;
7630
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7631
+ var INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
7632
+ var ACTION_TYPE = /^[a-z][a-z0-9._:-]*$/;
7633
+ var DIGEST = /^sha256:[0-9a-f]{64}$/;
7634
+ function isPlainObject3(value) {
7635
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
7636
+ const prototype = Object.getPrototypeOf(value);
7637
+ return prototype === Object.prototype || prototype === null;
7638
+ }
7639
+ var text2 = (min, max) => (value) => typeof value === "string" && value.length >= min && value.length <= max;
7640
+ var pattern = (expression) => (value) => typeof value === "string" && expression.test(value);
7641
+ var ACTOR = {
7642
+ required: { id: text2(1, 200), type: text2(1, 200) },
7643
+ optional: { runtime: text2(1, 200), trust_level: text2(1, 80) },
7644
+ closed: true
7645
+ };
7646
+ var ACTION = {
7647
+ required: {
7648
+ type: (value) => text2(1, 120)(value) && ACTION_TYPE.test(value),
7649
+ resource: text2(1, 500),
7650
+ parameters: isPlainObject3
7651
+ },
7652
+ optional: {},
7653
+ closed: true
7654
+ };
7655
+ var CONTEXT = {
7656
+ required: { idempotency_key: text2(1, 180) },
7657
+ optional: {},
7658
+ closed: false
7659
+ };
7660
+ var DOWNSTREAM_TARGET = {
7661
+ required: { system: text2(1, 200), operation: text2(1, 200) },
7662
+ optional: { environment: text2(1, 200), endpoint: text2(1, 500) },
7663
+ closed: true
7664
+ };
7665
+ var BINDING = {
7666
+ required: {
7667
+ protocol_version: (value) => value === AGENT_SAFE_INTENT_PROTOCOL,
7668
+ tenant_id: pattern(UUID),
7669
+ intent_id: pattern(UUID),
7670
+ captured_at: pattern(INSTANT),
7671
+ expires_at: pattern(INSTANT),
7672
+ actor: isPlainObject3,
7673
+ action: isPlainObject3,
7674
+ context: isPlainObject3,
7675
+ downstream_target: isPlainObject3
7676
+ },
7677
+ optional: { expected_effect_digest: pattern(DIGEST) },
7678
+ closed: true
7679
+ };
7580
7680
 
7581
7681
  // src/kubernetes/KubernetesOperations.ts
7582
7682
  var KubernetesOperations = class _KubernetesOperations {
@@ -7951,7 +8051,7 @@ function createKubernetesAuthorityClient(input) {
7951
8051
  }
7952
8052
 
7953
8053
  // src/kubernetes/KubernetesMcpTools.ts
7954
- var text2 = (value) => [
8054
+ var text3 = (value) => [
7955
8055
  { type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
7956
8056
  ];
7957
8057
  var KubernetesMcpTools = class _KubernetesMcpTools {
@@ -7984,7 +8084,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
7984
8084
  openWorldHint: true
7985
8085
  },
7986
8086
  handler: async (args) => ({
7987
- content: text2(
8087
+ content: text3(
7988
8088
  await this.operations.plan(
7989
8089
  _KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
7990
8090
  _KubernetesMcpTools.optionalNamespace(args.namespace)
@@ -8004,7 +8104,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
8004
8104
  openWorldHint: true
8005
8105
  },
8006
8106
  handler: async (args) => ({
8007
- content: text2(
8107
+ content: text3(
8008
8108
  await this.operations.requestAuthority(
8009
8109
  _KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
8010
8110
  _KubernetesMcpTools.optionalNamespace(args.namespace)
@@ -8024,7 +8124,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
8024
8124
  openWorldHint: true
8025
8125
  },
8026
8126
  handler: async (args) => ({
8027
- content: text2(
8127
+ content: text3(
8028
8128
  await this.operations.apply(
8029
8129
  _KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
8030
8130
  _KubernetesMcpTools.optionalNamespace(args.namespace)
@@ -8053,7 +8153,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
8053
8153
  openWorldHint: true
8054
8154
  },
8055
8155
  handler: async (args) => ({
8056
- content: text2(
8156
+ content: text3(
8057
8157
  await this.operations.scale({
8058
8158
  namespace: _KubernetesMcpTools.requiredDnsName(args.namespace, "namespace"),
8059
8159
  name: _KubernetesMcpTools.requiredDnsName(args.name, "name"),
@@ -8074,7 +8174,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
8074
8174
  openWorldHint: true
8075
8175
  },
8076
8176
  handler: async (args) => ({
8077
- content: text2(
8177
+ content: text3(
8078
8178
  await this.operations.rollback(
8079
8179
  _KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
8080
8180
  _KubernetesMcpTools.optionalNamespace(args.namespace)
@@ -8102,7 +8202,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
8102
8202
  openWorldHint: true
8103
8203
  },
8104
8204
  handler: async (args) => ({
8105
- content: text2(
8205
+ content: text3(
8106
8206
  await this.operations.verifyDossier(
8107
8207
  _KubernetesMcpTools.requiredString(args.dossier_url, "dossier_url"),
8108
8208
  _KubernetesMcpTools.requiredString(args.jwks_url, "jwks_url")
@@ -8180,17 +8280,94 @@ function boundedInteger(input, fallback, minimum, maximum) {
8180
8280
  return value;
8181
8281
  }
8182
8282
 
8283
+ // src/ShieldCredentialStore.ts
8284
+ import { mkdirSync, readFileSync, writeFileSync, chmodSync } from "fs";
8285
+ import { homedir, hostname } from "os";
8286
+ import { dirname, join } from "path";
8287
+ import { randomUUID as randomUUID2 } from "crypto";
8288
+ var SHIELD_MCP_APP_ID = "decionis-mcp";
8289
+ var DEFAULT_SHIELD_API_URL = "https://api.shield.decionis.com";
8290
+ function defaultCredentialPath() {
8291
+ return process.env.DECIONIS_SHIELD_CREDENTIAL_FILE?.trim() || join(homedir(), ".decionis", "shield-credential.json");
8292
+ }
8293
+ var ShieldCredentialStore = class {
8294
+ constructor(path = defaultCredentialPath()) {
8295
+ this.path = path;
8296
+ }
8297
+ /** The credential to use now: the environment first, then the paired file. */
8298
+ resolve() {
8299
+ const envKey = process.env.DECIONIS_SHIELD_API_KEY?.trim();
8300
+ const envApp = process.env.DECIONIS_SHIELD_APP_ID?.trim();
8301
+ const baseUrl = (process.env.DECIONIS_SHIELD_API_URL?.trim() || DEFAULT_SHIELD_API_URL).replace(
8302
+ /\/+$/,
8303
+ ""
8304
+ );
8305
+ if (envKey && envApp) {
8306
+ return { apiKey: envKey, appId: envApp, baseUrl, agentId: envApp };
8307
+ }
8308
+ const stored = this.read();
8309
+ if (!stored) return null;
8310
+ return { ...stored, baseUrl: process.env.DECIONIS_SHIELD_API_URL?.trim() || stored.baseUrl };
8311
+ }
8312
+ read() {
8313
+ try {
8314
+ const raw = JSON.parse(readFileSync(this.path, "utf8"));
8315
+ if (typeof raw.apiKey !== "string" || typeof raw.appId !== "string") return null;
8316
+ return {
8317
+ apiKey: raw.apiKey,
8318
+ appId: raw.appId,
8319
+ baseUrl: typeof raw.baseUrl === "string" ? raw.baseUrl : DEFAULT_SHIELD_API_URL,
8320
+ integrationId: typeof raw.integrationId === "string" ? raw.integrationId : void 0,
8321
+ displayName: typeof raw.displayName === "string" ? raw.displayName : void 0,
8322
+ agentId: typeof raw.agentId === "string" ? raw.agentId : randomUUID2(),
8323
+ pairedAt: typeof raw.pairedAt === "string" ? raw.pairedAt : void 0
8324
+ };
8325
+ } catch {
8326
+ return null;
8327
+ }
8328
+ }
8329
+ /** The install's stable id: the stored one, else a new one written at first pairing. */
8330
+ agentId() {
8331
+ return this.read()?.agentId ?? randomUUID2();
8332
+ }
8333
+ write(credential) {
8334
+ mkdirSync(dirname(this.path), { recursive: true, mode: 448 });
8335
+ writeFileSync(this.path, `${JSON.stringify(credential, null, 2)}
8336
+ `, { mode: 384 });
8337
+ try {
8338
+ chmodSync(this.path, 384);
8339
+ } catch {
8340
+ }
8341
+ }
8342
+ location() {
8343
+ return this.path;
8344
+ }
8345
+ };
8346
+ function defaultAgentDisplayName(clientName) {
8347
+ const host = hostname().split(".")[0] || "this computer";
8348
+ return clientName ? `${clientName} on ${host}` : `Decionis MCP on ${host}`;
8349
+ }
8350
+
8183
8351
  // src/ShieldClient.ts
8184
8352
  var ShieldClient = class {
8185
- baseUrl;
8186
- apiKey;
8187
- appId;
8188
8353
  fetchImpl;
8354
+ credentials;
8189
8355
  constructor(options = {}) {
8190
- this.baseUrl = (options.baseUrl ?? process.env.DECIONIS_SHIELD_API_URL ?? "https://api.decionis.com").replace(/\/+$/, "");
8191
- this.apiKey = options.apiKey ?? process.env.DECIONIS_SHIELD_API_KEY ?? "";
8192
- this.appId = options.appId ?? process.env.DECIONIS_SHIELD_APP_ID ?? "";
8193
8356
  this.fetchImpl = options.fetch ?? fetch;
8357
+ if (options.credentials) {
8358
+ this.credentials = options.credentials;
8359
+ } else if (options.apiKey !== void 0 || options.appId !== void 0 || options.baseUrl) {
8360
+ const fixed = options.apiKey && options.appId ? {
8361
+ apiKey: options.apiKey,
8362
+ appId: options.appId,
8363
+ baseUrl: (options.baseUrl ?? DEFAULT_SHIELD_API_URL).replace(/\/+$/, ""),
8364
+ agentId: options.appId
8365
+ } : null;
8366
+ this.credentials = () => fixed;
8367
+ } else {
8368
+ const store = new ShieldCredentialStore();
8369
+ this.credentials = () => store.resolve();
8370
+ }
8194
8371
  }
8195
8372
  authorize(request) {
8196
8373
  return this.json("/v1/shield/authorize", "POST", request);
@@ -8205,19 +8382,20 @@ var ShieldClient = class {
8205
8382
  return this.json(`/v1/shield/decisions/${encodeURIComponent(decisionId)}`, "GET");
8206
8383
  }
8207
8384
  async json(path, method, body) {
8208
- if (!this.apiKey || !this.appId) {
8385
+ const credential = this.credentials();
8386
+ if (!credential || !credential.apiKey || !credential.appId) {
8209
8387
  throw new ShieldClientError(
8210
8388
  "SHIELD_MCP_CONFIGURATION_REQUIRED",
8211
- "Configure DECIONIS_SHIELD_API_KEY and DECIONIS_SHIELD_APP_ID. Do not execute the transaction."
8389
+ "Shield is not paired. Ask the user for the six-character code from the Shield app (AI spending allowance \u2192 Bind your own agent) and call shield.pair with it \u2014 or configure DECIONIS_SHIELD_API_KEY and DECIONIS_SHIELD_APP_ID. Do not execute the transaction."
8212
8390
  );
8213
8391
  }
8214
- const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
8392
+ const response = await this.fetchImpl(`${credential.baseUrl}${path}`, {
8215
8393
  method,
8216
8394
  headers: {
8217
8395
  accept: "application/json",
8218
- authorization: `Bearer ${this.apiKey}`,
8396
+ authorization: `Bearer ${credential.apiKey}`,
8219
8397
  "content-type": "application/json",
8220
- "x-shield-app-id": this.appId,
8398
+ "x-shield-app-id": credential.appId,
8221
8399
  "x-shield-environment": "production"
8222
8400
  },
8223
8401
  ...method === "POST" ? { body: JSON.stringify(body ?? {}) } : {}
@@ -8245,7 +8423,7 @@ function asRecord3(value) {
8245
8423
  }
8246
8424
 
8247
8425
  // src/ShieldTools.ts
8248
- var text3 = (value) => [
8426
+ var text4 = (value) => [
8249
8427
  { type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
8250
8428
  ];
8251
8429
  var purchaseProperties = {
@@ -8284,11 +8462,94 @@ var networkRead = {
8284
8462
  idempotentHint: true,
8285
8463
  openWorldHint: true
8286
8464
  };
8465
+ function createShieldPairTool(options = {}) {
8466
+ const store = options.store ?? new ShieldCredentialStore();
8467
+ const fetchImpl = options.fetch ?? fetch;
8468
+ const baseUrl = () => (options.baseUrl?.() ?? process.env.DECIONIS_SHIELD_API_URL?.trim() ?? "").replace(/\/+$/, "") || "https://api.shield.decionis.com";
8469
+ return {
8470
+ name: "shield.pair",
8471
+ title: "Pair with the user's Shield",
8472
+ description: "Bind this assistant to the user's Decionis Shield with the six-character pairing code shown in the Shield app (AI spending allowance \u2192 Bind your own agent). Ask the user for the code; never guess one. After pairing, shield.request_purchase and the other shield.* tools ask the user's own spending rules.",
8473
+ inputSchema: {
8474
+ type: "object",
8475
+ required: ["code"],
8476
+ properties: {
8477
+ code: { type: "string", pattern: "^[A-Za-z0-9]{6,8}$", description: "The code from the Shield app." },
8478
+ displayName: {
8479
+ type: "string",
8480
+ maxLength: 120,
8481
+ description: "How the Shield app should list this assistant (default: the client and this computer)."
8482
+ }
8483
+ },
8484
+ additionalProperties: false
8485
+ },
8486
+ annotations: networkMutation,
8487
+ handler: async (args) => {
8488
+ const code = typeof args.code === "string" ? args.code.trim().toUpperCase() : "";
8489
+ if (!/^[A-Z0-9]{6,8}$/.test(code)) {
8490
+ return { content: text4("A pairing code is 6 to 8 letters and digits. Ask the user for the code shown in the Shield app."), isError: true };
8491
+ }
8492
+ const displayName = typeof args.displayName === "string" && args.displayName.trim() ? args.displayName.trim().slice(0, 120) : defaultAgentDisplayName(options.clientName?.());
8493
+ const agentId = store.agentId();
8494
+ let response;
8495
+ try {
8496
+ response = await fetchImpl(`${baseUrl()}/v1/consumer/pairing/claim`, {
8497
+ method: "POST",
8498
+ headers: {
8499
+ accept: "application/json",
8500
+ "content-type": "application/json",
8501
+ "x-shield-surface": "mcp"
8502
+ },
8503
+ body: JSON.stringify({
8504
+ code,
8505
+ agent: { app_id: SHIELD_MCP_APP_ID, agent_id: agentId, display_name: displayName }
8506
+ })
8507
+ });
8508
+ } catch (error) {
8509
+ return {
8510
+ content: text4({
8511
+ error: "SHIELD_PAIRING_UNREACHABLE",
8512
+ message: `Could not reach Shield: ${error instanceof Error ? error.message : String(error)}. Nothing was paired.`
8513
+ }),
8514
+ isError: true
8515
+ };
8516
+ }
8517
+ const payload = await response.json().catch(() => null);
8518
+ if (!response.ok || !payload || typeof payload.api_key !== "string" || typeof payload.app_id !== "string") {
8519
+ const code_ = payload && typeof payload.error === "string" ? payload.error : `HTTP ${response.status}`;
8520
+ const hint = code_ === "PAIRING_CODE_INVALID" ? "The code is wrong, expired (ten minutes) or already used. Ask the user for a fresh one from the Shield app." : "Nothing was paired.";
8521
+ return { content: text4({ error: code_, message: hint }), isError: true };
8522
+ }
8523
+ const integration = payload.integration ?? {};
8524
+ const credential = {
8525
+ apiKey: payload.api_key,
8526
+ appId: payload.app_id,
8527
+ baseUrl: baseUrl(),
8528
+ integrationId: typeof integration.integrationId === "string" ? integration.integrationId : void 0,
8529
+ displayName,
8530
+ agentId,
8531
+ pairedAt: (/* @__PURE__ */ new Date()).toISOString()
8532
+ };
8533
+ store.write(credential);
8534
+ const limit = typeof payload.weekly_limit === "number" ? payload.weekly_limit : null;
8535
+ return {
8536
+ content: text4({
8537
+ paired: true,
8538
+ rotated: payload.rotated === true,
8539
+ displayName,
8540
+ weeklyLimit: limit,
8541
+ storedAt: store.location(),
8542
+ next: "The shield.* tools now ask this user's own Shield. Before any purchase, call shield.request_purchase; execute only on ALLOW."
8543
+ })
8544
+ };
8545
+ }
8546
+ };
8547
+ }
8287
8548
  function createShieldTools(shield) {
8288
8549
  const authorize = async (args) => {
8289
8550
  if (typeof args.amount !== "number" || typeof args.currency !== "string" || typeof args.purpose !== "string") {
8290
8551
  return {
8291
- content: text3(
8552
+ content: text4(
8292
8553
  "amount, currency, and purpose are required. Do not execute the transaction."
8293
8554
  ),
8294
8555
  isError: true
@@ -8343,7 +8604,7 @@ function createShieldTools(shield) {
8343
8604
  },
8344
8605
  annotations: networkMutation,
8345
8606
  handler: async (args) => typeof args.decisionId === "string" ? shieldResult(async () => await shield.requestApproval(args.decisionId)) : {
8346
- content: text3("decisionId is required. Do not execute the transaction."),
8607
+ content: text4("decisionId is required. Do not execute the transaction."),
8347
8608
  isError: true
8348
8609
  }
8349
8610
  },
@@ -8359,7 +8620,7 @@ function createShieldTools(shield) {
8359
8620
  },
8360
8621
  annotations: networkRead,
8361
8622
  handler: async (args) => typeof args.decisionId === "string" ? shieldResult(async () => await shield.decision(args.decisionId)) : {
8362
- content: text3("decisionId is required. Do not execute the transaction."),
8623
+ content: text4("decisionId is required. Do not execute the transaction."),
8363
8624
  isError: true
8364
8625
  }
8365
8626
  }
@@ -8367,10 +8628,10 @@ function createShieldTools(shield) {
8367
8628
  }
8368
8629
  async function shieldResult(work) {
8369
8630
  try {
8370
- return { content: text3(await work()) };
8631
+ return { content: text4(await work()) };
8371
8632
  } catch (error) {
8372
8633
  return {
8373
- content: text3({
8634
+ content: text4({
8374
8635
  error: error instanceof Error ? error.name : "ShieldError",
8375
8636
  message: error instanceof Error ? error.message : String(error),
8376
8637
  safeToExecute: false
@@ -8387,16 +8648,15 @@ if (presenceKey && !process.env.DECIONIS_PRESENCE_SERVICE_TOKEN) {
8387
8648
  }
8388
8649
  var presenceConfigured = presenceKey.length > 0;
8389
8650
  var gateway = new LocalDecionisGateway();
8390
- var shieldConfigured = Boolean(
8391
- process.env.DECIONIS_SHIELD_API_KEY && process.env.DECIONIS_SHIELD_APP_ID
8392
- );
8651
+ var shieldClient = new ShieldClient();
8393
8652
  var handler = createMcpHandler({
8394
8653
  serverInfo: { name: "decionis-mcp", version: MCP_SERVER_VERSION },
8395
8654
  tools: createDecionisTools(
8396
8655
  gateway,
8397
8656
  [
8398
8657
  ...createKubernetesMcpToolsFromEnvironment(),
8399
- ...shieldConfigured ? createShieldTools(new ShieldClient()) : []
8658
+ createShieldPairTool(),
8659
+ ...createShieldTools(shieldClient)
8400
8660
  ],
8401
8661
  { presenceConfigured }
8402
8662
  )