@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/README.md +24 -2
- package/dist/AgentHookCli.js +2 -2
- package/dist/ManagedAgentHookCli.js +328 -194
- package/dist/agent/AgentIndex.js +2 -2
- package/dist/{chunk-7QNV7MXC.js → chunk-ABHNQFWI.js} +2 -2
- package/dist/{chunk-NF5JL6QR.js → chunk-LIVLO4MV.js} +329 -195
- package/dist/chunk-LIVLO4MV.js.map +1 -0
- package/dist/index.js +312 -52
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-NF5JL6QR.js.map +0 -1
- /package/dist/{chunk-7QNV7MXC.js.map → chunk-ABHNQFWI.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
external_exports,
|
|
6
6
|
normalizeVerdict,
|
|
7
7
|
sha256Hex
|
|
8
|
-
} from "./chunk-
|
|
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.
|
|
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
|
|
637
|
-
if (Buffer.byteLength(
|
|
638
|
-
return
|
|
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
|
|
675
|
-
return { response, text:
|
|
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:
|
|
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 (!
|
|
692
|
+
if (!text5) return { response, body: null };
|
|
693
693
|
try {
|
|
694
|
-
return { response, body: JSON.parse(
|
|
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-
|
|
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
|
|
6955
|
-
if (!
|
|
7000
|
+
const text22 = boundedString(value, 2048);
|
|
7001
|
+
if (!text22) return null;
|
|
6956
7002
|
try {
|
|
6957
|
-
const url = new URL(
|
|
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
|
|
7414
|
-
if (
|
|
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
|
|
7468
|
+
const hostname2 = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
7421
7469
|
let addresses;
|
|
7422
|
-
if (isIP(
|
|
7423
|
-
addresses = [{ address:
|
|
7470
|
+
if (isIP(hostname2) !== 0) {
|
|
7471
|
+
addresses = [{ address: hostname2, family: isIP(hostname2) }];
|
|
7424
7472
|
} else {
|
|
7425
7473
|
try {
|
|
7426
|
-
addresses = await lookup(
|
|
7474
|
+
addresses = await lookup(hostname2, { all: true, verbatim: true });
|
|
7427
7475
|
} catch {
|
|
7428
|
-
throw new Error(`Unable to resolve verification host: ${
|
|
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(
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
8385
|
+
const credential = this.credentials();
|
|
8386
|
+
if (!credential || !credential.apiKey || !credential.appId) {
|
|
8209
8387
|
throw new ShieldClientError(
|
|
8210
8388
|
"SHIELD_MCP_CONFIGURATION_REQUIRED",
|
|
8211
|
-
"
|
|
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(`${
|
|
8392
|
+
const response = await this.fetchImpl(`${credential.baseUrl}${path}`, {
|
|
8215
8393
|
method,
|
|
8216
8394
|
headers: {
|
|
8217
8395
|
accept: "application/json",
|
|
8218
|
-
authorization: `Bearer ${
|
|
8396
|
+
authorization: `Bearer ${credential.apiKey}`,
|
|
8219
8397
|
"content-type": "application/json",
|
|
8220
|
-
"x-shield-app-id":
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
8631
|
+
return { content: text4(await work()) };
|
|
8371
8632
|
} catch (error) {
|
|
8372
8633
|
return {
|
|
8373
|
-
content:
|
|
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
|
|
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
|
-
|
|
8658
|
+
createShieldPairTool(),
|
|
8659
|
+
...createShieldTools(shieldClient)
|
|
8400
8660
|
],
|
|
8401
8661
|
{ presenceConfigured }
|
|
8402
8662
|
)
|