@decionis/mcp 0.2.0 → 0.4.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/.claude-plugin/plugin.json +1 -1
- package/README.md +30 -2
- package/dist/AgentHookCli.js +2 -2
- package/dist/ManagedAgentHookCli.js +1949 -1105
- package/dist/agent/AgentIndex.js +2 -2
- package/dist/{chunk-NF5JL6QR.js → chunk-5Q6BCK32.js} +1309 -465
- package/dist/chunk-5Q6BCK32.js.map +1 -0
- package/dist/{chunk-7QNV7MXC.js → chunk-GCE6PUYD.js} +2 -2
- package/dist/index.js +370 -51
- package/dist/index.js.map +1 -1
- package/package.json +22 -22
- package/dist/chunk-NF5JL6QR.js.map +0 -1
- /package/dist/{chunk-7QNV7MXC.js.map → chunk-GCE6PUYD.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
LocalDecionisGateway,
|
|
4
4
|
PolicyFileRepository,
|
|
5
5
|
normalizeVerdict
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-5Q6BCK32.js";
|
|
7
7
|
|
|
8
8
|
// src/agent/AgentContracts.ts
|
|
9
9
|
var AgentHookInputError = class extends Error {
|
|
@@ -498,4 +498,4 @@ export {
|
|
|
498
498
|
AgentHostRegistry,
|
|
499
499
|
AgentHookCommand
|
|
500
500
|
};
|
|
501
|
-
//# sourceMappingURL=chunk-
|
|
501
|
+
//# sourceMappingURL=chunk-GCE6PUYD.js.map
|
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-5Q6BCK32.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.4.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) {
|
|
@@ -606,7 +606,7 @@ function createDecionisTools(gateway2, additionalTools = [], options = {}) {
|
|
|
606
606
|
// src/kubernetes/KubernetesActionPlanner.ts
|
|
607
607
|
import { randomUUID } from "crypto";
|
|
608
608
|
|
|
609
|
-
//
|
|
609
|
+
// ../../../Decionis/packages/sdk-node/dist/index.js
|
|
610
610
|
import { createHash as createHash2 } from "crypto";
|
|
611
611
|
import { createHash as createHash3 } from "crypto";
|
|
612
612
|
var DEFAULT_SKEW_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -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
|
}
|
|
@@ -2310,7 +2310,7 @@ var KubectlCommandRunner = class {
|
|
|
2310
2310
|
// src/kubernetes/KubernetesOperations.ts
|
|
2311
2311
|
import { createHash as createHash4 } from "crypto";
|
|
2312
2312
|
|
|
2313
|
-
//
|
|
2313
|
+
// ../../../Decionis/packages/dossier-verify/dist/chunk-WNX2CYWD.js
|
|
2314
2314
|
import { createHash, createPublicKey, verify as verifyEd25519 } from "crypto";
|
|
2315
2315
|
import { lookup } from "dns/promises";
|
|
2316
2316
|
import { request as httpsRequest } from "https";
|
|
@@ -6951,10 +6951,10 @@ function validEd25519Signature(value) {
|
|
|
6951
6951
|
}
|
|
6952
6952
|
function normalizedPublicJwksUrl(value) {
|
|
6953
6953
|
if (value === void 0) return void 0;
|
|
6954
|
-
const
|
|
6955
|
-
if (!
|
|
6954
|
+
const text22 = boundedString(value, 2048);
|
|
6955
|
+
if (!text22) return null;
|
|
6956
6956
|
try {
|
|
6957
|
-
const url = new URL(
|
|
6957
|
+
const url = new URL(text22);
|
|
6958
6958
|
if (url.protocol !== "https:" || url.username || url.password) return null;
|
|
6959
6959
|
return url.toString();
|
|
6960
6960
|
} catch {
|
|
@@ -7320,6 +7320,8 @@ function extractDossierPayload(body) {
|
|
|
7320
7320
|
const candidate = asRecord2(record2[key]);
|
|
7321
7321
|
if (asRecord2(candidate.integrity).proof_bundle !== void 0) return candidate;
|
|
7322
7322
|
}
|
|
7323
|
+
const payload = asRecord2(asRecord2(record2.dossier).dossier_payload);
|
|
7324
|
+
if (asRecord2(payload.integrity).proof_bundle !== void 0) return payload;
|
|
7323
7325
|
return record2;
|
|
7324
7326
|
}
|
|
7325
7327
|
function resolveJwksUrl(payload, dossierUrl) {
|
|
@@ -7410,22 +7412,22 @@ async function assertSafeRemoteUrl(urlText) {
|
|
|
7410
7412
|
if (url.username || url.password) {
|
|
7411
7413
|
throw new Error(`Verification URL must not contain credentials: ${urlText}`);
|
|
7412
7414
|
}
|
|
7413
|
-
const
|
|
7414
|
-
if (
|
|
7415
|
+
const hostname2 = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
7416
|
+
if (hostname2 === "localhost" || hostname2.endsWith(".localhost") || hostname2.endsWith(".local") || isIP(hostname2) !== 0 && isForbiddenIp(hostname2)) {
|
|
7415
7417
|
throw new Error(`Verification URL resolves to a non-public destination: ${urlText}`);
|
|
7416
7418
|
}
|
|
7417
7419
|
return url;
|
|
7418
7420
|
}
|
|
7419
7421
|
async function pinnedHttpsFetch(url, signal) {
|
|
7420
|
-
const
|
|
7422
|
+
const hostname2 = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
7421
7423
|
let addresses;
|
|
7422
|
-
if (isIP(
|
|
7423
|
-
addresses = [{ address:
|
|
7424
|
+
if (isIP(hostname2) !== 0) {
|
|
7425
|
+
addresses = [{ address: hostname2, family: isIP(hostname2) }];
|
|
7424
7426
|
} else {
|
|
7425
7427
|
try {
|
|
7426
|
-
addresses = await lookup(
|
|
7428
|
+
addresses = await lookup(hostname2, { all: true, verbatim: true });
|
|
7427
7429
|
} catch {
|
|
7428
|
-
throw new Error(`Unable to resolve verification host: ${
|
|
7430
|
+
throw new Error(`Unable to resolve verification host: ${hostname2}`);
|
|
7429
7431
|
}
|
|
7430
7432
|
}
|
|
7431
7433
|
if (addresses.length === 0 || addresses.some(({ address }) => isForbiddenIp(address))) {
|
|
@@ -7441,7 +7443,7 @@ async function pinnedHttpsFetch(url, signal) {
|
|
|
7441
7443
|
port: url.port || 443,
|
|
7442
7444
|
path: `${url.pathname}${url.search}`,
|
|
7443
7445
|
method: "GET",
|
|
7444
|
-
servername: isIP(
|
|
7446
|
+
servername: isIP(hostname2) === 0 ? hostname2 : void 0,
|
|
7445
7447
|
headers: { accept: "application/json", host: url.host },
|
|
7446
7448
|
signal
|
|
7447
7449
|
},
|
|
@@ -7577,6 +7579,58 @@ async function verifyDossierFromUrls(input) {
|
|
|
7577
7579
|
payload
|
|
7578
7580
|
};
|
|
7579
7581
|
}
|
|
7582
|
+
var AGENT_SAFE_INTENT_PROTOCOL = "agent-safe.intent/1";
|
|
7583
|
+
var MAX_BYTES = 100 * 1024;
|
|
7584
|
+
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;
|
|
7585
|
+
var INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
|
|
7586
|
+
var ACTION_TYPE = /^[a-z][a-z0-9._:-]*$/;
|
|
7587
|
+
var DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
7588
|
+
function isPlainObject3(value) {
|
|
7589
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
7590
|
+
const prototype = Object.getPrototypeOf(value);
|
|
7591
|
+
return prototype === Object.prototype || prototype === null;
|
|
7592
|
+
}
|
|
7593
|
+
var text2 = (min, max) => (value) => typeof value === "string" && value.length >= min && value.length <= max;
|
|
7594
|
+
var pattern = (expression) => (value) => typeof value === "string" && expression.test(value);
|
|
7595
|
+
var ACTOR = {
|
|
7596
|
+
required: { id: text2(1, 200), type: text2(1, 200) },
|
|
7597
|
+
optional: { runtime: text2(1, 200), trust_level: text2(1, 80) },
|
|
7598
|
+
closed: true
|
|
7599
|
+
};
|
|
7600
|
+
var ACTION = {
|
|
7601
|
+
required: {
|
|
7602
|
+
type: (value) => text2(1, 120)(value) && ACTION_TYPE.test(value),
|
|
7603
|
+
resource: text2(1, 500),
|
|
7604
|
+
parameters: isPlainObject3
|
|
7605
|
+
},
|
|
7606
|
+
optional: {},
|
|
7607
|
+
closed: true
|
|
7608
|
+
};
|
|
7609
|
+
var CONTEXT = {
|
|
7610
|
+
required: { idempotency_key: text2(1, 180) },
|
|
7611
|
+
optional: {},
|
|
7612
|
+
closed: false
|
|
7613
|
+
};
|
|
7614
|
+
var DOWNSTREAM_TARGET = {
|
|
7615
|
+
required: { system: text2(1, 200), operation: text2(1, 200) },
|
|
7616
|
+
optional: { environment: text2(1, 200), endpoint: text2(1, 500) },
|
|
7617
|
+
closed: true
|
|
7618
|
+
};
|
|
7619
|
+
var BINDING = {
|
|
7620
|
+
required: {
|
|
7621
|
+
protocol_version: (value) => value === AGENT_SAFE_INTENT_PROTOCOL,
|
|
7622
|
+
tenant_id: pattern(UUID),
|
|
7623
|
+
intent_id: pattern(UUID),
|
|
7624
|
+
captured_at: pattern(INSTANT),
|
|
7625
|
+
expires_at: pattern(INSTANT),
|
|
7626
|
+
actor: isPlainObject3,
|
|
7627
|
+
action: isPlainObject3,
|
|
7628
|
+
context: isPlainObject3,
|
|
7629
|
+
downstream_target: isPlainObject3
|
|
7630
|
+
},
|
|
7631
|
+
optional: { expected_effect_digest: pattern(DIGEST) },
|
|
7632
|
+
closed: true
|
|
7633
|
+
};
|
|
7580
7634
|
|
|
7581
7635
|
// src/kubernetes/KubernetesOperations.ts
|
|
7582
7636
|
var KubernetesOperations = class _KubernetesOperations {
|
|
@@ -7951,7 +8005,7 @@ function createKubernetesAuthorityClient(input) {
|
|
|
7951
8005
|
}
|
|
7952
8006
|
|
|
7953
8007
|
// src/kubernetes/KubernetesMcpTools.ts
|
|
7954
|
-
var
|
|
8008
|
+
var text3 = (value) => [
|
|
7955
8009
|
{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
|
|
7956
8010
|
];
|
|
7957
8011
|
var KubernetesMcpTools = class _KubernetesMcpTools {
|
|
@@ -7984,7 +8038,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
|
|
|
7984
8038
|
openWorldHint: true
|
|
7985
8039
|
},
|
|
7986
8040
|
handler: async (args) => ({
|
|
7987
|
-
content:
|
|
8041
|
+
content: text3(
|
|
7988
8042
|
await this.operations.plan(
|
|
7989
8043
|
_KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
|
|
7990
8044
|
_KubernetesMcpTools.optionalNamespace(args.namespace)
|
|
@@ -8004,7 +8058,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
|
|
|
8004
8058
|
openWorldHint: true
|
|
8005
8059
|
},
|
|
8006
8060
|
handler: async (args) => ({
|
|
8007
|
-
content:
|
|
8061
|
+
content: text3(
|
|
8008
8062
|
await this.operations.requestAuthority(
|
|
8009
8063
|
_KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
|
|
8010
8064
|
_KubernetesMcpTools.optionalNamespace(args.namespace)
|
|
@@ -8024,7 +8078,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
|
|
|
8024
8078
|
openWorldHint: true
|
|
8025
8079
|
},
|
|
8026
8080
|
handler: async (args) => ({
|
|
8027
|
-
content:
|
|
8081
|
+
content: text3(
|
|
8028
8082
|
await this.operations.apply(
|
|
8029
8083
|
_KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
|
|
8030
8084
|
_KubernetesMcpTools.optionalNamespace(args.namespace)
|
|
@@ -8053,7 +8107,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
|
|
|
8053
8107
|
openWorldHint: true
|
|
8054
8108
|
},
|
|
8055
8109
|
handler: async (args) => ({
|
|
8056
|
-
content:
|
|
8110
|
+
content: text3(
|
|
8057
8111
|
await this.operations.scale({
|
|
8058
8112
|
namespace: _KubernetesMcpTools.requiredDnsName(args.namespace, "namespace"),
|
|
8059
8113
|
name: _KubernetesMcpTools.requiredDnsName(args.name, "name"),
|
|
@@ -8074,7 +8128,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
|
|
|
8074
8128
|
openWorldHint: true
|
|
8075
8129
|
},
|
|
8076
8130
|
handler: async (args) => ({
|
|
8077
|
-
content:
|
|
8131
|
+
content: text3(
|
|
8078
8132
|
await this.operations.rollback(
|
|
8079
8133
|
_KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
|
|
8080
8134
|
_KubernetesMcpTools.optionalNamespace(args.namespace)
|
|
@@ -8102,7 +8156,7 @@ var KubernetesMcpTools = class _KubernetesMcpTools {
|
|
|
8102
8156
|
openWorldHint: true
|
|
8103
8157
|
},
|
|
8104
8158
|
handler: async (args) => ({
|
|
8105
|
-
content:
|
|
8159
|
+
content: text3(
|
|
8106
8160
|
await this.operations.verifyDossier(
|
|
8107
8161
|
_KubernetesMcpTools.requiredString(args.dossier_url, "dossier_url"),
|
|
8108
8162
|
_KubernetesMcpTools.requiredString(args.jwks_url, "jwks_url")
|
|
@@ -8180,17 +8234,94 @@ function boundedInteger(input, fallback, minimum, maximum) {
|
|
|
8180
8234
|
return value;
|
|
8181
8235
|
}
|
|
8182
8236
|
|
|
8237
|
+
// src/ShieldCredentialStore.ts
|
|
8238
|
+
import { mkdirSync, readFileSync, writeFileSync, chmodSync } from "fs";
|
|
8239
|
+
import { homedir, hostname } from "os";
|
|
8240
|
+
import { dirname, join } from "path";
|
|
8241
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
8242
|
+
var SHIELD_MCP_APP_ID = "decionis-mcp";
|
|
8243
|
+
var DEFAULT_SHIELD_API_URL = "https://api.shield.decionis.com";
|
|
8244
|
+
function defaultCredentialPath() {
|
|
8245
|
+
return process.env.DECIONIS_SHIELD_CREDENTIAL_FILE?.trim() || join(homedir(), ".decionis", "shield-credential.json");
|
|
8246
|
+
}
|
|
8247
|
+
var ShieldCredentialStore = class {
|
|
8248
|
+
constructor(path = defaultCredentialPath()) {
|
|
8249
|
+
this.path = path;
|
|
8250
|
+
}
|
|
8251
|
+
/** The credential to use now: the environment first, then the paired file. */
|
|
8252
|
+
resolve() {
|
|
8253
|
+
const envKey = process.env.DECIONIS_SHIELD_API_KEY?.trim();
|
|
8254
|
+
const envApp = process.env.DECIONIS_SHIELD_APP_ID?.trim();
|
|
8255
|
+
const baseUrl = (process.env.DECIONIS_SHIELD_API_URL?.trim() || DEFAULT_SHIELD_API_URL).replace(
|
|
8256
|
+
/\/+$/,
|
|
8257
|
+
""
|
|
8258
|
+
);
|
|
8259
|
+
if (envKey && envApp) {
|
|
8260
|
+
return { apiKey: envKey, appId: envApp, baseUrl, agentId: envApp };
|
|
8261
|
+
}
|
|
8262
|
+
const stored = this.read();
|
|
8263
|
+
if (!stored) return null;
|
|
8264
|
+
return { ...stored, baseUrl: process.env.DECIONIS_SHIELD_API_URL?.trim() || stored.baseUrl };
|
|
8265
|
+
}
|
|
8266
|
+
read() {
|
|
8267
|
+
try {
|
|
8268
|
+
const raw = JSON.parse(readFileSync(this.path, "utf8"));
|
|
8269
|
+
if (typeof raw.apiKey !== "string" || typeof raw.appId !== "string") return null;
|
|
8270
|
+
return {
|
|
8271
|
+
apiKey: raw.apiKey,
|
|
8272
|
+
appId: raw.appId,
|
|
8273
|
+
baseUrl: typeof raw.baseUrl === "string" ? raw.baseUrl : DEFAULT_SHIELD_API_URL,
|
|
8274
|
+
integrationId: typeof raw.integrationId === "string" ? raw.integrationId : void 0,
|
|
8275
|
+
displayName: typeof raw.displayName === "string" ? raw.displayName : void 0,
|
|
8276
|
+
agentId: typeof raw.agentId === "string" ? raw.agentId : randomUUID2(),
|
|
8277
|
+
pairedAt: typeof raw.pairedAt === "string" ? raw.pairedAt : void 0
|
|
8278
|
+
};
|
|
8279
|
+
} catch {
|
|
8280
|
+
return null;
|
|
8281
|
+
}
|
|
8282
|
+
}
|
|
8283
|
+
/** The install's stable id: the stored one, else a new one written at first pairing. */
|
|
8284
|
+
agentId() {
|
|
8285
|
+
return this.read()?.agentId ?? randomUUID2();
|
|
8286
|
+
}
|
|
8287
|
+
write(credential) {
|
|
8288
|
+
mkdirSync(dirname(this.path), { recursive: true, mode: 448 });
|
|
8289
|
+
writeFileSync(this.path, `${JSON.stringify(credential, null, 2)}
|
|
8290
|
+
`, { mode: 384 });
|
|
8291
|
+
try {
|
|
8292
|
+
chmodSync(this.path, 384);
|
|
8293
|
+
} catch {
|
|
8294
|
+
}
|
|
8295
|
+
}
|
|
8296
|
+
location() {
|
|
8297
|
+
return this.path;
|
|
8298
|
+
}
|
|
8299
|
+
};
|
|
8300
|
+
function defaultAgentDisplayName(clientName) {
|
|
8301
|
+
const host = hostname().split(".")[0] || "this computer";
|
|
8302
|
+
return clientName ? `${clientName} on ${host}` : `Decionis MCP on ${host}`;
|
|
8303
|
+
}
|
|
8304
|
+
|
|
8183
8305
|
// src/ShieldClient.ts
|
|
8184
8306
|
var ShieldClient = class {
|
|
8185
|
-
baseUrl;
|
|
8186
|
-
apiKey;
|
|
8187
|
-
appId;
|
|
8188
8307
|
fetchImpl;
|
|
8308
|
+
credentials;
|
|
8189
8309
|
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
8310
|
this.fetchImpl = options.fetch ?? fetch;
|
|
8311
|
+
if (options.credentials) {
|
|
8312
|
+
this.credentials = options.credentials;
|
|
8313
|
+
} else if (options.apiKey !== void 0 || options.appId !== void 0 || options.baseUrl) {
|
|
8314
|
+
const fixed = options.apiKey && options.appId ? {
|
|
8315
|
+
apiKey: options.apiKey,
|
|
8316
|
+
appId: options.appId,
|
|
8317
|
+
baseUrl: (options.baseUrl ?? DEFAULT_SHIELD_API_URL).replace(/\/+$/, ""),
|
|
8318
|
+
agentId: options.appId
|
|
8319
|
+
} : null;
|
|
8320
|
+
this.credentials = () => fixed;
|
|
8321
|
+
} else {
|
|
8322
|
+
const store = new ShieldCredentialStore();
|
|
8323
|
+
this.credentials = () => store.resolve();
|
|
8324
|
+
}
|
|
8194
8325
|
}
|
|
8195
8326
|
authorize(request) {
|
|
8196
8327
|
return this.json("/v1/shield/authorize", "POST", request);
|
|
@@ -8205,19 +8336,20 @@ var ShieldClient = class {
|
|
|
8205
8336
|
return this.json(`/v1/shield/decisions/${encodeURIComponent(decisionId)}`, "GET");
|
|
8206
8337
|
}
|
|
8207
8338
|
async json(path, method, body) {
|
|
8208
|
-
|
|
8339
|
+
const credential = this.credentials();
|
|
8340
|
+
if (!credential || !credential.apiKey || !credential.appId) {
|
|
8209
8341
|
throw new ShieldClientError(
|
|
8210
8342
|
"SHIELD_MCP_CONFIGURATION_REQUIRED",
|
|
8211
|
-
"
|
|
8343
|
+
"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
8344
|
);
|
|
8213
8345
|
}
|
|
8214
|
-
const response = await this.fetchImpl(`${
|
|
8346
|
+
const response = await this.fetchImpl(`${credential.baseUrl}${path}`, {
|
|
8215
8347
|
method,
|
|
8216
8348
|
headers: {
|
|
8217
8349
|
accept: "application/json",
|
|
8218
|
-
authorization: `Bearer ${
|
|
8350
|
+
authorization: `Bearer ${credential.apiKey}`,
|
|
8219
8351
|
"content-type": "application/json",
|
|
8220
|
-
"x-shield-app-id":
|
|
8352
|
+
"x-shield-app-id": credential.appId,
|
|
8221
8353
|
"x-shield-environment": "production"
|
|
8222
8354
|
},
|
|
8223
8355
|
...method === "POST" ? { body: JSON.stringify(body ?? {}) } : {}
|
|
@@ -8245,7 +8377,7 @@ function asRecord3(value) {
|
|
|
8245
8377
|
}
|
|
8246
8378
|
|
|
8247
8379
|
// src/ShieldTools.ts
|
|
8248
|
-
var
|
|
8380
|
+
var text4 = (value) => [
|
|
8249
8381
|
{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
|
|
8250
8382
|
];
|
|
8251
8383
|
var purchaseProperties = {
|
|
@@ -8284,11 +8416,199 @@ var networkRead = {
|
|
|
8284
8416
|
idempotentHint: true,
|
|
8285
8417
|
openWorldHint: true
|
|
8286
8418
|
};
|
|
8419
|
+
function createShieldPairTool(options = {}) {
|
|
8420
|
+
const store = options.store ?? new ShieldCredentialStore();
|
|
8421
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
8422
|
+
const baseUrl = () => (options.baseUrl?.() ?? process.env.DECIONIS_SHIELD_API_URL?.trim() ?? "").replace(
|
|
8423
|
+
/\/+$/,
|
|
8424
|
+
""
|
|
8425
|
+
) || "https://api.shield.decionis.com";
|
|
8426
|
+
const pendingEmail = /* @__PURE__ */ new Map();
|
|
8427
|
+
const post = async (path, body) => fetchImpl(`${baseUrl()}${path}`, {
|
|
8428
|
+
method: "POST",
|
|
8429
|
+
headers: {
|
|
8430
|
+
accept: "application/json",
|
|
8431
|
+
"content-type": "application/json",
|
|
8432
|
+
"x-shield-surface": "mcp"
|
|
8433
|
+
},
|
|
8434
|
+
body: JSON.stringify(body)
|
|
8435
|
+
});
|
|
8436
|
+
const unreachable = (error) => ({
|
|
8437
|
+
content: text4({
|
|
8438
|
+
error: "SHIELD_PAIRING_UNREACHABLE",
|
|
8439
|
+
message: `Could not reach Shield: ${error instanceof Error ? error.message : String(error)}. Nothing was paired.`
|
|
8440
|
+
}),
|
|
8441
|
+
isError: true
|
|
8442
|
+
});
|
|
8443
|
+
const keep = async (response, displayName, agentId, hints) => {
|
|
8444
|
+
const payload = await response.json().catch(() => null);
|
|
8445
|
+
if (response.ok && payload?.upgrade_required === true) {
|
|
8446
|
+
return {
|
|
8447
|
+
content: text4({
|
|
8448
|
+
error: "SHIELD_SOURCE_LIMIT",
|
|
8449
|
+
message: "The user's free Shield already covers its limit of spending sources. They can remove one in the Shield app or upgrade; nothing was paired."
|
|
8450
|
+
}),
|
|
8451
|
+
isError: true
|
|
8452
|
+
};
|
|
8453
|
+
}
|
|
8454
|
+
if (!response.ok || !payload || typeof payload.api_key !== "string" || typeof payload.app_id !== "string") {
|
|
8455
|
+
const code_ = payload && typeof payload.error === "string" ? payload.error : `HTTP ${response.status}`;
|
|
8456
|
+
return {
|
|
8457
|
+
content: text4({ error: code_, message: hints[code_] ?? "Nothing was paired." }),
|
|
8458
|
+
isError: true
|
|
8459
|
+
};
|
|
8460
|
+
}
|
|
8461
|
+
const integration = payload.integration ?? {};
|
|
8462
|
+
const credential = {
|
|
8463
|
+
apiKey: payload.api_key,
|
|
8464
|
+
appId: payload.app_id,
|
|
8465
|
+
baseUrl: baseUrl(),
|
|
8466
|
+
integrationId: typeof integration.integrationId === "string" ? integration.integrationId : void 0,
|
|
8467
|
+
displayName,
|
|
8468
|
+
agentId,
|
|
8469
|
+
pairedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8470
|
+
};
|
|
8471
|
+
store.write(credential);
|
|
8472
|
+
const limit = typeof payload.weekly_limit === "number" ? payload.weekly_limit : null;
|
|
8473
|
+
return {
|
|
8474
|
+
content: text4({
|
|
8475
|
+
paired: true,
|
|
8476
|
+
rotated: payload.rotated === true,
|
|
8477
|
+
displayName,
|
|
8478
|
+
weeklyLimit: limit,
|
|
8479
|
+
storedAt: store.location(),
|
|
8480
|
+
next: "The shield.* tools now ask this user's own Shield. Before any purchase, call shield.request_purchase; execute only on ALLOW."
|
|
8481
|
+
})
|
|
8482
|
+
};
|
|
8483
|
+
};
|
|
8484
|
+
return {
|
|
8485
|
+
name: "shield.pair",
|
|
8486
|
+
title: "Pair with the user's Shield",
|
|
8487
|
+
description: "Bind this assistant to the user's Decionis Shield, one of two ways. (1) The six-character pairing code shown in the Shield app (AI spending allowance \u2192 Bind your own agent): pass it as code. (2) The user's email: pass email, and Shield emails them a 6-digit code; ask the user for it, then call again with email and emailCode. Ask the user; never guess a code. After pairing, shield.request_purchase and the other shield.* tools ask the user's own spending rules.",
|
|
8488
|
+
inputSchema: {
|
|
8489
|
+
type: "object",
|
|
8490
|
+
properties: {
|
|
8491
|
+
code: {
|
|
8492
|
+
type: "string",
|
|
8493
|
+
pattern: "^[A-Za-z0-9]{6,8}$",
|
|
8494
|
+
description: "The pairing code from the Shield app."
|
|
8495
|
+
},
|
|
8496
|
+
email: {
|
|
8497
|
+
type: "string",
|
|
8498
|
+
maxLength: 254,
|
|
8499
|
+
description: "The user's Shield email, to be sent a sign-in code."
|
|
8500
|
+
},
|
|
8501
|
+
emailCode: {
|
|
8502
|
+
type: "string",
|
|
8503
|
+
pattern: "^[0-9]{6}$",
|
|
8504
|
+
description: "The 6-digit code Shield emailed to the user."
|
|
8505
|
+
},
|
|
8506
|
+
displayName: {
|
|
8507
|
+
type: "string",
|
|
8508
|
+
maxLength: 120,
|
|
8509
|
+
description: "How the Shield app should list this assistant (default: the client and this computer)."
|
|
8510
|
+
}
|
|
8511
|
+
},
|
|
8512
|
+
additionalProperties: false
|
|
8513
|
+
},
|
|
8514
|
+
annotations: networkMutation,
|
|
8515
|
+
handler: async (args) => {
|
|
8516
|
+
const displayName = typeof args.displayName === "string" && args.displayName.trim() ? args.displayName.trim().slice(0, 120) : defaultAgentDisplayName(options.clientName?.());
|
|
8517
|
+
const agent = {
|
|
8518
|
+
app_id: SHIELD_MCP_APP_ID,
|
|
8519
|
+
agent_id: store.agentId(),
|
|
8520
|
+
display_name: displayName
|
|
8521
|
+
};
|
|
8522
|
+
const email = typeof args.email === "string" ? args.email.trim().toLowerCase() : "";
|
|
8523
|
+
const emailCode = typeof args.emailCode === "string" ? args.emailCode.trim() : "";
|
|
8524
|
+
if (email) {
|
|
8525
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
8526
|
+
return {
|
|
8527
|
+
content: text4(
|
|
8528
|
+
"That is not an email address. Ask the user for the email they use with Shield."
|
|
8529
|
+
),
|
|
8530
|
+
isError: true
|
|
8531
|
+
};
|
|
8532
|
+
}
|
|
8533
|
+
if (!emailCode) {
|
|
8534
|
+
let response3;
|
|
8535
|
+
try {
|
|
8536
|
+
response3 = await post("/v1/consumer/auth/start", { email, scope: "agent" });
|
|
8537
|
+
} catch (error) {
|
|
8538
|
+
return unreachable(error);
|
|
8539
|
+
}
|
|
8540
|
+
const payload = await response3.json().catch(() => null);
|
|
8541
|
+
if (!response3.ok || !payload || typeof payload.challenge_id !== "string") {
|
|
8542
|
+
return {
|
|
8543
|
+
content: text4({
|
|
8544
|
+
error: response3.status === 429 ? "SHIELD_TOO_MANY_CODES" : `HTTP ${response3.status}`,
|
|
8545
|
+
message: response3.status === 429 ? "Shield has sent several codes already. Wait a minute, then try again." : "Shield could not send a code right now. Nothing was paired."
|
|
8546
|
+
}),
|
|
8547
|
+
isError: true
|
|
8548
|
+
};
|
|
8549
|
+
}
|
|
8550
|
+
pendingEmail.set(email, payload.challenge_id);
|
|
8551
|
+
return {
|
|
8552
|
+
content: text4({
|
|
8553
|
+
emailed: true,
|
|
8554
|
+
email,
|
|
8555
|
+
next: `Ask the user for the 6-digit code Shield just emailed to ${email}, then call shield.pair again with email and emailCode.`
|
|
8556
|
+
})
|
|
8557
|
+
};
|
|
8558
|
+
}
|
|
8559
|
+
if (!/^[0-9]{6}$/.test(emailCode)) {
|
|
8560
|
+
return {
|
|
8561
|
+
content: text4(
|
|
8562
|
+
"The emailed code is 6 digits. Ask the user to read it from the Shield email."
|
|
8563
|
+
),
|
|
8564
|
+
isError: true
|
|
8565
|
+
};
|
|
8566
|
+
}
|
|
8567
|
+
let response2;
|
|
8568
|
+
try {
|
|
8569
|
+
response2 = await post("/v1/consumer/auth/verify", {
|
|
8570
|
+
email,
|
|
8571
|
+
code: emailCode,
|
|
8572
|
+
...pendingEmail.has(email) ? { challenge_id: pendingEmail.get(email) } : {},
|
|
8573
|
+
scope: "agent",
|
|
8574
|
+
agent
|
|
8575
|
+
});
|
|
8576
|
+
} catch (error) {
|
|
8577
|
+
return unreachable(error);
|
|
8578
|
+
}
|
|
8579
|
+
if (response2.ok) pendingEmail.delete(email);
|
|
8580
|
+
return keep(response2, displayName, agent.agent_id, {
|
|
8581
|
+
INVALID_CODE: "The code is wrong or has expired (five minutes). Ask the user for the newest code, or call shield.pair with email again to send a fresh one.",
|
|
8582
|
+
OTP_TOO_MANY_ATTEMPTS: "Too many wrong codes for that email. Call shield.pair with email again to send a fresh one.",
|
|
8583
|
+
CHALLENGE_NOT_FOUND: "No code is waiting for that email. Call shield.pair with email first."
|
|
8584
|
+
});
|
|
8585
|
+
}
|
|
8586
|
+
const code = typeof args.code === "string" ? args.code.trim().toUpperCase() : "";
|
|
8587
|
+
if (!/^[A-Z0-9]{6,8}$/.test(code)) {
|
|
8588
|
+
return {
|
|
8589
|
+
content: text4(
|
|
8590
|
+
"Give either the pairing code shown in the Shield app (6 to 8 letters and digits) or the user's email. Ask the user; never guess."
|
|
8591
|
+
),
|
|
8592
|
+
isError: true
|
|
8593
|
+
};
|
|
8594
|
+
}
|
|
8595
|
+
let response;
|
|
8596
|
+
try {
|
|
8597
|
+
response = await post("/v1/consumer/pairing/claim", { code, agent });
|
|
8598
|
+
} catch (error) {
|
|
8599
|
+
return unreachable(error);
|
|
8600
|
+
}
|
|
8601
|
+
return keep(response, displayName, agent.agent_id, {
|
|
8602
|
+
PAIRING_CODE_INVALID: "The code is wrong, expired (ten minutes) or already used. Ask the user for a fresh one from the Shield app."
|
|
8603
|
+
});
|
|
8604
|
+
}
|
|
8605
|
+
};
|
|
8606
|
+
}
|
|
8287
8607
|
function createShieldTools(shield) {
|
|
8288
8608
|
const authorize = async (args) => {
|
|
8289
8609
|
if (typeof args.amount !== "number" || typeof args.currency !== "string" || typeof args.purpose !== "string") {
|
|
8290
8610
|
return {
|
|
8291
|
-
content:
|
|
8611
|
+
content: text4(
|
|
8292
8612
|
"amount, currency, and purpose are required. Do not execute the transaction."
|
|
8293
8613
|
),
|
|
8294
8614
|
isError: true
|
|
@@ -8343,7 +8663,7 @@ function createShieldTools(shield) {
|
|
|
8343
8663
|
},
|
|
8344
8664
|
annotations: networkMutation,
|
|
8345
8665
|
handler: async (args) => typeof args.decisionId === "string" ? shieldResult(async () => await shield.requestApproval(args.decisionId)) : {
|
|
8346
|
-
content:
|
|
8666
|
+
content: text4("decisionId is required. Do not execute the transaction."),
|
|
8347
8667
|
isError: true
|
|
8348
8668
|
}
|
|
8349
8669
|
},
|
|
@@ -8359,7 +8679,7 @@ function createShieldTools(shield) {
|
|
|
8359
8679
|
},
|
|
8360
8680
|
annotations: networkRead,
|
|
8361
8681
|
handler: async (args) => typeof args.decisionId === "string" ? shieldResult(async () => await shield.decision(args.decisionId)) : {
|
|
8362
|
-
content:
|
|
8682
|
+
content: text4("decisionId is required. Do not execute the transaction."),
|
|
8363
8683
|
isError: true
|
|
8364
8684
|
}
|
|
8365
8685
|
}
|
|
@@ -8367,10 +8687,10 @@ function createShieldTools(shield) {
|
|
|
8367
8687
|
}
|
|
8368
8688
|
async function shieldResult(work) {
|
|
8369
8689
|
try {
|
|
8370
|
-
return { content:
|
|
8690
|
+
return { content: text4(await work()) };
|
|
8371
8691
|
} catch (error) {
|
|
8372
8692
|
return {
|
|
8373
|
-
content:
|
|
8693
|
+
content: text4({
|
|
8374
8694
|
error: error instanceof Error ? error.name : "ShieldError",
|
|
8375
8695
|
message: error instanceof Error ? error.message : String(error),
|
|
8376
8696
|
safeToExecute: false
|
|
@@ -8387,16 +8707,15 @@ if (presenceKey && !process.env.DECIONIS_PRESENCE_SERVICE_TOKEN) {
|
|
|
8387
8707
|
}
|
|
8388
8708
|
var presenceConfigured = presenceKey.length > 0;
|
|
8389
8709
|
var gateway = new LocalDecionisGateway();
|
|
8390
|
-
var
|
|
8391
|
-
process.env.DECIONIS_SHIELD_API_KEY && process.env.DECIONIS_SHIELD_APP_ID
|
|
8392
|
-
);
|
|
8710
|
+
var shieldClient = new ShieldClient();
|
|
8393
8711
|
var handler = createMcpHandler({
|
|
8394
8712
|
serverInfo: { name: "decionis-mcp", version: MCP_SERVER_VERSION },
|
|
8395
8713
|
tools: createDecionisTools(
|
|
8396
8714
|
gateway,
|
|
8397
8715
|
[
|
|
8398
8716
|
...createKubernetesMcpToolsFromEnvironment(),
|
|
8399
|
-
|
|
8717
|
+
createShieldPairTool(),
|
|
8718
|
+
...createShieldTools(shieldClient)
|
|
8400
8719
|
],
|
|
8401
8720
|
{ presenceConfigured }
|
|
8402
8721
|
)
|