@peac/mcp-server 0.10.13
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/LICENSE +190 -0
- package/README.md +229 -0
- package/dist/cli.cjs +1708 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.mjs +1687 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/handlers/bundle.d.ts +10 -0
- package/dist/handlers/bundle.d.ts.map +1 -0
- package/dist/handlers/decode.d.ts +10 -0
- package/dist/handlers/decode.d.ts.map +1 -0
- package/dist/handlers/guards.d.ts +60 -0
- package/dist/handlers/guards.d.ts.map +1 -0
- package/dist/handlers/index.d.ts +8 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/inspect.d.ts +10 -0
- package/dist/handlers/inspect.d.ts.map +1 -0
- package/dist/handlers/issue.d.ts +10 -0
- package/dist/handlers/issue.d.ts.map +1 -0
- package/dist/handlers/types.d.ts +31 -0
- package/dist/handlers/types.d.ts.map +1 -0
- package/dist/handlers/verify.d.ts +12 -0
- package/dist/handlers/verify.d.ts.map +1 -0
- package/dist/index.cjs +1685 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1623 -0
- package/dist/index.mjs.map +1 -0
- package/dist/infra/constants.d.ts +14 -0
- package/dist/infra/constants.d.ts.map +1 -0
- package/dist/infra/errors.d.ts +36 -0
- package/dist/infra/errors.d.ts.map +1 -0
- package/dist/infra/index.d.ts +7 -0
- package/dist/infra/index.d.ts.map +1 -0
- package/dist/infra/jwks-loader.d.ts +13 -0
- package/dist/infra/jwks-loader.d.ts.map +1 -0
- package/dist/infra/key-loader.d.ts +14 -0
- package/dist/infra/key-loader.d.ts.map +1 -0
- package/dist/infra/path-safety.d.ts +35 -0
- package/dist/infra/path-safety.d.ts.map +1 -0
- package/dist/infra/policy.d.ts +130 -0
- package/dist/infra/policy.d.ts.map +1 -0
- package/dist/schemas/bundle.d.ts +71 -0
- package/dist/schemas/bundle.d.ts.map +1 -0
- package/dist/schemas/decode.d.ts +50 -0
- package/dist/schemas/decode.d.ts.map +1 -0
- package/dist/schemas/index.d.ts +6 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/inspect.d.ts +94 -0
- package/dist/schemas/inspect.d.ts.map +1 -0
- package/dist/schemas/issue.d.ts +123 -0
- package/dist/schemas/issue.d.ts.map +1 -0
- package/dist/schemas/verify.d.ts +94 -0
- package/dist/schemas/verify.d.ts.map +1 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/stdout-fence.d.ts +11 -0
- package/dist/stdout-fence.d.ts.map +1 -0
- package/package.json +66 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1685 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
4
|
+
var protocol = require('@peac/protocol');
|
|
5
|
+
var crypto = require('@peac/crypto');
|
|
6
|
+
var promises = require('fs/promises');
|
|
7
|
+
var schema = require('@peac/schema');
|
|
8
|
+
var nodePath = require('path');
|
|
9
|
+
var crypto$1 = require('crypto');
|
|
10
|
+
var zod = require('zod');
|
|
11
|
+
|
|
12
|
+
function _interopNamespace(e) {
|
|
13
|
+
if (e && e.__esModule) return e;
|
|
14
|
+
var n = Object.create(null);
|
|
15
|
+
if (e) {
|
|
16
|
+
Object.keys(e).forEach(function (k) {
|
|
17
|
+
if (k !== 'default') {
|
|
18
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
19
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return e[k]; }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
n.default = e;
|
|
27
|
+
return Object.freeze(n);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var nodePath__namespace = /*#__PURE__*/_interopNamespace(nodePath);
|
|
31
|
+
|
|
32
|
+
// src/server.ts
|
|
33
|
+
|
|
34
|
+
// src/infra/constants.ts
|
|
35
|
+
var SERVER_NAME = "peac-mcp-server";
|
|
36
|
+
var SERVER_VERSION = "0.10.12";
|
|
37
|
+
var MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
38
|
+
var DEFAULT_MAX_JWS_BYTES = 16384;
|
|
39
|
+
var DEFAULT_MAX_RESPONSE_BYTES = 65536;
|
|
40
|
+
var DEFAULT_TOOL_TIMEOUT_MS = 3e4;
|
|
41
|
+
var DEFAULT_MAX_CLAIMS_BYTES = 262144;
|
|
42
|
+
var DEFAULT_MAX_BUNDLE_RECEIPTS = 256;
|
|
43
|
+
var DEFAULT_MAX_BUNDLE_BYTES = 16777216;
|
|
44
|
+
var DEFAULT_MAX_TTL_SECONDS = 86400;
|
|
45
|
+
|
|
46
|
+
// src/infra/errors.ts
|
|
47
|
+
var McpServerError = class extends Error {
|
|
48
|
+
code;
|
|
49
|
+
constructor(code, message) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = "McpServerError";
|
|
52
|
+
this.code = code;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var KeyLoadError = class extends McpServerError {
|
|
56
|
+
constructor(message) {
|
|
57
|
+
super("E_MCP_KEY_LOAD", message);
|
|
58
|
+
this.name = "KeyLoadError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var PolicyLoadError = class extends McpServerError {
|
|
62
|
+
constructor(message) {
|
|
63
|
+
super("E_MCP_POLICY_LOAD", message);
|
|
64
|
+
this.name = "PolicyLoadError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var JwksLoadError = class extends McpServerError {
|
|
68
|
+
constructor(message) {
|
|
69
|
+
super("E_MCP_JWKS_LOAD", message);
|
|
70
|
+
this.name = "JwksLoadError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var IssueToolError = class extends McpServerError {
|
|
74
|
+
constructor(message) {
|
|
75
|
+
super("E_MCP_ISSUE_FAILED", message);
|
|
76
|
+
this.name = "IssueToolError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var BundleToolError = class extends McpServerError {
|
|
80
|
+
constructor(message) {
|
|
81
|
+
super("E_MCP_BUNDLE_FAILED", message);
|
|
82
|
+
this.name = "BundleToolError";
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var PathTraversalError = class extends McpServerError {
|
|
86
|
+
constructor(message) {
|
|
87
|
+
super("E_MCP_PATH_TRAVERSAL", message);
|
|
88
|
+
this.name = "PathTraversalError";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
function sanitizeOutput(text, sensitivePatterns) {
|
|
92
|
+
let result = text;
|
|
93
|
+
for (const pattern of sensitivePatterns) {
|
|
94
|
+
result = result.replace(pattern, "[REDACTED]");
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/infra/jwks-loader.ts
|
|
100
|
+
async function loadJwksFile(filePath) {
|
|
101
|
+
let raw;
|
|
102
|
+
try {
|
|
103
|
+
raw = await promises.readFile(filePath, "utf-8");
|
|
104
|
+
} catch (err) {
|
|
105
|
+
throw new JwksLoadError(
|
|
106
|
+
`Failed to read JWKS file: ${filePath} -- ${err instanceof Error ? err.message : String(err)}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
let parsed;
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(raw);
|
|
112
|
+
} catch {
|
|
113
|
+
throw new JwksLoadError(`JWKS file is not valid JSON: ${filePath}`);
|
|
114
|
+
}
|
|
115
|
+
if (typeof parsed !== "object" || parsed === null || !("keys" in parsed)) {
|
|
116
|
+
throw new JwksLoadError('JWKS must contain a "keys" array');
|
|
117
|
+
}
|
|
118
|
+
const jwks = parsed;
|
|
119
|
+
if (!Array.isArray(jwks.keys)) {
|
|
120
|
+
throw new JwksLoadError('JWKS "keys" must be an array');
|
|
121
|
+
}
|
|
122
|
+
const entries = [];
|
|
123
|
+
for (const key of jwks.keys) {
|
|
124
|
+
if (typeof key !== "object" || key === null) continue;
|
|
125
|
+
const k = key;
|
|
126
|
+
if (k.kty !== "OKP" || k.crv !== "Ed25519") continue;
|
|
127
|
+
if (typeof k.x !== "string") continue;
|
|
128
|
+
if (k.alg !== void 0 && k.alg !== "EdDSA") continue;
|
|
129
|
+
const kid = typeof k.kid === "string" ? k.kid : `key-${entries.length}`;
|
|
130
|
+
try {
|
|
131
|
+
const publicKey = crypto.base64urlDecode(k.x);
|
|
132
|
+
if (publicKey.length === 32) {
|
|
133
|
+
entries.push({ kid, publicKey });
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return entries;
|
|
139
|
+
}
|
|
140
|
+
function resolveKeyByKid(keys, kid) {
|
|
141
|
+
const entry = keys.find((k) => k.kid === kid);
|
|
142
|
+
return entry?.publicKey;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/handlers/guards.ts
|
|
146
|
+
function checkJwsSize(jws, policy) {
|
|
147
|
+
const byteLength = new TextEncoder().encode(jws).length;
|
|
148
|
+
if (byteLength > policy.limits.max_jws_bytes) {
|
|
149
|
+
return {
|
|
150
|
+
text: `Input rejected: JWS is ${byteLength} bytes, exceeding limit of ${policy.limits.max_jws_bytes} bytes`,
|
|
151
|
+
structured: {
|
|
152
|
+
ok: false,
|
|
153
|
+
code: "E_MCP_INPUT_TOO_LARGE",
|
|
154
|
+
message: `JWS input is ${byteLength} bytes, limit is ${policy.limits.max_jws_bytes}`
|
|
155
|
+
},
|
|
156
|
+
isError: true
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
function checkToolEnabled(toolName, policy) {
|
|
162
|
+
const toolConfig = policy.tools[toolName];
|
|
163
|
+
if (toolConfig && toolConfig.enabled === false) {
|
|
164
|
+
return {
|
|
165
|
+
text: `Tool "${toolName}" is disabled by server policy`,
|
|
166
|
+
structured: {
|
|
167
|
+
ok: false,
|
|
168
|
+
code: "E_MCP_TOOL_DISABLED",
|
|
169
|
+
message: `Tool "${toolName}" is disabled by server policy`
|
|
170
|
+
},
|
|
171
|
+
isError: true
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
176
|
+
function sumStringBytes(value, seen) {
|
|
177
|
+
if (typeof value === "string") {
|
|
178
|
+
return new TextEncoder().encode(value).length;
|
|
179
|
+
}
|
|
180
|
+
if (typeof value !== "object" || value === null) return 0;
|
|
181
|
+
const visited = seen ?? /* @__PURE__ */ new WeakSet();
|
|
182
|
+
if (visited.has(value)) return 0;
|
|
183
|
+
visited.add(value);
|
|
184
|
+
if (Array.isArray(value)) {
|
|
185
|
+
let total2 = 0;
|
|
186
|
+
for (const item of value) {
|
|
187
|
+
total2 += sumStringBytes(item, visited);
|
|
188
|
+
}
|
|
189
|
+
return total2;
|
|
190
|
+
}
|
|
191
|
+
let total = 0;
|
|
192
|
+
for (const v of Object.values(value)) {
|
|
193
|
+
total += sumStringBytes(v, visited);
|
|
194
|
+
}
|
|
195
|
+
return total;
|
|
196
|
+
}
|
|
197
|
+
function estimateSerializedBytes(value, limit, seen) {
|
|
198
|
+
if (value === null) return 4;
|
|
199
|
+
if (value === void 0) return 0;
|
|
200
|
+
if (typeof value === "boolean") return value ? 4 : 5;
|
|
201
|
+
if (typeof value === "number") return String(value).length;
|
|
202
|
+
if (typeof value === "string") return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
203
|
+
if (typeof value !== "object") return 0;
|
|
204
|
+
const visited = seen ?? /* @__PURE__ */ new WeakSet();
|
|
205
|
+
if (visited.has(value)) return 0;
|
|
206
|
+
visited.add(value);
|
|
207
|
+
let total = 2;
|
|
208
|
+
if (Array.isArray(value)) {
|
|
209
|
+
for (let i = 0; i < value.length; i++) {
|
|
210
|
+
if (i > 0) total += 1;
|
|
211
|
+
total += estimateSerializedBytes(value[i], limit, visited);
|
|
212
|
+
if (total > limit) return Infinity;
|
|
213
|
+
}
|
|
214
|
+
return total;
|
|
215
|
+
}
|
|
216
|
+
const entries = Object.entries(value);
|
|
217
|
+
for (let i = 0; i < entries.length; i++) {
|
|
218
|
+
if (i > 0) total += 1;
|
|
219
|
+
const [k, v] = entries[i];
|
|
220
|
+
total += Buffer.byteLength(JSON.stringify(k), "utf8") + 1;
|
|
221
|
+
total += estimateSerializedBytes(v, limit, visited);
|
|
222
|
+
if (total > limit) return Infinity;
|
|
223
|
+
}
|
|
224
|
+
return total;
|
|
225
|
+
}
|
|
226
|
+
function checkInputSizes(input, policy) {
|
|
227
|
+
const limit = policy.limits.max_jws_bytes * 2;
|
|
228
|
+
const totalBytes = sumStringBytes(input);
|
|
229
|
+
if (totalBytes > limit) {
|
|
230
|
+
return {
|
|
231
|
+
text: `Input rejected: total input is ${totalBytes} bytes, exceeding limit of ${limit} bytes`,
|
|
232
|
+
structured: {
|
|
233
|
+
ok: false,
|
|
234
|
+
code: "E_MCP_INPUT_TOO_LARGE",
|
|
235
|
+
message: `Total input is ${totalBytes} bytes, limit is ${limit}`
|
|
236
|
+
},
|
|
237
|
+
isError: true
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const estimatedBytes = estimateSerializedBytes(input, limit);
|
|
241
|
+
if (estimatedBytes > limit) {
|
|
242
|
+
return {
|
|
243
|
+
text: `Input rejected: estimated serialized input exceeds limit of ${limit} bytes`,
|
|
244
|
+
structured: {
|
|
245
|
+
ok: false,
|
|
246
|
+
code: "E_MCP_INPUT_TOO_LARGE",
|
|
247
|
+
message: `Estimated serialized input exceeds limit of ${limit} bytes`
|
|
248
|
+
},
|
|
249
|
+
isError: true
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return void 0;
|
|
253
|
+
}
|
|
254
|
+
function checkObjectDepth(obj, maxDepth = 10, currentDepth = 0) {
|
|
255
|
+
if (currentDepth > maxDepth) return false;
|
|
256
|
+
if (typeof obj !== "object" || obj === null) return true;
|
|
257
|
+
if (Array.isArray(obj)) {
|
|
258
|
+
return obj.every((item) => checkObjectDepth(item, maxDepth, currentDepth + 1));
|
|
259
|
+
}
|
|
260
|
+
return Object.values(obj).every(
|
|
261
|
+
(value) => checkObjectDepth(value, maxDepth, currentDepth + 1)
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
var CONSERVATIVE_ID_PLACEHOLDER = "00000000-0000-0000-0000-000000000000";
|
|
265
|
+
function measureEnvelopeBytes(callToolResult, id = CONSERVATIVE_ID_PLACEHOLDER) {
|
|
266
|
+
const envelope = {
|
|
267
|
+
jsonrpc: "2.0",
|
|
268
|
+
id,
|
|
269
|
+
result: {
|
|
270
|
+
content: callToolResult.content,
|
|
271
|
+
structuredContent: callToolResult.structuredContent,
|
|
272
|
+
isError: callToolResult.isError
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
return Buffer.byteLength(JSON.stringify(envelope), "utf8") + 1;
|
|
276
|
+
}
|
|
277
|
+
var TRUNCATION_SUFFIX = "\n\n[TRUNCATED: response exceeded policy limit]";
|
|
278
|
+
var TRUNCATION_SUFFIX_BYTES = new TextEncoder().encode(TRUNCATION_SUFFIX).length;
|
|
279
|
+
function truncateResponse(text, policy) {
|
|
280
|
+
const maxBytes = policy.limits.max_response_bytes;
|
|
281
|
+
const encoded = new TextEncoder().encode(text);
|
|
282
|
+
if (encoded.length <= maxBytes) {
|
|
283
|
+
return { text, truncated: false, originalBytes: encoded.length, returnedBytes: encoded.length };
|
|
284
|
+
}
|
|
285
|
+
const bodyBudget = Math.max(0, maxBytes - TRUNCATION_SUFFIX_BYTES);
|
|
286
|
+
const truncatedText = new TextDecoder().decode(encoded.slice(0, bodyBudget));
|
|
287
|
+
const finalText = truncatedText + TRUNCATION_SUFFIX;
|
|
288
|
+
const finalEncoded = new TextEncoder().encode(finalText);
|
|
289
|
+
return {
|
|
290
|
+
text: finalText,
|
|
291
|
+
truncated: true,
|
|
292
|
+
originalBytes: encoded.length,
|
|
293
|
+
returnedBytes: finalEncoded.length
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/handlers/verify.ts
|
|
298
|
+
function resolvePublicKey(params) {
|
|
299
|
+
const { input, context } = params;
|
|
300
|
+
if (input.public_key_base64url) {
|
|
301
|
+
return {
|
|
302
|
+
publicKey: crypto.base64urlDecode(input.public_key_base64url),
|
|
303
|
+
keySource: "inline"
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
if (input.jwks) {
|
|
307
|
+
const jwks = JSON.parse(input.jwks);
|
|
308
|
+
if (!jwks.keys?.length) {
|
|
309
|
+
throw new Error("Provided JWKS contains no keys");
|
|
310
|
+
}
|
|
311
|
+
const { header } = crypto.decode(input.jws);
|
|
312
|
+
const kid = header.kid;
|
|
313
|
+
const ed25519Keys = jwks.keys.filter(
|
|
314
|
+
(k) => k.kty === "OKP" && k.crv === "Ed25519" && typeof k.x === "string" && (k.alg === void 0 || k.alg === "EdDSA")
|
|
315
|
+
);
|
|
316
|
+
if (ed25519Keys.length === 0) {
|
|
317
|
+
throw new Error("Provided JWKS contains no Ed25519 keys");
|
|
318
|
+
}
|
|
319
|
+
if (kid) {
|
|
320
|
+
const matched = ed25519Keys.find((k) => k.kid === kid);
|
|
321
|
+
if (matched) {
|
|
322
|
+
return {
|
|
323
|
+
publicKey: crypto.base64urlDecode(matched.x),
|
|
324
|
+
keySource: "inline-jwks"
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
throw Object.assign(
|
|
328
|
+
new Error(`No matching Ed25519 key found in provided JWKS for kid="${kid}"`),
|
|
329
|
+
{ errorCode: "E_MCP_JWKS_NO_MATCH" }
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
if (ed25519Keys.length === 1) {
|
|
333
|
+
return {
|
|
334
|
+
publicKey: crypto.base64urlDecode(ed25519Keys[0].x),
|
|
335
|
+
keySource: "inline-jwks"
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
throw Object.assign(
|
|
339
|
+
new Error(
|
|
340
|
+
`JWS has no kid and JWKS contains ${ed25519Keys.length} Ed25519 keys -- provide kid to select`
|
|
341
|
+
),
|
|
342
|
+
{ errorCode: "E_MCP_KID_REQUIRED" }
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
if (context.jwksKeys?.length) {
|
|
346
|
+
const { header } = crypto.decode(input.jws);
|
|
347
|
+
const kid = header.kid;
|
|
348
|
+
if (kid) {
|
|
349
|
+
const key = resolveKeyByKid(context.jwksKeys, kid);
|
|
350
|
+
if (key) {
|
|
351
|
+
return { publicKey: key, keySource: "server-jwks" };
|
|
352
|
+
}
|
|
353
|
+
throw Object.assign(new Error(`No matching key in server JWKS for kid="${kid}"`), {
|
|
354
|
+
errorCode: "E_MCP_JWKS_NO_MATCH"
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (context.jwksKeys.length === 1) {
|
|
358
|
+
return { publicKey: context.jwksKeys[0].publicKey, keySource: "server-jwks" };
|
|
359
|
+
}
|
|
360
|
+
throw Object.assign(
|
|
361
|
+
new Error(
|
|
362
|
+
`JWS has no kid and server JWKS contains ${context.jwksKeys.length} keys -- provide kid to select`
|
|
363
|
+
),
|
|
364
|
+
{ errorCode: "E_MCP_KID_REQUIRED" }
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
throw Object.assign(
|
|
368
|
+
new Error(
|
|
369
|
+
"No public key available: provide public_key_base64url, jwks, or configure --jwks-file"
|
|
370
|
+
),
|
|
371
|
+
{ errorCode: "E_MCP_KEY_RESOLUTION" }
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
async function handleVerify(params) {
|
|
375
|
+
const { input, policy } = params;
|
|
376
|
+
const disabledResult = checkToolEnabled("peac_verify", policy);
|
|
377
|
+
if (disabledResult) return disabledResult;
|
|
378
|
+
const sizeResult = checkJwsSize(input.jws, policy);
|
|
379
|
+
if (sizeResult) return sizeResult;
|
|
380
|
+
let publicKey;
|
|
381
|
+
let keySource;
|
|
382
|
+
try {
|
|
383
|
+
const resolved = resolvePublicKey(params);
|
|
384
|
+
publicKey = resolved.publicKey;
|
|
385
|
+
keySource = resolved.keySource;
|
|
386
|
+
} catch (err) {
|
|
387
|
+
const code = err.errorCode ?? "E_MCP_KEY_RESOLUTION";
|
|
388
|
+
return {
|
|
389
|
+
text: `Verification failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
390
|
+
structured: {
|
|
391
|
+
ok: false,
|
|
392
|
+
code,
|
|
393
|
+
message: err instanceof Error ? err.message : String(err),
|
|
394
|
+
checks: []
|
|
395
|
+
},
|
|
396
|
+
isError: true
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const result = await protocol.verifyLocal(input.jws, publicKey, {
|
|
400
|
+
issuer: input.issuer,
|
|
401
|
+
audience: input.audience
|
|
402
|
+
});
|
|
403
|
+
if (result.valid) {
|
|
404
|
+
const checks2 = [
|
|
405
|
+
{ name: "signature", passed: true },
|
|
406
|
+
{ name: "schema", passed: true },
|
|
407
|
+
{ name: "expiry", passed: true }
|
|
408
|
+
];
|
|
409
|
+
if (input.issuer) {
|
|
410
|
+
checks2.push({ name: "issuer_binding", passed: true });
|
|
411
|
+
}
|
|
412
|
+
if (input.audience) {
|
|
413
|
+
checks2.push({ name: "audience_binding", passed: true });
|
|
414
|
+
}
|
|
415
|
+
const claims = result.claims;
|
|
416
|
+
const claimsSummary = {
|
|
417
|
+
iss: claims.iss,
|
|
418
|
+
aud: claims.aud,
|
|
419
|
+
rid: claims.rid,
|
|
420
|
+
variant: result.variant
|
|
421
|
+
};
|
|
422
|
+
if (result.variant === "commerce") {
|
|
423
|
+
claimsSummary.amt = claims.amt;
|
|
424
|
+
claimsSummary.cur = claims.cur;
|
|
425
|
+
}
|
|
426
|
+
const tr = truncateResponse(
|
|
427
|
+
`Verification PASSED (${result.variant} receipt, kid=${result.kid})`,
|
|
428
|
+
policy
|
|
429
|
+
);
|
|
430
|
+
return {
|
|
431
|
+
text: tr.text,
|
|
432
|
+
structured: {
|
|
433
|
+
ok: true,
|
|
434
|
+
variant: result.variant,
|
|
435
|
+
checks: checks2,
|
|
436
|
+
claimsSummary,
|
|
437
|
+
keySource,
|
|
438
|
+
...tr.truncated ? {
|
|
439
|
+
_truncation: {
|
|
440
|
+
truncated: tr.truncated,
|
|
441
|
+
originalBytes: tr.originalBytes,
|
|
442
|
+
returnedBytes: tr.returnedBytes
|
|
443
|
+
}
|
|
444
|
+
} : {}
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
const checks = [];
|
|
449
|
+
if (result.code === "E_INVALID_SIGNATURE") {
|
|
450
|
+
checks.push({ name: "signature", passed: false, message: result.message });
|
|
451
|
+
} else if (result.code === "E_INVALID_FORMAT") {
|
|
452
|
+
checks.push({ name: "signature", passed: true });
|
|
453
|
+
checks.push({ name: "schema", passed: false, message: result.message });
|
|
454
|
+
} else if (result.code === "E_EXPIRED") {
|
|
455
|
+
checks.push({ name: "signature", passed: true });
|
|
456
|
+
checks.push({ name: "schema", passed: true });
|
|
457
|
+
checks.push({ name: "expiry", passed: false, message: result.message });
|
|
458
|
+
} else if (result.code === "E_INVALID_ISSUER") {
|
|
459
|
+
checks.push({ name: "signature", passed: true });
|
|
460
|
+
checks.push({ name: "schema", passed: true });
|
|
461
|
+
checks.push({ name: "issuer_binding", passed: false, message: result.message });
|
|
462
|
+
} else if (result.code === "E_INVALID_AUDIENCE") {
|
|
463
|
+
checks.push({ name: "signature", passed: true });
|
|
464
|
+
checks.push({ name: "schema", passed: true });
|
|
465
|
+
checks.push({ name: "audience_binding", passed: false, message: result.message });
|
|
466
|
+
} else {
|
|
467
|
+
checks.push({ name: result.code, passed: false, message: result.message });
|
|
468
|
+
}
|
|
469
|
+
const trFail = truncateResponse(
|
|
470
|
+
`Verification FAILED: ${result.code} -- ${result.message}`,
|
|
471
|
+
policy
|
|
472
|
+
);
|
|
473
|
+
return {
|
|
474
|
+
text: trFail.text,
|
|
475
|
+
structured: {
|
|
476
|
+
ok: false,
|
|
477
|
+
code: result.code,
|
|
478
|
+
message: result.message,
|
|
479
|
+
checks,
|
|
480
|
+
keySource,
|
|
481
|
+
...trFail.truncated ? {
|
|
482
|
+
_truncation: {
|
|
483
|
+
truncated: trFail.truncated,
|
|
484
|
+
originalBytes: trFail.originalBytes,
|
|
485
|
+
returnedBytes: trFail.returnedBytes
|
|
486
|
+
}
|
|
487
|
+
} : {}
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
async function handleInspect(params) {
|
|
492
|
+
const { input, policy } = params;
|
|
493
|
+
const disabledResult = checkToolEnabled("peac_inspect", policy);
|
|
494
|
+
if (disabledResult) return disabledResult;
|
|
495
|
+
const sizeResult = checkJwsSize(input.jws, policy);
|
|
496
|
+
if (sizeResult) return sizeResult;
|
|
497
|
+
let header;
|
|
498
|
+
let payload;
|
|
499
|
+
try {
|
|
500
|
+
const decoded = crypto.decode(input.jws);
|
|
501
|
+
header = decoded.header;
|
|
502
|
+
payload = decoded.payload;
|
|
503
|
+
} catch (err) {
|
|
504
|
+
return {
|
|
505
|
+
text: `Inspect failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
506
|
+
structured: {
|
|
507
|
+
error: err instanceof Error ? err.message : String(err),
|
|
508
|
+
code: "E_MCP_INVALID_INPUT"
|
|
509
|
+
},
|
|
510
|
+
isError: true
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
let variant = "unknown";
|
|
514
|
+
try {
|
|
515
|
+
const pr = schema.parseReceiptClaims(payload);
|
|
516
|
+
if (pr.ok) {
|
|
517
|
+
variant = pr.variant;
|
|
518
|
+
}
|
|
519
|
+
} catch {
|
|
520
|
+
}
|
|
521
|
+
const payloadMeta = {
|
|
522
|
+
variant,
|
|
523
|
+
issuer: payload.iss,
|
|
524
|
+
audience: payload.aud
|
|
525
|
+
};
|
|
526
|
+
if (typeof payload.iat === "number") {
|
|
527
|
+
payloadMeta.issuedAt = new Date(payload.iat * 1e3).toISOString();
|
|
528
|
+
}
|
|
529
|
+
if (typeof payload.exp === "number") {
|
|
530
|
+
payloadMeta.expiresAt = new Date(payload.exp * 1e3).toISOString();
|
|
531
|
+
}
|
|
532
|
+
if (typeof payload.rid === "string") {
|
|
533
|
+
payloadMeta.receiptId = payload.rid;
|
|
534
|
+
}
|
|
535
|
+
let redacted = false;
|
|
536
|
+
let fullPayload;
|
|
537
|
+
const effectiveFullClaims = input.full_claims && policy.redaction.inspect_full_claims;
|
|
538
|
+
if (effectiveFullClaims) {
|
|
539
|
+
fullPayload = { ...payload };
|
|
540
|
+
if (policy.redaction.strip_evidence && "evidence" in fullPayload) {
|
|
541
|
+
fullPayload.evidence = "[REDACTED by policy]";
|
|
542
|
+
redacted = true;
|
|
543
|
+
}
|
|
544
|
+
if (policy.redaction.strip_payment && "payment" in fullPayload) {
|
|
545
|
+
fullPayload.payment = "[REDACTED by policy]";
|
|
546
|
+
redacted = true;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const lines = [
|
|
550
|
+
"WARNING: Signature NOT verified (use peac_verify to verify)",
|
|
551
|
+
"",
|
|
552
|
+
`Variant: ${variant}`,
|
|
553
|
+
`Issuer: ${payload.iss ?? "N/A"}`,
|
|
554
|
+
`Audience: ${payload.aud ?? "N/A"}`
|
|
555
|
+
];
|
|
556
|
+
if (payloadMeta.issuedAt) lines.push(`Issued At: ${payloadMeta.issuedAt}`);
|
|
557
|
+
if (payloadMeta.expiresAt) lines.push(`Expires At: ${payloadMeta.expiresAt}`);
|
|
558
|
+
if (payloadMeta.receiptId) lines.push(`Receipt ID: ${payloadMeta.receiptId}`);
|
|
559
|
+
if (redacted) lines.push("", "Note: Some fields redacted by policy");
|
|
560
|
+
const tr = truncateResponse(lines.join("\n"), policy);
|
|
561
|
+
return {
|
|
562
|
+
text: tr.text,
|
|
563
|
+
structured: {
|
|
564
|
+
header,
|
|
565
|
+
payloadMeta,
|
|
566
|
+
...fullPayload !== void 0 ? { fullPayload } : {},
|
|
567
|
+
redacted,
|
|
568
|
+
verified: false,
|
|
569
|
+
...tr.truncated ? {
|
|
570
|
+
_truncation: {
|
|
571
|
+
truncated: tr.truncated,
|
|
572
|
+
originalBytes: tr.originalBytes,
|
|
573
|
+
returnedBytes: tr.returnedBytes
|
|
574
|
+
}
|
|
575
|
+
} : {}
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
async function handleDecode(params) {
|
|
580
|
+
const { input, policy } = params;
|
|
581
|
+
const disabledResult = checkToolEnabled("peac_decode", policy);
|
|
582
|
+
if (disabledResult) return disabledResult;
|
|
583
|
+
const sizeResult = checkJwsSize(input.jws, policy);
|
|
584
|
+
if (sizeResult) return sizeResult;
|
|
585
|
+
try {
|
|
586
|
+
const decoded = crypto.decode(input.jws);
|
|
587
|
+
const header = decoded.header;
|
|
588
|
+
const payload = decoded.payload;
|
|
589
|
+
const tr = truncateResponse(
|
|
590
|
+
[
|
|
591
|
+
"WARNING: Signature NOT verified",
|
|
592
|
+
"",
|
|
593
|
+
`Header: ${JSON.stringify(header, null, 2)}`,
|
|
594
|
+
"",
|
|
595
|
+
`Payload: ${JSON.stringify(payload, null, 2)}`
|
|
596
|
+
].join("\n"),
|
|
597
|
+
policy
|
|
598
|
+
);
|
|
599
|
+
return {
|
|
600
|
+
text: tr.text,
|
|
601
|
+
structured: {
|
|
602
|
+
header,
|
|
603
|
+
payload,
|
|
604
|
+
verified: false,
|
|
605
|
+
...tr.truncated ? {
|
|
606
|
+
_truncation: {
|
|
607
|
+
truncated: tr.truncated,
|
|
608
|
+
originalBytes: tr.originalBytes,
|
|
609
|
+
returnedBytes: tr.returnedBytes
|
|
610
|
+
}
|
|
611
|
+
} : {}
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
} catch (err) {
|
|
615
|
+
return {
|
|
616
|
+
text: `Decode failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
617
|
+
structured: {
|
|
618
|
+
error: err instanceof Error ? err.message : String(err),
|
|
619
|
+
code: "E_MCP_INVALID_INPUT"
|
|
620
|
+
},
|
|
621
|
+
isError: true
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
function buildKeyPatterns(privateKey, publicKey) {
|
|
626
|
+
const privB64 = crypto.base64urlEncode(privateKey);
|
|
627
|
+
const pubB64 = crypto.base64urlEncode(publicKey);
|
|
628
|
+
const escPriv = privB64.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
629
|
+
const escPub = pubB64.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
630
|
+
return [new RegExp(escPriv, "g"), new RegExp(escPub, "g")];
|
|
631
|
+
}
|
|
632
|
+
async function handleIssue(params) {
|
|
633
|
+
const { input, policy, context } = params;
|
|
634
|
+
const disabledResult = checkToolEnabled("peac_issue", policy);
|
|
635
|
+
if (disabledResult) return disabledResult;
|
|
636
|
+
if (!context.issuerKey || !context.issuerId) {
|
|
637
|
+
return {
|
|
638
|
+
text: "Issue failed: server not configured with issuer key and ID (--issuer-key + --issuer-id)",
|
|
639
|
+
structured: {
|
|
640
|
+
ok: false,
|
|
641
|
+
code: "E_MCP_KEY_REQUIRED",
|
|
642
|
+
message: "Server requires --issuer-key and --issuer-id for peac_issue"
|
|
643
|
+
},
|
|
644
|
+
isError: true
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
const inputJson = JSON.stringify(input);
|
|
648
|
+
const inputBytes = new TextEncoder().encode(inputJson).length;
|
|
649
|
+
if (inputBytes > policy.limits.max_claims_bytes) {
|
|
650
|
+
return {
|
|
651
|
+
text: `Input rejected: claims are ${inputBytes} bytes, exceeding limit of ${policy.limits.max_claims_bytes} bytes`,
|
|
652
|
+
structured: {
|
|
653
|
+
ok: false,
|
|
654
|
+
code: "E_MCP_INPUT_TOO_LARGE",
|
|
655
|
+
message: `Claims input is ${inputBytes} bytes, limit is ${policy.limits.max_claims_bytes}`
|
|
656
|
+
},
|
|
657
|
+
isError: true
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (input.ttl_seconds !== void 0 && input.ttl_seconds > policy.limits.max_ttl_seconds) {
|
|
661
|
+
return {
|
|
662
|
+
text: `Input rejected: ttl_seconds ${input.ttl_seconds} exceeds max_ttl_seconds ${policy.limits.max_ttl_seconds}`,
|
|
663
|
+
structured: {
|
|
664
|
+
ok: false,
|
|
665
|
+
code: "E_MCP_INVALID_INPUT",
|
|
666
|
+
message: `ttl_seconds ${input.ttl_seconds} exceeds policy max of ${policy.limits.max_ttl_seconds}`
|
|
667
|
+
},
|
|
668
|
+
isError: true
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const keyPatterns = buildKeyPatterns(context.issuerKey.privateKey, context.issuerKey.publicKey);
|
|
672
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
673
|
+
const exp = input.ttl_seconds !== void 0 ? now + input.ttl_seconds : void 0;
|
|
674
|
+
try {
|
|
675
|
+
const result = await protocol.issue({
|
|
676
|
+
iss: context.issuerId,
|
|
677
|
+
aud: input.aud,
|
|
678
|
+
amt: input.amt,
|
|
679
|
+
cur: input.cur,
|
|
680
|
+
rail: input.rail,
|
|
681
|
+
reference: input.reference,
|
|
682
|
+
asset: input.asset,
|
|
683
|
+
env: input.env,
|
|
684
|
+
network: input.network,
|
|
685
|
+
evidence: input.evidence,
|
|
686
|
+
subject: input.subject,
|
|
687
|
+
exp,
|
|
688
|
+
privateKey: context.issuerKey.privateKey,
|
|
689
|
+
kid: context.issuerKey.kid
|
|
690
|
+
});
|
|
691
|
+
const { payload } = crypto.decode(result.jws);
|
|
692
|
+
const claimsSummary = {
|
|
693
|
+
iss: payload.iss,
|
|
694
|
+
aud: payload.aud,
|
|
695
|
+
iat: payload.iat,
|
|
696
|
+
...payload.exp !== void 0 ? { exp: payload.exp } : {},
|
|
697
|
+
rid: payload.rid,
|
|
698
|
+
amt: payload.amt,
|
|
699
|
+
cur: payload.cur
|
|
700
|
+
};
|
|
701
|
+
const tr = truncateResponse(
|
|
702
|
+
`Receipt issued: rid=${claimsSummary.rid}, ${claimsSummary.amt} ${claimsSummary.cur}`,
|
|
703
|
+
policy
|
|
704
|
+
);
|
|
705
|
+
const safeText = sanitizeOutput(tr.text, keyPatterns);
|
|
706
|
+
return {
|
|
707
|
+
text: safeText,
|
|
708
|
+
structured: {
|
|
709
|
+
ok: true,
|
|
710
|
+
jws: result.jws,
|
|
711
|
+
claimsSummary
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
} catch (err) {
|
|
715
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
716
|
+
const safeMessage = sanitizeOutput(rawMessage, keyPatterns);
|
|
717
|
+
if (err instanceof protocol.IssueError) {
|
|
718
|
+
return {
|
|
719
|
+
text: `Issue failed: ${safeMessage}`,
|
|
720
|
+
structured: {
|
|
721
|
+
ok: false,
|
|
722
|
+
code: "E_MCP_ISSUE_FAILED",
|
|
723
|
+
message: safeMessage
|
|
724
|
+
},
|
|
725
|
+
isError: true
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
if (err instanceof McpServerError) {
|
|
729
|
+
return {
|
|
730
|
+
text: `Issue failed: ${safeMessage}`,
|
|
731
|
+
structured: {
|
|
732
|
+
ok: false,
|
|
733
|
+
code: err.code,
|
|
734
|
+
message: safeMessage
|
|
735
|
+
},
|
|
736
|
+
isError: true
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
text: `Issue failed: ${safeMessage}`,
|
|
741
|
+
structured: {
|
|
742
|
+
ok: false,
|
|
743
|
+
code: "E_MCP_ISSUE_FAILED",
|
|
744
|
+
message: safeMessage
|
|
745
|
+
},
|
|
746
|
+
isError: true
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
function assertRelativePath(p) {
|
|
751
|
+
if (!p || p.trim().length === 0) {
|
|
752
|
+
throw new PathTraversalError("Path must not be empty");
|
|
753
|
+
}
|
|
754
|
+
if (nodePath__namespace.isAbsolute(p)) {
|
|
755
|
+
throw new PathTraversalError("Absolute paths are not allowed");
|
|
756
|
+
}
|
|
757
|
+
const normalized = nodePath__namespace.normalize(p);
|
|
758
|
+
if (normalized.startsWith("..") || normalized.includes("/..") || normalized.includes("\\..")) {
|
|
759
|
+
throw new PathTraversalError("Path traversal (..) is not allowed");
|
|
760
|
+
}
|
|
761
|
+
if (p.includes("/") || p.includes("\\")) {
|
|
762
|
+
throw new PathTraversalError("Nested directories are not allowed (single segment only)");
|
|
763
|
+
}
|
|
764
|
+
if (p.length > 255) {
|
|
765
|
+
throw new PathTraversalError(`Path segment exceeds 255 characters (got ${p.length})`);
|
|
766
|
+
}
|
|
767
|
+
if (p.startsWith(".")) {
|
|
768
|
+
throw new PathTraversalError("Hidden paths (starting with .) are not allowed");
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function resolveOutputPath(basePath, relativePath) {
|
|
772
|
+
assertRelativePath(relativePath);
|
|
773
|
+
const normalizedBase = nodePath__namespace.resolve(basePath);
|
|
774
|
+
const resolved = nodePath__namespace.resolve(normalizedBase, relativePath);
|
|
775
|
+
const rel = nodePath__namespace.relative(normalizedBase, resolved);
|
|
776
|
+
if (rel.startsWith("..") || rel.startsWith("/") || nodePath__namespace.isAbsolute(rel)) {
|
|
777
|
+
throw new PathTraversalError("Resolved path escapes base directory");
|
|
778
|
+
}
|
|
779
|
+
return resolved;
|
|
780
|
+
}
|
|
781
|
+
async function assertNotSymlink(componentPath) {
|
|
782
|
+
try {
|
|
783
|
+
const stats = await promises.lstat(componentPath);
|
|
784
|
+
if (stats.isSymbolicLink()) {
|
|
785
|
+
throw new PathTraversalError(`Symlink detected at ${componentPath}`);
|
|
786
|
+
}
|
|
787
|
+
} catch (err) {
|
|
788
|
+
if (err instanceof PathTraversalError) throw err;
|
|
789
|
+
if (err.code === "ENOENT") return;
|
|
790
|
+
throw err;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
async function safeMkdir(basePath, relativePath) {
|
|
794
|
+
const resolved = resolveOutputPath(basePath, relativePath);
|
|
795
|
+
await assertNotSymlink(basePath);
|
|
796
|
+
await assertNotSymlink(resolved);
|
|
797
|
+
await promises.mkdir(resolved, { recursive: false, mode: 448 });
|
|
798
|
+
return resolved;
|
|
799
|
+
}
|
|
800
|
+
async function atomicWriteDir(tempDir, finalPath) {
|
|
801
|
+
try {
|
|
802
|
+
await promises.rename(tempDir, finalPath);
|
|
803
|
+
} catch (err) {
|
|
804
|
+
try {
|
|
805
|
+
await promises.rm(tempDir, { recursive: true, force: true });
|
|
806
|
+
} catch {
|
|
807
|
+
}
|
|
808
|
+
throw err;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
async function createTempDir(basePath) {
|
|
812
|
+
await assertNotSymlink(basePath);
|
|
813
|
+
const prefix = nodePath__namespace.join(basePath, `.tmp-${crypto$1.randomUUID().slice(0, 8)}-`);
|
|
814
|
+
return promises.mkdtemp(prefix);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/handlers/bundle.ts
|
|
818
|
+
function stableJsonStringify(value) {
|
|
819
|
+
return JSON.stringify(
|
|
820
|
+
value,
|
|
821
|
+
(_key, val) => {
|
|
822
|
+
if (val !== null && typeof val === "object" && !Array.isArray(val)) {
|
|
823
|
+
const sorted = {};
|
|
824
|
+
for (const k of Object.keys(val).sort()) {
|
|
825
|
+
sorted[k] = val[k];
|
|
826
|
+
}
|
|
827
|
+
return sorted;
|
|
828
|
+
}
|
|
829
|
+
return val;
|
|
830
|
+
},
|
|
831
|
+
2
|
|
832
|
+
) + "\n";
|
|
833
|
+
}
|
|
834
|
+
function buildKeyPatterns2(privateKey, publicKey) {
|
|
835
|
+
const privB64 = crypto.base64urlEncode(privateKey);
|
|
836
|
+
const pubB64 = crypto.base64urlEncode(publicKey);
|
|
837
|
+
const escPriv = privB64.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
838
|
+
const escPub = pubB64.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
839
|
+
return [new RegExp(escPriv, "g"), new RegExp(escPub, "g")];
|
|
840
|
+
}
|
|
841
|
+
function generateBundleName() {
|
|
842
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
843
|
+
const rand = crypto$1.randomUUID().slice(0, 8);
|
|
844
|
+
return `bundle-${ts}-${rand}`;
|
|
845
|
+
}
|
|
846
|
+
async function handleCreateBundle(params) {
|
|
847
|
+
const { input, policy, context } = params;
|
|
848
|
+
const disabledResult = checkToolEnabled("peac_create_bundle", policy);
|
|
849
|
+
if (disabledResult) return disabledResult;
|
|
850
|
+
if (!context.bundleDir) {
|
|
851
|
+
return {
|
|
852
|
+
text: "Bundle creation failed: server not configured with --bundle-dir",
|
|
853
|
+
structured: {
|
|
854
|
+
ok: false,
|
|
855
|
+
code: "E_MCP_BUNDLE_DIR_REQUIRED",
|
|
856
|
+
message: "Server requires --bundle-dir for peac_create_bundle"
|
|
857
|
+
},
|
|
858
|
+
isError: true
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
if (!context.issuerKey || !context.issuerId) {
|
|
862
|
+
return {
|
|
863
|
+
text: "Bundle creation failed: server not configured with issuer key and ID",
|
|
864
|
+
structured: {
|
|
865
|
+
ok: false,
|
|
866
|
+
code: "E_MCP_KEY_REQUIRED",
|
|
867
|
+
message: "Server requires --issuer-key and --issuer-id for peac_create_bundle"
|
|
868
|
+
},
|
|
869
|
+
isError: true
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
if (input.receipts.length > policy.limits.max_bundle_receipts) {
|
|
873
|
+
return {
|
|
874
|
+
text: `Input rejected: ${input.receipts.length} receipts exceeds limit of ${policy.limits.max_bundle_receipts}`,
|
|
875
|
+
structured: {
|
|
876
|
+
ok: false,
|
|
877
|
+
code: "E_MCP_INPUT_TOO_LARGE",
|
|
878
|
+
message: `${input.receipts.length} receipts exceeds limit of ${policy.limits.max_bundle_receipts}`
|
|
879
|
+
},
|
|
880
|
+
isError: true
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
let totalReceiptBytes = 0;
|
|
884
|
+
for (const r of input.receipts) {
|
|
885
|
+
totalReceiptBytes += new TextEncoder().encode(r).length;
|
|
886
|
+
}
|
|
887
|
+
if (totalReceiptBytes > policy.limits.max_bundle_bytes) {
|
|
888
|
+
return {
|
|
889
|
+
text: `Input rejected: total receipt bytes ${totalReceiptBytes} exceeds limit of ${policy.limits.max_bundle_bytes}`,
|
|
890
|
+
structured: {
|
|
891
|
+
ok: false,
|
|
892
|
+
code: "E_MCP_INPUT_TOO_LARGE",
|
|
893
|
+
message: `Total receipt bytes ${totalReceiptBytes} exceeds limit of ${policy.limits.max_bundle_bytes}`
|
|
894
|
+
},
|
|
895
|
+
isError: true
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
const outputName = input.output_path ?? generateBundleName();
|
|
899
|
+
try {
|
|
900
|
+
assertRelativePath(outputName);
|
|
901
|
+
} catch (err) {
|
|
902
|
+
return {
|
|
903
|
+
text: `Bundle creation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
904
|
+
structured: {
|
|
905
|
+
ok: false,
|
|
906
|
+
code: "E_MCP_PATH_TRAVERSAL",
|
|
907
|
+
message: err instanceof Error ? err.message : String(err)
|
|
908
|
+
},
|
|
909
|
+
isError: true
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
const keyPatterns = buildKeyPatterns2(context.issuerKey.privateKey, context.issuerKey.publicKey);
|
|
913
|
+
let tempDir;
|
|
914
|
+
try {
|
|
915
|
+
tempDir = await createTempDir(context.bundleDir);
|
|
916
|
+
const receiptsDir = nodePath.join(tempDir, "receipts");
|
|
917
|
+
await promises.mkdir(receiptsDir, { mode: 448 });
|
|
918
|
+
const entries = [];
|
|
919
|
+
let totalBytes = 0;
|
|
920
|
+
const seenHashes = /* @__PURE__ */ new Set();
|
|
921
|
+
const receiptHashes = [];
|
|
922
|
+
for (let i = 0; i < input.receipts.length; i++) {
|
|
923
|
+
if (params.signal?.aborted) {
|
|
924
|
+
throw new McpServerError("E_MCP_CANCELLED", "Request cancelled during bundle creation");
|
|
925
|
+
}
|
|
926
|
+
const jws = input.receipts[i];
|
|
927
|
+
const data = new TextEncoder().encode(jws);
|
|
928
|
+
const hash = await crypto.sha256Hex(data);
|
|
929
|
+
if (seenHashes.has(hash)) continue;
|
|
930
|
+
seenHashes.add(hash);
|
|
931
|
+
receiptHashes.push(hash);
|
|
932
|
+
const fileName = `${hash}.jws`;
|
|
933
|
+
const filePath = nodePath.join(receiptsDir, fileName);
|
|
934
|
+
await promises.writeFile(filePath, data, { mode: 384 });
|
|
935
|
+
entries.push({
|
|
936
|
+
index: i,
|
|
937
|
+
file: `receipts/${fileName}`,
|
|
938
|
+
sha256: hash,
|
|
939
|
+
length: data.length
|
|
940
|
+
});
|
|
941
|
+
totalBytes += data.length;
|
|
942
|
+
}
|
|
943
|
+
entries.sort((a, b) => a.sha256.localeCompare(b.sha256));
|
|
944
|
+
const sortedHashes = [...receiptHashes].sort();
|
|
945
|
+
const bundleIdInput = `${context.policyHash}:${sortedHashes.join(":")}`;
|
|
946
|
+
const bundleId = await crypto.sha256Hex(new TextEncoder().encode(bundleIdInput));
|
|
947
|
+
const manifest = {
|
|
948
|
+
bundle_id: bundleId,
|
|
949
|
+
tool_version: SERVER_VERSION,
|
|
950
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
951
|
+
policy_hash: context.policyHash,
|
|
952
|
+
receipt_count: entries.length,
|
|
953
|
+
receipts: entries,
|
|
954
|
+
...input.metadata ? { metadata: input.metadata } : {}
|
|
955
|
+
};
|
|
956
|
+
const manifestJson = stableJsonStringify(manifest);
|
|
957
|
+
await promises.writeFile(nodePath.join(tempDir, "manifest.json"), manifestJson, { mode: 384 });
|
|
958
|
+
totalBytes += new TextEncoder().encode(manifestJson).length;
|
|
959
|
+
const manifestBytes = new TextEncoder().encode(manifestJson);
|
|
960
|
+
const manifestSha256 = await crypto.sha256Hex(manifestBytes);
|
|
961
|
+
const canonicalManifest = JSON.parse(manifestJson);
|
|
962
|
+
const manifestJws = await crypto.sign(
|
|
963
|
+
canonicalManifest,
|
|
964
|
+
context.issuerKey.privateKey,
|
|
965
|
+
context.issuerKey.kid
|
|
966
|
+
);
|
|
967
|
+
await promises.writeFile(nodePath.join(tempDir, "manifest.jws"), manifestJws, { mode: 384 });
|
|
968
|
+
totalBytes += new TextEncoder().encode(manifestJws).length;
|
|
969
|
+
const fileCount = entries.length + 2;
|
|
970
|
+
const finalPath = nodePath.join(context.bundleDir, outputName);
|
|
971
|
+
try {
|
|
972
|
+
await promises.stat(finalPath);
|
|
973
|
+
return {
|
|
974
|
+
text: `Bundle creation failed: output path already exists: ${outputName}`,
|
|
975
|
+
structured: {
|
|
976
|
+
ok: false,
|
|
977
|
+
code: "E_MCP_BUNDLE_FAILED",
|
|
978
|
+
message: `Output path already exists: ${outputName}`
|
|
979
|
+
},
|
|
980
|
+
isError: true
|
|
981
|
+
};
|
|
982
|
+
} catch (err) {
|
|
983
|
+
if (err.code !== "ENOENT") {
|
|
984
|
+
throw err;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
await atomicWriteDir(tempDir, finalPath);
|
|
988
|
+
tempDir = void 0;
|
|
989
|
+
const tr = truncateResponse(
|
|
990
|
+
`Bundle created: ${outputName} (${fileCount} files, ${totalBytes} bytes)`,
|
|
991
|
+
policy
|
|
992
|
+
);
|
|
993
|
+
const safeText = sanitizeOutput(tr.text, keyPatterns);
|
|
994
|
+
return {
|
|
995
|
+
text: safeText,
|
|
996
|
+
structured: {
|
|
997
|
+
ok: true,
|
|
998
|
+
bundleId,
|
|
999
|
+
bundleName: outputName,
|
|
1000
|
+
receiptCount: entries.length,
|
|
1001
|
+
fileCount,
|
|
1002
|
+
totalBytes,
|
|
1003
|
+
createdAt: manifest.created_at,
|
|
1004
|
+
manifestSha256
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
} catch (err) {
|
|
1008
|
+
if (tempDir) {
|
|
1009
|
+
try {
|
|
1010
|
+
await promises.rm(tempDir, { recursive: true, force: true });
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
1015
|
+
const safeMessage = sanitizeOutput(rawMessage, keyPatterns);
|
|
1016
|
+
if (err instanceof McpServerError) {
|
|
1017
|
+
return {
|
|
1018
|
+
text: `Bundle creation failed: ${safeMessage}`,
|
|
1019
|
+
structured: {
|
|
1020
|
+
ok: false,
|
|
1021
|
+
code: err.code,
|
|
1022
|
+
message: safeMessage
|
|
1023
|
+
},
|
|
1024
|
+
isError: true
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
return {
|
|
1028
|
+
text: `Bundle creation failed: ${safeMessage}`,
|
|
1029
|
+
structured: {
|
|
1030
|
+
ok: false,
|
|
1031
|
+
code: "E_MCP_BUNDLE_FAILED",
|
|
1032
|
+
message: safeMessage
|
|
1033
|
+
},
|
|
1034
|
+
isError: true
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
var VerifyInputSchema = zod.z.object({
|
|
1039
|
+
jws: zod.z.string().describe("JWS compact serialization (header.payload.signature)"),
|
|
1040
|
+
public_key_base64url: zod.z.string().optional().describe("Ed25519 public key in base64url encoding (32 bytes)"),
|
|
1041
|
+
jwks: zod.z.string().optional().describe("Inline JWKS JSON containing the verifying key"),
|
|
1042
|
+
issuer: zod.z.string().optional().describe("Expected issuer URI for binding check"),
|
|
1043
|
+
audience: zod.z.string().optional().describe("Expected audience URI for binding check")
|
|
1044
|
+
});
|
|
1045
|
+
var MetaSchema = zod.z.object({
|
|
1046
|
+
serverVersion: zod.z.string(),
|
|
1047
|
+
policyHash: zod.z.string(),
|
|
1048
|
+
protocolVersion: zod.z.string()
|
|
1049
|
+
});
|
|
1050
|
+
var CheckSchema = zod.z.object({
|
|
1051
|
+
name: zod.z.string(),
|
|
1052
|
+
passed: zod.z.boolean(),
|
|
1053
|
+
message: zod.z.string().optional()
|
|
1054
|
+
});
|
|
1055
|
+
var VerifyOutputSchema = zod.z.object({
|
|
1056
|
+
_meta: MetaSchema,
|
|
1057
|
+
ok: zod.z.boolean(),
|
|
1058
|
+
code: zod.z.string().optional(),
|
|
1059
|
+
message: zod.z.string().optional(),
|
|
1060
|
+
variant: zod.z.string().optional(),
|
|
1061
|
+
checks: zod.z.array(CheckSchema),
|
|
1062
|
+
claimsSummary: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
1063
|
+
keySource: zod.z.string().optional()
|
|
1064
|
+
});
|
|
1065
|
+
var InspectInputSchema = zod.z.object({
|
|
1066
|
+
jws: zod.z.string().describe("JWS compact serialization to inspect"),
|
|
1067
|
+
full_claims: zod.z.boolean().optional().default(false).describe("Include full decoded claims (may be redacted by policy)")
|
|
1068
|
+
});
|
|
1069
|
+
var MetaSchema2 = zod.z.object({
|
|
1070
|
+
serverVersion: zod.z.string(),
|
|
1071
|
+
policyHash: zod.z.string(),
|
|
1072
|
+
protocolVersion: zod.z.string()
|
|
1073
|
+
});
|
|
1074
|
+
var InspectOutputSchema = zod.z.object({
|
|
1075
|
+
_meta: MetaSchema2,
|
|
1076
|
+
header: zod.z.record(zod.z.string(), zod.z.unknown()),
|
|
1077
|
+
payloadMeta: zod.z.object({
|
|
1078
|
+
variant: zod.z.string(),
|
|
1079
|
+
issuer: zod.z.string().optional(),
|
|
1080
|
+
audience: zod.z.string().optional(),
|
|
1081
|
+
issuedAt: zod.z.string().optional(),
|
|
1082
|
+
expiresAt: zod.z.string().optional(),
|
|
1083
|
+
receiptId: zod.z.string().optional()
|
|
1084
|
+
}),
|
|
1085
|
+
fullPayload: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
1086
|
+
redacted: zod.z.boolean(),
|
|
1087
|
+
verified: zod.z.literal(false)
|
|
1088
|
+
});
|
|
1089
|
+
var DecodeInputSchema = zod.z.object({
|
|
1090
|
+
jws: zod.z.string().describe("JWS compact serialization to decode")
|
|
1091
|
+
});
|
|
1092
|
+
var MetaSchema3 = zod.z.object({
|
|
1093
|
+
serverVersion: zod.z.string(),
|
|
1094
|
+
policyHash: zod.z.string(),
|
|
1095
|
+
protocolVersion: zod.z.string()
|
|
1096
|
+
});
|
|
1097
|
+
var DecodeOutputSchema = zod.z.object({
|
|
1098
|
+
_meta: MetaSchema3,
|
|
1099
|
+
header: zod.z.record(zod.z.string(), zod.z.unknown()),
|
|
1100
|
+
payload: zod.z.record(zod.z.string(), zod.z.unknown()),
|
|
1101
|
+
verified: zod.z.literal(false)
|
|
1102
|
+
});
|
|
1103
|
+
var IssueInputSchema = zod.z.object({
|
|
1104
|
+
aud: zod.z.string().url().describe("Audience / resource URI (https://)"),
|
|
1105
|
+
amt: zod.z.number().int().nonnegative().describe("Amount in smallest currency unit"),
|
|
1106
|
+
cur: zod.z.string().regex(/^[A-Z]{3}$/).describe("ISO 4217 currency code (uppercase, e.g. USD)"),
|
|
1107
|
+
rail: zod.z.string().describe("Payment rail identifier"),
|
|
1108
|
+
reference: zod.z.string().describe("Unique payment reference"),
|
|
1109
|
+
asset: zod.z.string().optional().describe("Asset transferred (e.g. USDC) -- defaults to currency"),
|
|
1110
|
+
env: zod.z.enum(["live", "test"]).default("test").describe("Environment"),
|
|
1111
|
+
network: zod.z.string().optional().describe("Network/chain identifier"),
|
|
1112
|
+
evidence: zod.z.unknown().optional().describe("Rail-specific evidence (JSON-safe)"),
|
|
1113
|
+
subject: zod.z.string().url().optional().describe("Subject URI (https://)"),
|
|
1114
|
+
ttl_seconds: zod.z.number().int().positive().optional().describe("Time-to-live in seconds (computes exp = iat + ttl)"),
|
|
1115
|
+
kind: zod.z.string().optional().describe("Receipt kind for policy filtering")
|
|
1116
|
+
});
|
|
1117
|
+
var MetaSchema4 = zod.z.object({
|
|
1118
|
+
serverVersion: zod.z.string(),
|
|
1119
|
+
policyHash: zod.z.string(),
|
|
1120
|
+
protocolVersion: zod.z.string()
|
|
1121
|
+
});
|
|
1122
|
+
var ClaimsSummarySchema = zod.z.object({
|
|
1123
|
+
iss: zod.z.string(),
|
|
1124
|
+
aud: zod.z.string(),
|
|
1125
|
+
iat: zod.z.number(),
|
|
1126
|
+
exp: zod.z.number().optional(),
|
|
1127
|
+
rid: zod.z.string(),
|
|
1128
|
+
amt: zod.z.number(),
|
|
1129
|
+
cur: zod.z.string()
|
|
1130
|
+
});
|
|
1131
|
+
var IssueOutputSchema = zod.z.object({
|
|
1132
|
+
_meta: MetaSchema4,
|
|
1133
|
+
ok: zod.z.boolean(),
|
|
1134
|
+
jws: zod.z.string(),
|
|
1135
|
+
claimsSummary: ClaimsSummarySchema
|
|
1136
|
+
});
|
|
1137
|
+
var BundleInputSchema = zod.z.object({
|
|
1138
|
+
receipts: zod.z.array(zod.z.string().max(16384)).min(1).max(256).describe("Receipt JWS strings to bundle (1-256)"),
|
|
1139
|
+
metadata: zod.z.record(zod.z.unknown()).optional().describe("Optional metadata to include in the manifest"),
|
|
1140
|
+
output_path: zod.z.string().max(255).optional().describe("Output directory name (single segment within --bundle-dir)")
|
|
1141
|
+
});
|
|
1142
|
+
var MetaSchema5 = zod.z.object({
|
|
1143
|
+
serverVersion: zod.z.string(),
|
|
1144
|
+
policyHash: zod.z.string(),
|
|
1145
|
+
protocolVersion: zod.z.string()
|
|
1146
|
+
});
|
|
1147
|
+
var BundleOutputSchema = zod.z.object({
|
|
1148
|
+
_meta: MetaSchema5,
|
|
1149
|
+
ok: zod.z.boolean(),
|
|
1150
|
+
bundleId: zod.z.string(),
|
|
1151
|
+
bundleName: zod.z.string(),
|
|
1152
|
+
receiptCount: zod.z.number(),
|
|
1153
|
+
fileCount: zod.z.number(),
|
|
1154
|
+
totalBytes: zod.z.number(),
|
|
1155
|
+
createdAt: zod.z.string(),
|
|
1156
|
+
manifestSha256: zod.z.string()
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
// src/server.ts
|
|
1160
|
+
function makeMeta(ctx, serverName) {
|
|
1161
|
+
return {
|
|
1162
|
+
serverName,
|
|
1163
|
+
serverVersion: ctx.version,
|
|
1164
|
+
policyHash: ctx.policyHash,
|
|
1165
|
+
protocolVersion: ctx.protocolVersion
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
function handlerResultToCallToolResult(result, meta) {
|
|
1169
|
+
return {
|
|
1170
|
+
content: [{ type: "text", text: result.text }],
|
|
1171
|
+
structuredContent: { _meta: meta, ...result.structured },
|
|
1172
|
+
isError: result.isError
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
var PURE_TOOL_ANNOTATIONS = {
|
|
1176
|
+
readOnlyHint: true,
|
|
1177
|
+
destructiveHint: false,
|
|
1178
|
+
idempotentHint: true,
|
|
1179
|
+
openWorldHint: false
|
|
1180
|
+
};
|
|
1181
|
+
var ISSUE_TOOL_ANNOTATIONS = {
|
|
1182
|
+
readOnlyHint: true,
|
|
1183
|
+
destructiveHint: false,
|
|
1184
|
+
idempotentHint: false,
|
|
1185
|
+
openWorldHint: false
|
|
1186
|
+
};
|
|
1187
|
+
var BUNDLE_TOOL_ANNOTATIONS = {
|
|
1188
|
+
readOnlyHint: false,
|
|
1189
|
+
destructiveHint: true,
|
|
1190
|
+
idempotentHint: false,
|
|
1191
|
+
openWorldHint: true
|
|
1192
|
+
};
|
|
1193
|
+
var MAX_INPUT_DEPTH = 10;
|
|
1194
|
+
function errorCallToolResult(meta, code, message) {
|
|
1195
|
+
return {
|
|
1196
|
+
content: [{ type: "text", text: message }],
|
|
1197
|
+
structuredContent: { _meta: meta, ok: false, code, message },
|
|
1198
|
+
isError: true
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
function createPeacMcpServer(options) {
|
|
1202
|
+
const { version, policy, context } = options;
|
|
1203
|
+
const registeredTools = ["peac_verify", "peac_inspect", "peac_decode"];
|
|
1204
|
+
if (context.issuerKey && context.issuerId) {
|
|
1205
|
+
registeredTools.push("peac_issue");
|
|
1206
|
+
if (context.bundleDir) {
|
|
1207
|
+
registeredTools.push("peac_create_bundle");
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
const meta = { ...makeMeta(context, SERVER_NAME), registeredTools };
|
|
1211
|
+
const maxConcurrency = policy.limits.max_concurrency;
|
|
1212
|
+
let activeHandlers = 0;
|
|
1213
|
+
async function wrapHandler(toolName, args, handlerFn, signal) {
|
|
1214
|
+
if (signal?.aborted) {
|
|
1215
|
+
return errorCallToolResult(meta, "E_MCP_CANCELLED", "Request cancelled");
|
|
1216
|
+
}
|
|
1217
|
+
if (activeHandlers >= maxConcurrency) {
|
|
1218
|
+
return errorCallToolResult(
|
|
1219
|
+
meta,
|
|
1220
|
+
"E_MCP_CONCURRENCY_LIMIT",
|
|
1221
|
+
`Server at capacity (${maxConcurrency} concurrent handlers)`
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
if (!checkObjectDepth(args, MAX_INPUT_DEPTH)) {
|
|
1225
|
+
return errorCallToolResult(
|
|
1226
|
+
meta,
|
|
1227
|
+
"E_MCP_INPUT_TOO_DEEP",
|
|
1228
|
+
`Input exceeds maximum nesting depth of ${MAX_INPUT_DEPTH}`
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1231
|
+
const inputSizeResult = checkInputSizes(args, policy);
|
|
1232
|
+
if (inputSizeResult) {
|
|
1233
|
+
return handlerResultToCallToolResult(inputSizeResult, meta);
|
|
1234
|
+
}
|
|
1235
|
+
activeHandlers++;
|
|
1236
|
+
const handlerPromise = handlerFn(signal);
|
|
1237
|
+
void handlerPromise.finally(() => {
|
|
1238
|
+
activeHandlers--;
|
|
1239
|
+
}).catch(() => {
|
|
1240
|
+
});
|
|
1241
|
+
let timedOut = false;
|
|
1242
|
+
let cancelled = false;
|
|
1243
|
+
let timer;
|
|
1244
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
1245
|
+
timer = setTimeout(() => {
|
|
1246
|
+
timedOut = true;
|
|
1247
|
+
reject(new Error(`Tool "${toolName}" timed out after ${policy.limits.tool_timeout_ms}ms`));
|
|
1248
|
+
}, policy.limits.tool_timeout_ms);
|
|
1249
|
+
timer.unref?.();
|
|
1250
|
+
});
|
|
1251
|
+
const raceParticipants = [handlerPromise, timeoutPromise];
|
|
1252
|
+
let abortCleanup;
|
|
1253
|
+
if (signal && !signal.aborted) {
|
|
1254
|
+
const cancelPromise = new Promise((_resolve, reject) => {
|
|
1255
|
+
const onAbort = () => {
|
|
1256
|
+
cancelled = true;
|
|
1257
|
+
reject(new Error("Request cancelled"));
|
|
1258
|
+
};
|
|
1259
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1260
|
+
abortCleanup = () => signal.removeEventListener("abort", onAbort);
|
|
1261
|
+
});
|
|
1262
|
+
raceParticipants.push(cancelPromise);
|
|
1263
|
+
}
|
|
1264
|
+
try {
|
|
1265
|
+
const result = await Promise.race(raceParticipants);
|
|
1266
|
+
const callResult = handlerResultToCallToolResult(result, meta);
|
|
1267
|
+
const outputBytes = measureEnvelopeBytes(callResult);
|
|
1268
|
+
if (outputBytes > policy.limits.max_response_bytes) {
|
|
1269
|
+
return errorCallToolResult(
|
|
1270
|
+
meta,
|
|
1271
|
+
"E_MCP_OUTPUT_TOO_LARGE",
|
|
1272
|
+
`Response is ${outputBytes} bytes, exceeding limit of ${policy.limits.max_response_bytes}`
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
return callResult;
|
|
1276
|
+
} catch (err) {
|
|
1277
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1278
|
+
if (cancelled) {
|
|
1279
|
+
return errorCallToolResult(meta, "E_MCP_CANCELLED", "Request cancelled");
|
|
1280
|
+
}
|
|
1281
|
+
if (timedOut) {
|
|
1282
|
+
return errorCallToolResult(meta, "E_MCP_TOOL_TIMEOUT", message);
|
|
1283
|
+
}
|
|
1284
|
+
return errorCallToolResult(meta, "E_MCP_INTERNAL", message);
|
|
1285
|
+
} finally {
|
|
1286
|
+
clearTimeout(timer);
|
|
1287
|
+
abortCleanup?.();
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
const server = new mcp_js.McpServer(
|
|
1291
|
+
{ name: SERVER_NAME, version },
|
|
1292
|
+
// listChanged: false -- tool set is static per server instance (determined
|
|
1293
|
+
// at startup from CLI flags). The server never emits notifications/tools/list_changed.
|
|
1294
|
+
{ capabilities: { tools: { listChanged: false } } }
|
|
1295
|
+
);
|
|
1296
|
+
const register = server.registerTool.bind(server);
|
|
1297
|
+
register(
|
|
1298
|
+
"peac_verify",
|
|
1299
|
+
{
|
|
1300
|
+
title: "Verify PEAC Receipt",
|
|
1301
|
+
description: "Verify a PEAC receipt JWS signature and validate claims. Returns structured check results.",
|
|
1302
|
+
inputSchema: VerifyInputSchema,
|
|
1303
|
+
outputSchema: VerifyOutputSchema,
|
|
1304
|
+
annotations: PURE_TOOL_ANNOTATIONS
|
|
1305
|
+
},
|
|
1306
|
+
async (args, extra) => wrapHandler(
|
|
1307
|
+
"peac_verify",
|
|
1308
|
+
args,
|
|
1309
|
+
(signal) => handleVerify({ input: args, policy, context, signal }),
|
|
1310
|
+
extra.signal
|
|
1311
|
+
)
|
|
1312
|
+
);
|
|
1313
|
+
register(
|
|
1314
|
+
"peac_inspect",
|
|
1315
|
+
{
|
|
1316
|
+
title: "Inspect PEAC Receipt",
|
|
1317
|
+
description: "Decode and inspect a PEAC receipt without verifying the signature. Shows header, payload metadata, and optionally full claims.",
|
|
1318
|
+
inputSchema: InspectInputSchema,
|
|
1319
|
+
outputSchema: InspectOutputSchema,
|
|
1320
|
+
annotations: PURE_TOOL_ANNOTATIONS
|
|
1321
|
+
},
|
|
1322
|
+
async (args, extra) => wrapHandler(
|
|
1323
|
+
"peac_inspect",
|
|
1324
|
+
args,
|
|
1325
|
+
(signal) => handleInspect({ input: args, policy}),
|
|
1326
|
+
extra.signal
|
|
1327
|
+
)
|
|
1328
|
+
);
|
|
1329
|
+
register(
|
|
1330
|
+
"peac_decode",
|
|
1331
|
+
{
|
|
1332
|
+
title: "Decode PEAC Receipt",
|
|
1333
|
+
description: "Raw decode of a PEAC receipt JWS. Returns header and payload without signature verification.",
|
|
1334
|
+
inputSchema: DecodeInputSchema,
|
|
1335
|
+
outputSchema: DecodeOutputSchema,
|
|
1336
|
+
annotations: PURE_TOOL_ANNOTATIONS
|
|
1337
|
+
},
|
|
1338
|
+
async (args, extra) => wrapHandler(
|
|
1339
|
+
"peac_decode",
|
|
1340
|
+
args,
|
|
1341
|
+
(signal) => handleDecode({ input: args, policy}),
|
|
1342
|
+
extra.signal
|
|
1343
|
+
)
|
|
1344
|
+
);
|
|
1345
|
+
if (context.issuerKey && context.issuerId) {
|
|
1346
|
+
register(
|
|
1347
|
+
"peac_issue",
|
|
1348
|
+
{
|
|
1349
|
+
title: "Issue PEAC Receipt",
|
|
1350
|
+
description: "Sign and return a PEAC receipt JWS. Requires server to be configured with --issuer-key and --issuer-id.",
|
|
1351
|
+
inputSchema: IssueInputSchema,
|
|
1352
|
+
outputSchema: IssueOutputSchema,
|
|
1353
|
+
annotations: ISSUE_TOOL_ANNOTATIONS
|
|
1354
|
+
},
|
|
1355
|
+
async (args, extra) => wrapHandler(
|
|
1356
|
+
"peac_issue",
|
|
1357
|
+
args,
|
|
1358
|
+
(signal) => handleIssue({ input: args, policy, context}),
|
|
1359
|
+
extra.signal
|
|
1360
|
+
)
|
|
1361
|
+
);
|
|
1362
|
+
if (context.bundleDir) {
|
|
1363
|
+
register(
|
|
1364
|
+
"peac_create_bundle",
|
|
1365
|
+
{
|
|
1366
|
+
title: "Create Evidence Bundle",
|
|
1367
|
+
description: "Create a signed evidence bundle directory from receipt JWS strings. Requires --issuer-key, --issuer-id, and --bundle-dir.",
|
|
1368
|
+
inputSchema: BundleInputSchema,
|
|
1369
|
+
outputSchema: BundleOutputSchema,
|
|
1370
|
+
annotations: BUNDLE_TOOL_ANNOTATIONS
|
|
1371
|
+
},
|
|
1372
|
+
async (args, extra) => wrapHandler(
|
|
1373
|
+
"peac_create_bundle",
|
|
1374
|
+
args,
|
|
1375
|
+
(signal) => handleCreateBundle({ input: args, policy, context, signal }),
|
|
1376
|
+
extra.signal
|
|
1377
|
+
)
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
return server;
|
|
1382
|
+
}
|
|
1383
|
+
var ToolPolicySchema = zod.z.object({
|
|
1384
|
+
enabled: zod.z.boolean().default(true),
|
|
1385
|
+
allowed_kinds: zod.z.array(zod.z.string()).optional()
|
|
1386
|
+
});
|
|
1387
|
+
var RedactionSchema = zod.z.object({
|
|
1388
|
+
strip_evidence: zod.z.boolean().default(false),
|
|
1389
|
+
strip_payment: zod.z.boolean().default(false),
|
|
1390
|
+
inspect_full_claims: zod.z.boolean().default(false)
|
|
1391
|
+
});
|
|
1392
|
+
var LimitsSchema = zod.z.object({
|
|
1393
|
+
max_jws_bytes: zod.z.number().int().positive().default(16384),
|
|
1394
|
+
max_response_bytes: zod.z.number().int().positive().default(65536),
|
|
1395
|
+
tool_timeout_ms: zod.z.number().int().positive().default(3e4),
|
|
1396
|
+
max_concurrency: zod.z.number().int().positive().default(10),
|
|
1397
|
+
max_claims_bytes: zod.z.number().int().positive().default(262144),
|
|
1398
|
+
max_bundle_receipts: zod.z.number().int().positive().default(256),
|
|
1399
|
+
max_bundle_bytes: zod.z.number().int().positive().default(16777216),
|
|
1400
|
+
max_ttl_seconds: zod.z.number().int().positive().default(86400)
|
|
1401
|
+
});
|
|
1402
|
+
var JwksConfigSchema = zod.z.object({
|
|
1403
|
+
file: zod.z.string().optional()
|
|
1404
|
+
});
|
|
1405
|
+
var PolicySchema = zod.z.object({
|
|
1406
|
+
version: zod.z.literal("1"),
|
|
1407
|
+
allow_network: zod.z.boolean().default(false),
|
|
1408
|
+
redaction: RedactionSchema.default({}),
|
|
1409
|
+
tools: zod.z.record(zod.z.string(), ToolPolicySchema).default({}),
|
|
1410
|
+
limits: LimitsSchema.default({}),
|
|
1411
|
+
jwks: JwksConfigSchema.optional()
|
|
1412
|
+
});
|
|
1413
|
+
var DEFAULT_POLICY = {
|
|
1414
|
+
version: "1",
|
|
1415
|
+
allow_network: false,
|
|
1416
|
+
redaction: { strip_evidence: false, strip_payment: false, inspect_full_claims: false },
|
|
1417
|
+
tools: {},
|
|
1418
|
+
limits: {
|
|
1419
|
+
max_jws_bytes: 16384,
|
|
1420
|
+
max_response_bytes: 65536,
|
|
1421
|
+
tool_timeout_ms: 3e4,
|
|
1422
|
+
max_concurrency: 10,
|
|
1423
|
+
max_claims_bytes: 262144,
|
|
1424
|
+
max_bundle_receipts: 256,
|
|
1425
|
+
max_bundle_bytes: 16777216,
|
|
1426
|
+
max_ttl_seconds: 86400
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
function getDefaultPolicy() {
|
|
1430
|
+
return structuredClone(DEFAULT_POLICY);
|
|
1431
|
+
}
|
|
1432
|
+
function deepStableStringify(value) {
|
|
1433
|
+
if (value === null || typeof value !== "object") {
|
|
1434
|
+
return JSON.stringify(value);
|
|
1435
|
+
}
|
|
1436
|
+
if (Array.isArray(value)) {
|
|
1437
|
+
return "[" + value.map(deepStableStringify).join(",") + "]";
|
|
1438
|
+
}
|
|
1439
|
+
const obj = value;
|
|
1440
|
+
const keys = Object.keys(obj).sort();
|
|
1441
|
+
const pairs = keys.map((k) => JSON.stringify(k) + ":" + deepStableStringify(obj[k]));
|
|
1442
|
+
return "{" + pairs.join(",") + "}";
|
|
1443
|
+
}
|
|
1444
|
+
async function computePolicyHash(input) {
|
|
1445
|
+
const canonical = typeof input === "string" ? input : deepStableStringify(input);
|
|
1446
|
+
return crypto.sha256Hex(canonical);
|
|
1447
|
+
}
|
|
1448
|
+
async function loadPolicy(filePath) {
|
|
1449
|
+
let raw;
|
|
1450
|
+
try {
|
|
1451
|
+
raw = await promises.readFile(filePath, "utf-8");
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
throw new PolicyLoadError(
|
|
1454
|
+
`Failed to read policy file: ${filePath} -- ${err instanceof Error ? err.message : String(err)}`
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
try {
|
|
1458
|
+
const s = await promises.stat(filePath);
|
|
1459
|
+
if (s.mode & 2) {
|
|
1460
|
+
process.stderr.write(
|
|
1461
|
+
`[peac-mcp-server] WARNING: Policy file ${filePath} is world-writable
|
|
1462
|
+
`
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
} catch {
|
|
1466
|
+
}
|
|
1467
|
+
let parsed;
|
|
1468
|
+
try {
|
|
1469
|
+
parsed = JSON.parse(raw);
|
|
1470
|
+
} catch {
|
|
1471
|
+
throw new PolicyLoadError(`Policy file is not valid JSON: ${filePath}`);
|
|
1472
|
+
}
|
|
1473
|
+
const result = PolicySchema.safeParse(parsed);
|
|
1474
|
+
if (!result.success) {
|
|
1475
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
1476
|
+
throw new PolicyLoadError(`Policy validation failed: ${issues}`);
|
|
1477
|
+
}
|
|
1478
|
+
const hash = await computePolicyHash(result.data);
|
|
1479
|
+
return { policy: result.data, hash };
|
|
1480
|
+
}
|
|
1481
|
+
async function loadIssuerKey(schemeUri) {
|
|
1482
|
+
let raw;
|
|
1483
|
+
if (schemeUri.startsWith("env:")) {
|
|
1484
|
+
const varName = schemeUri.slice(4);
|
|
1485
|
+
const envValue = process.env[varName];
|
|
1486
|
+
if (!envValue) {
|
|
1487
|
+
throw new KeyLoadError(`Environment variable ${varName} is not set or empty`);
|
|
1488
|
+
}
|
|
1489
|
+
raw = envValue;
|
|
1490
|
+
} else if (schemeUri.startsWith("file:")) {
|
|
1491
|
+
const filePath = schemeUri.slice(5);
|
|
1492
|
+
try {
|
|
1493
|
+
raw = await promises.readFile(filePath, "utf-8");
|
|
1494
|
+
} catch (err) {
|
|
1495
|
+
throw new KeyLoadError(
|
|
1496
|
+
`Failed to read key file: ${filePath} -- ${err instanceof Error ? err.message : String(err)}`
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
} else {
|
|
1500
|
+
throw new KeyLoadError(
|
|
1501
|
+
`Unsupported key scheme: ${schemeUri} (expected env:VAR_NAME or file:/path)`
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
let jwk;
|
|
1505
|
+
try {
|
|
1506
|
+
jwk = JSON.parse(raw);
|
|
1507
|
+
} catch {
|
|
1508
|
+
throw new KeyLoadError("Key data is not valid JSON");
|
|
1509
|
+
}
|
|
1510
|
+
if (typeof jwk !== "object" || jwk === null || !("kty" in jwk) || !("crv" in jwk) || !("d" in jwk)) {
|
|
1511
|
+
throw new KeyLoadError("JWK must contain kty, crv, and d fields");
|
|
1512
|
+
}
|
|
1513
|
+
const j = jwk;
|
|
1514
|
+
if (j.kty !== "OKP" || j.crv !== "Ed25519") {
|
|
1515
|
+
throw new KeyLoadError(
|
|
1516
|
+
`JWK must be Ed25519 (kty: OKP, crv: Ed25519), got kty=${String(j.kty)} crv=${String(j.crv)}`
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1519
|
+
if (typeof j.d !== "string" || typeof j.x !== "string") {
|
|
1520
|
+
throw new KeyLoadError("JWK d and x fields must be base64url strings");
|
|
1521
|
+
}
|
|
1522
|
+
const ed25519Jwk = {
|
|
1523
|
+
kty: "OKP",
|
|
1524
|
+
crv: "Ed25519",
|
|
1525
|
+
x: j.x,
|
|
1526
|
+
d: j.d
|
|
1527
|
+
};
|
|
1528
|
+
const valid = await crypto.validateKeypair(ed25519Jwk);
|
|
1529
|
+
if (!valid) {
|
|
1530
|
+
throw new KeyLoadError("JWK keypair validation failed: d does not derive to x");
|
|
1531
|
+
}
|
|
1532
|
+
const privateKey = crypto.base64urlDecode(ed25519Jwk.d);
|
|
1533
|
+
const publicKey = await crypto.derivePublicKey(privateKey);
|
|
1534
|
+
let kid;
|
|
1535
|
+
if (typeof j.kid === "string") {
|
|
1536
|
+
kid = j.kid;
|
|
1537
|
+
} else {
|
|
1538
|
+
const pubB64 = crypto.base64urlEncode(publicKey);
|
|
1539
|
+
const hash = await crypto.sha256Hex(pubB64);
|
|
1540
|
+
kid = hash.slice(0, 16);
|
|
1541
|
+
}
|
|
1542
|
+
return { privateKey, publicKey, kid };
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// src/stdout-fence.ts
|
|
1546
|
+
var MAX_LINE_BYTES = 4 * 1024 * 1024;
|
|
1547
|
+
function installStdoutFence() {
|
|
1548
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
1549
|
+
let buffer = "";
|
|
1550
|
+
let bufferBytes = 0;
|
|
1551
|
+
function validateJsonRpcLine(line) {
|
|
1552
|
+
const lineBytes = new TextEncoder().encode(line).length;
|
|
1553
|
+
if (lineBytes > MAX_LINE_BYTES) {
|
|
1554
|
+
throw new McpServerError(
|
|
1555
|
+
"E_MCP_STDOUT_FENCE_VIOLATION",
|
|
1556
|
+
`Stdout fence: line is ${lineBytes} bytes, exceeding limit of ${MAX_LINE_BYTES}`
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
let parsed;
|
|
1560
|
+
try {
|
|
1561
|
+
parsed = JSON.parse(line);
|
|
1562
|
+
} catch {
|
|
1563
|
+
throw new McpServerError(
|
|
1564
|
+
"E_MCP_STDOUT_FENCE_VIOLATION",
|
|
1565
|
+
`Stdout fence: non-JSON line detected: ${line.slice(0, 100)}`
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
1569
|
+
throw new McpServerError(
|
|
1570
|
+
"E_MCP_STDOUT_FENCE_VIOLATION",
|
|
1571
|
+
`Stdout fence: non-JSON-RPC 2.0 object on stdout: ${line.slice(0, 100)}`
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
const obj = parsed;
|
|
1575
|
+
if (obj.jsonrpc !== "2.0") {
|
|
1576
|
+
throw new McpServerError(
|
|
1577
|
+
"E_MCP_STDOUT_FENCE_VIOLATION",
|
|
1578
|
+
`Stdout fence: non-JSON-RPC 2.0 object on stdout: ${line.slice(0, 100)}`
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
const hasId = "id" in obj;
|
|
1582
|
+
const hasResult = "result" in obj;
|
|
1583
|
+
const hasError = "error" in obj;
|
|
1584
|
+
const hasMethod = "method" in obj;
|
|
1585
|
+
if (hasId && (hasResult || hasError)) {
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
if (hasMethod && !hasId) {
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
throw new McpServerError(
|
|
1592
|
+
"E_MCP_STDOUT_FENCE_VIOLATION",
|
|
1593
|
+
`Stdout fence: malformed JSON-RPC 2.0 message (expected response or notification): ${line.slice(0, 100)}`
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
function processBuffer() {
|
|
1597
|
+
const lines = buffer.split("\n");
|
|
1598
|
+
buffer = lines.pop();
|
|
1599
|
+
bufferBytes = new TextEncoder().encode(buffer).length;
|
|
1600
|
+
for (const line of lines) {
|
|
1601
|
+
const trimmed = line.trim();
|
|
1602
|
+
if (trimmed.length === 0) continue;
|
|
1603
|
+
validateJsonRpcLine(trimmed);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
const patchedWrite = function(chunk, encodingOrCallback, callback) {
|
|
1607
|
+
let encoding;
|
|
1608
|
+
let cb;
|
|
1609
|
+
if (typeof encodingOrCallback === "function") {
|
|
1610
|
+
cb = encodingOrCallback;
|
|
1611
|
+
} else {
|
|
1612
|
+
encoding = encodingOrCallback;
|
|
1613
|
+
cb = callback;
|
|
1614
|
+
}
|
|
1615
|
+
const str = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
|
|
1616
|
+
const chunkBytes = new TextEncoder().encode(str).length;
|
|
1617
|
+
if (bufferBytes + chunkBytes > MAX_LINE_BYTES) {
|
|
1618
|
+
throw new McpServerError(
|
|
1619
|
+
"E_MCP_STDOUT_FENCE_VIOLATION",
|
|
1620
|
+
`Stdout fence: buffer exceeded ${MAX_LINE_BYTES} bytes without a newline`
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
buffer += str;
|
|
1624
|
+
bufferBytes += chunkBytes;
|
|
1625
|
+
processBuffer();
|
|
1626
|
+
if (encoding) {
|
|
1627
|
+
return originalWrite(chunk, encoding, cb);
|
|
1628
|
+
}
|
|
1629
|
+
return originalWrite(chunk, cb);
|
|
1630
|
+
};
|
|
1631
|
+
process.stdout.write = patchedWrite;
|
|
1632
|
+
return function teardown() {
|
|
1633
|
+
if (buffer.trim().length > 0) {
|
|
1634
|
+
validateJsonRpcLine(buffer.trim());
|
|
1635
|
+
}
|
|
1636
|
+
buffer = "";
|
|
1637
|
+
bufferBytes = 0;
|
|
1638
|
+
process.stdout.write = originalWrite;
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
exports.BundleToolError = BundleToolError;
|
|
1643
|
+
exports.DEFAULT_MAX_BUNDLE_BYTES = DEFAULT_MAX_BUNDLE_BYTES;
|
|
1644
|
+
exports.DEFAULT_MAX_BUNDLE_RECEIPTS = DEFAULT_MAX_BUNDLE_RECEIPTS;
|
|
1645
|
+
exports.DEFAULT_MAX_CLAIMS_BYTES = DEFAULT_MAX_CLAIMS_BYTES;
|
|
1646
|
+
exports.DEFAULT_MAX_JWS_BYTES = DEFAULT_MAX_JWS_BYTES;
|
|
1647
|
+
exports.DEFAULT_MAX_RESPONSE_BYTES = DEFAULT_MAX_RESPONSE_BYTES;
|
|
1648
|
+
exports.DEFAULT_MAX_TTL_SECONDS = DEFAULT_MAX_TTL_SECONDS;
|
|
1649
|
+
exports.DEFAULT_TOOL_TIMEOUT_MS = DEFAULT_TOOL_TIMEOUT_MS;
|
|
1650
|
+
exports.IssueToolError = IssueToolError;
|
|
1651
|
+
exports.JwksLoadError = JwksLoadError;
|
|
1652
|
+
exports.KeyLoadError = KeyLoadError;
|
|
1653
|
+
exports.MCP_PROTOCOL_VERSION = MCP_PROTOCOL_VERSION;
|
|
1654
|
+
exports.McpServerError = McpServerError;
|
|
1655
|
+
exports.PathTraversalError = PathTraversalError;
|
|
1656
|
+
exports.PolicyLoadError = PolicyLoadError;
|
|
1657
|
+
exports.SERVER_NAME = SERVER_NAME;
|
|
1658
|
+
exports.SERVER_VERSION = SERVER_VERSION;
|
|
1659
|
+
exports.assertRelativePath = assertRelativePath;
|
|
1660
|
+
exports.atomicWriteDir = atomicWriteDir;
|
|
1661
|
+
exports.checkInputSizes = checkInputSizes;
|
|
1662
|
+
exports.checkJwsSize = checkJwsSize;
|
|
1663
|
+
exports.checkObjectDepth = checkObjectDepth;
|
|
1664
|
+
exports.checkToolEnabled = checkToolEnabled;
|
|
1665
|
+
exports.computePolicyHash = computePolicyHash;
|
|
1666
|
+
exports.createPeacMcpServer = createPeacMcpServer;
|
|
1667
|
+
exports.createTempDir = createTempDir;
|
|
1668
|
+
exports.getDefaultPolicy = getDefaultPolicy;
|
|
1669
|
+
exports.handleCreateBundle = handleCreateBundle;
|
|
1670
|
+
exports.handleDecode = handleDecode;
|
|
1671
|
+
exports.handleInspect = handleInspect;
|
|
1672
|
+
exports.handleIssue = handleIssue;
|
|
1673
|
+
exports.handleVerify = handleVerify;
|
|
1674
|
+
exports.installStdoutFence = installStdoutFence;
|
|
1675
|
+
exports.loadIssuerKey = loadIssuerKey;
|
|
1676
|
+
exports.loadJwksFile = loadJwksFile;
|
|
1677
|
+
exports.loadPolicy = loadPolicy;
|
|
1678
|
+
exports.measureEnvelopeBytes = measureEnvelopeBytes;
|
|
1679
|
+
exports.resolveKeyByKid = resolveKeyByKid;
|
|
1680
|
+
exports.resolveOutputPath = resolveOutputPath;
|
|
1681
|
+
exports.safeMkdir = safeMkdir;
|
|
1682
|
+
exports.sanitizeOutput = sanitizeOutput;
|
|
1683
|
+
exports.truncateResponse = truncateResponse;
|
|
1684
|
+
//# sourceMappingURL=index.cjs.map
|
|
1685
|
+
//# sourceMappingURL=index.cjs.map
|