@peac/mappings-mcp 0.11.0 → 0.11.2

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.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * MCP _meta reserved key guard.
3
+ *
4
+ * Per MCP specification (2025-11-25), the reserved prefix rule is:
5
+ * "Any prefix where the second label is `modelcontextprotocol` or `mcp`
6
+ * is reserved."
7
+ *
8
+ * The prefix is the segment before the first `/`, split by `.` into labels.
9
+ * Only the second label (labels[1], 0-indexed) determines reservation.
10
+ *
11
+ * Examples:
12
+ * "dev.mcp/anything" -> RESERVED (2nd label = mcp)
13
+ * "io.modelcontextprotocol/data" -> RESERVED (2nd label = modelcontextprotocol)
14
+ * "com.mcp.tools/data" -> RESERVED (2nd label = mcp)
15
+ * "tools.mcp.com/data" -> RESERVED (2nd label = mcp)
16
+ * "mcp.dev/anything" -> NOT reserved (2nd label = dev)
17
+ * "modelcontextprotocol.io/data" -> NOT reserved (2nd label = io)
18
+ * "com.example.mcp/data" -> NOT reserved (2nd label = example)
19
+ * "org.peacprotocol/receipt_ref" -> NOT reserved (2nd label = peacprotocol)
20
+ */
21
+ /**
22
+ * Assert that a _meta key does not use an MCP-reserved prefix.
23
+ *
24
+ * A prefix is reserved when its second label (labels[1], 0-indexed)
25
+ * equals "mcp" or "modelcontextprotocol" (case-insensitive).
26
+ *
27
+ * @throws Error if the key uses a reserved prefix
28
+ */
29
+ export declare function assertNotMcpReservedKey(key: string): void;
30
+ /**
31
+ * Check if a _meta key uses an MCP-reserved prefix (non-throwing).
32
+ */
33
+ export declare function isMcpReservedKey(key: string): boolean;
34
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAczD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOrD"}
package/dist/index.cjs CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var schema = require('@peac/schema');
4
+
3
5
  // src/budget.ts
4
6
  function checkBudget(spentMinor, costMinor, currency, config) {
5
7
  if (currency !== config.currency) {
@@ -65,6 +67,162 @@ function checkBudget(spentMinor, costMinor, currency, config) {
65
67
  remainingMinor
66
68
  };
67
69
  }
70
+ var META_KEY_RECEIPT_REF = "org.peacprotocol/receipt_ref";
71
+ var META_KEY_RECEIPT_JWS = "org.peacprotocol/receipt_jws";
72
+ var META_KEY_AGENT_ID = "org.peacprotocol/agent_id";
73
+ var META_KEY_VERIFIED_AT = "org.peacprotocol/verified_at";
74
+ var META_KEY_RECEIPT_URL = "org.peacprotocol/receipt_url";
75
+ var META_KEY_LEGACY_RECEIPT = "org.peacprotocol/receipt";
76
+ var MCP_MAX_CARRIER_SIZE = schema.CARRIER_TRANSPORT_LIMITS.mcp;
77
+ function attachReceiptToMeta(result, carrier, opts) {
78
+ const meta = {
79
+ transport: "mcp",
80
+ format: "embed",
81
+ max_size: MCP_MAX_CARRIER_SIZE
82
+ };
83
+ const validation = schema.validateCarrierConstraints(carrier, meta);
84
+ if (!validation.valid) {
85
+ throw new Error(`Carrier constraint violation: ${validation.violations.join("; ")}`);
86
+ }
87
+ if (!result._meta) {
88
+ result._meta = {};
89
+ }
90
+ if (opts?.legacyFormat) {
91
+ if (carrier.receipt_jws) {
92
+ result._meta[META_KEY_LEGACY_RECEIPT] = carrier.receipt_jws;
93
+ }
94
+ } else {
95
+ result._meta[META_KEY_RECEIPT_REF] = carrier.receipt_ref;
96
+ if (carrier.receipt_jws) {
97
+ result._meta[META_KEY_RECEIPT_JWS] = carrier.receipt_jws;
98
+ }
99
+ if (carrier.receipt_url) {
100
+ result._meta[META_KEY_RECEIPT_URL] = carrier.receipt_url;
101
+ }
102
+ }
103
+ if (opts?.agentId) {
104
+ result._meta[META_KEY_AGENT_ID] = opts.agentId;
105
+ }
106
+ if (opts?.verifiedAt) {
107
+ result._meta[META_KEY_VERIFIED_AT] = opts.verifiedAt;
108
+ }
109
+ return result;
110
+ }
111
+ function extractReceiptFromMeta(result) {
112
+ if (!result._meta) return null;
113
+ const meta = {
114
+ transport: "mcp",
115
+ format: "embed",
116
+ max_size: MCP_MAX_CARRIER_SIZE
117
+ };
118
+ const receiptRef = result._meta[META_KEY_RECEIPT_REF];
119
+ if (typeof receiptRef === "string") {
120
+ const carrier = { receipt_ref: receiptRef };
121
+ const receiptJws = result._meta[META_KEY_RECEIPT_JWS];
122
+ if (typeof receiptJws === "string") {
123
+ carrier.receipt_jws = receiptJws;
124
+ }
125
+ const receiptUrl = result._meta[META_KEY_RECEIPT_URL];
126
+ if (typeof receiptUrl === "string") {
127
+ carrier.receipt_url = receiptUrl;
128
+ }
129
+ const parsed = schema.PeacEvidenceCarrierSchema.safeParse(carrier);
130
+ if (parsed.success) {
131
+ return { receipts: [parsed.data], meta };
132
+ }
133
+ }
134
+ const legacyReceipt = result._meta[META_KEY_LEGACY_RECEIPT];
135
+ if (typeof legacyReceipt === "string" && legacyReceipt.length > 0) {
136
+ return {
137
+ receipts: [
138
+ {
139
+ receipt_ref: "sha256:0000000000000000000000000000000000000000000000000000000000000000",
140
+ receipt_jws: legacyReceipt
141
+ }
142
+ ],
143
+ meta: { ...meta, redaction: ["legacy_receipt_ref_pending"] }
144
+ };
145
+ }
146
+ return null;
147
+ }
148
+ async function extractReceiptFromMetaAsync(result) {
149
+ if (!result._meta) return null;
150
+ const meta = {
151
+ transport: "mcp",
152
+ format: "embed",
153
+ max_size: MCP_MAX_CARRIER_SIZE
154
+ };
155
+ const receiptRef = result._meta[META_KEY_RECEIPT_REF];
156
+ if (typeof receiptRef === "string") {
157
+ const carrier = { receipt_ref: receiptRef };
158
+ const receiptJws = result._meta[META_KEY_RECEIPT_JWS];
159
+ if (typeof receiptJws === "string") {
160
+ carrier.receipt_jws = receiptJws;
161
+ }
162
+ const receiptUrl = result._meta[META_KEY_RECEIPT_URL];
163
+ if (typeof receiptUrl === "string") {
164
+ carrier.receipt_url = receiptUrl;
165
+ }
166
+ const parsed = schema.PeacEvidenceCarrierSchema.safeParse(carrier);
167
+ if (parsed.success) {
168
+ const validCarrier = parsed.data;
169
+ const error = await schema.verifyReceiptRefConsistency(validCarrier);
170
+ const violations = error ? [error] : [];
171
+ return {
172
+ receipts: error ? [] : [validCarrier],
173
+ meta,
174
+ violations
175
+ };
176
+ }
177
+ }
178
+ const legacyReceipt = result._meta[META_KEY_LEGACY_RECEIPT];
179
+ if (typeof legacyReceipt === "string" && legacyReceipt.length > 0) {
180
+ const computedRef = await schema.computeReceiptRef(legacyReceipt);
181
+ const carrier = {
182
+ receipt_ref: computedRef,
183
+ receipt_jws: legacyReceipt
184
+ };
185
+ return { receipts: [carrier], meta, violations: [] };
186
+ }
187
+ return null;
188
+ }
189
+ var McpCarrierAdapter = class {
190
+ extract(input) {
191
+ return extractReceiptFromMeta(input);
192
+ }
193
+ attach(output, carriers, meta) {
194
+ if (carriers.length > 0) {
195
+ attachReceiptToMeta(output, carriers[0]);
196
+ }
197
+ return output;
198
+ }
199
+ validateConstraints(carrier, meta) {
200
+ return schema.validateCarrierConstraints(carrier, meta);
201
+ }
202
+ };
203
+
204
+ // src/guard.ts
205
+ var MCP_RESERVED_SECOND_LABELS = ["modelcontextprotocol", "mcp"];
206
+ function assertNotMcpReservedKey(key) {
207
+ const slashIndex = key.indexOf("/");
208
+ if (slashIndex === -1) return;
209
+ const prefix = key.substring(0, slashIndex);
210
+ const labels = prefix.split(".");
211
+ if (labels.length < 2) return;
212
+ if (MCP_RESERVED_SECOND_LABELS.includes(labels[1].toLowerCase())) {
213
+ throw new Error(
214
+ `Reserved MCP _meta key prefix: ${key} (second label "${labels[1]}" is reserved per MCP spec)`
215
+ );
216
+ }
217
+ }
218
+ function isMcpReservedKey(key) {
219
+ try {
220
+ assertNotMcpReservedKey(key);
221
+ return false;
222
+ } catch {
223
+ return true;
224
+ }
225
+ }
68
226
 
69
227
  // src/index.ts
70
228
  function attachReceipt(response, receiptJWS) {
@@ -74,6 +232,17 @@ function attachReceipt(response, receiptJWS) {
74
232
  };
75
233
  }
76
234
  function extractReceipt(response) {
235
+ if (response._meta) {
236
+ const meta = response._meta;
237
+ const jws = meta["org.peacprotocol/receipt_jws"];
238
+ if (typeof jws === "string" && jws.length > 0) {
239
+ return jws;
240
+ }
241
+ const legacyJws = meta["org.peacprotocol/receipt"];
242
+ if (typeof legacyJws === "string" && legacyJws.length > 0) {
243
+ return legacyJws;
244
+ }
245
+ }
77
246
  if (typeof response.peac_receipt === "string" && response.peac_receipt.length > 0) {
78
247
  return response.peac_receipt;
79
248
  }
@@ -91,10 +260,23 @@ function createPaidToolResponse(tool, result, receiptJWS, metadata) {
91
260
  return attachReceipt(response, receiptJWS);
92
261
  }
93
262
 
263
+ exports.MCP_MAX_CARRIER_SIZE = MCP_MAX_CARRIER_SIZE;
264
+ exports.META_KEY_AGENT_ID = META_KEY_AGENT_ID;
265
+ exports.META_KEY_LEGACY_RECEIPT = META_KEY_LEGACY_RECEIPT;
266
+ exports.META_KEY_RECEIPT_JWS = META_KEY_RECEIPT_JWS;
267
+ exports.META_KEY_RECEIPT_REF = META_KEY_RECEIPT_REF;
268
+ exports.META_KEY_RECEIPT_URL = META_KEY_RECEIPT_URL;
269
+ exports.META_KEY_VERIFIED_AT = META_KEY_VERIFIED_AT;
270
+ exports.McpCarrierAdapter = McpCarrierAdapter;
271
+ exports.assertNotMcpReservedKey = assertNotMcpReservedKey;
94
272
  exports.attachReceipt = attachReceipt;
273
+ exports.attachReceiptToMeta = attachReceiptToMeta;
95
274
  exports.checkBudget = checkBudget;
96
275
  exports.createPaidToolResponse = createPaidToolResponse;
97
276
  exports.extractReceipt = extractReceipt;
277
+ exports.extractReceiptFromMeta = extractReceiptFromMeta;
278
+ exports.extractReceiptFromMetaAsync = extractReceiptFromMetaAsync;
98
279
  exports.hasReceipt = hasReceipt;
280
+ exports.isMcpReservedKey = isMcpReservedKey;
99
281
  //# sourceMappingURL=index.cjs.map
100
282
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/budget.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Budget enforcement utilities for MCP tool calls\n * Uses bigint for all currency calculations to avoid floating-point errors\n */\n\nexport interface BudgetConfig {\n /** Maximum cost per single call in minor currency units (e.g., cents for USD) */\n maxPerCallMinor?: bigint;\n /** Maximum total cost per day in minor currency units */\n maxDailyMinor?: bigint;\n /** Maximum total cost per month in minor currency units */\n maxMonthlyMinor?: bigint;\n /** Currency code (must match at runtime, e.g., 'USD', 'INR') */\n currency: string;\n}\n\nexport interface BudgetResult {\n /** Whether the cost is allowed within budget limits */\n allowed: boolean;\n /**\n * Remaining budget in minor units (after this operation).\n * - When allowed: the minimum remaining across all applicable limits\n * - When denied: 0n (never negative)\n * - When unbounded: undefined (check `unbounded` flag instead)\n */\n remainingMinor?: bigint;\n /**\n * True when no budget limits are configured.\n * In this case, remainingMinor is undefined.\n */\n unbounded?: boolean;\n /** Error code if not allowed */\n code?: 'budget_exceeded' | 'currency_mismatch';\n /** Human-readable reason if not allowed */\n reason?: string;\n}\n\n/**\n * Check if a cost is within budget.\n * Pure, deterministic function - caller tracks time windows externally.\n *\n * @param spentMinor - Amount already spent in minor units (e.g., cents)\n * @param costMinor - Cost of current operation in minor units\n * @param currency - Currency code (must match config.currency)\n * @param config - Budget configuration\n * @returns Result indicating whether cost is allowed and remaining budget\n *\n * @example\n * ```typescript\n * const config: BudgetConfig = {\n * maxPerCallMinor: 500n, // $5.00 max per call\n * maxDailyMinor: 10000n, // $100.00 max per day\n * currency: 'USD'\n * };\n *\n * const result = checkBudget(\n * 8500n, // Already spent $85.00 today\n * 1000n, // Current call costs $10.00\n * 'USD',\n * config\n * );\n *\n * if (result.allowed) {\n * console.log(`Allowed. Remaining: ${result.remainingMinor} cents`);\n * } else {\n * console.log(`Denied: ${result.reason}`);\n * }\n * ```\n */\nexport function checkBudget(\n spentMinor: bigint,\n costMinor: bigint,\n currency: string,\n config: BudgetConfig\n): BudgetResult {\n // Currency mismatch check\n if (currency !== config.currency) {\n return {\n allowed: false,\n remainingMinor: 0n,\n code: 'currency_mismatch',\n reason: `Currency mismatch: expected ${config.currency}, got ${currency}`,\n };\n }\n\n // Check per-call limit\n if (config.maxPerCallMinor !== undefined && costMinor > config.maxPerCallMinor) {\n return {\n allowed: false,\n remainingMinor: 0n, // Never negative - clamp to 0\n code: 'budget_exceeded',\n reason: `Per-call limit exceeded: cost ${costMinor} > limit ${config.maxPerCallMinor}`,\n };\n }\n\n // Check daily limit (if configured)\n if (config.maxDailyMinor !== undefined) {\n const newTotal = spentMinor + costMinor;\n if (newTotal > config.maxDailyMinor) {\n return {\n allowed: false,\n remainingMinor: config.maxDailyMinor - spentMinor,\n code: 'budget_exceeded',\n reason: `Daily limit exceeded: total ${newTotal} > limit ${config.maxDailyMinor}`,\n };\n }\n }\n\n // Check monthly limit (if configured)\n if (config.maxMonthlyMinor !== undefined) {\n const newTotal = spentMinor + costMinor;\n if (newTotal > config.maxMonthlyMinor) {\n return {\n allowed: false,\n remainingMinor: config.maxMonthlyMinor - spentMinor,\n code: 'budget_exceeded',\n reason: `Monthly limit exceeded: total ${newTotal} > limit ${config.maxMonthlyMinor}`,\n };\n }\n }\n\n // Calculate remaining budget (how much can be spent in future operations)\n // This is the minimum of:\n // 1. Per-call limit (max single operation)\n // 2. Remaining daily budget\n // 3. Remaining monthly budget\n const remainingCandidates: bigint[] = [];\n\n if (config.maxPerCallMinor !== undefined) {\n remainingCandidates.push(config.maxPerCallMinor);\n }\n\n if (config.maxDailyMinor !== undefined) {\n const dailyRemaining = config.maxDailyMinor - (spentMinor + costMinor);\n remainingCandidates.push(dailyRemaining);\n }\n\n if (config.maxMonthlyMinor !== undefined) {\n const monthlyRemaining = config.maxMonthlyMinor - (spentMinor + costMinor);\n remainingCandidates.push(monthlyRemaining);\n }\n\n // Return the most restrictive limit\n if (remainingCandidates.length === 0) {\n // No limits configured - unbounded\n return {\n allowed: true,\n unbounded: true,\n };\n }\n\n const remainingMinor = remainingCandidates.reduce((min, val) => (val < min ? val : min));\n\n return {\n allowed: true,\n remainingMinor,\n };\n}\n","/**\n * Model Context Protocol (MCP) integration\n * Attach PEAC receipts to MCP tool responses\n */\n\nexport * from './budget';\n\n/**\n * MCP Tool Response\n */\nexport interface MCPToolResponse {\n tool: string;\n result: unknown;\n metadata?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * MCP Tool Response with PEAC receipt\n */\nexport interface MCPToolResponseWithReceipt extends MCPToolResponse {\n peac_receipt?: string;\n}\n\n/**\n * Attach PEAC receipt to MCP tool response\n *\n * @param response - MCP tool response\n * @param receiptJWS - PEAC receipt JWS\n * @returns MCP tool response with PEAC receipt attached\n */\nexport function attachReceipt(\n response: MCPToolResponse,\n receiptJWS: string\n): MCPToolResponseWithReceipt {\n return {\n ...response,\n peac_receipt: receiptJWS,\n };\n}\n\n/**\n * Extract PEAC receipt from MCP tool response\n *\n * @param response - MCP tool response (possibly with receipt)\n * @returns PEAC receipt JWS or null if not present\n */\nexport function extractReceipt(response: MCPToolResponseWithReceipt): string | null {\n if (typeof response.peac_receipt === 'string' && response.peac_receipt.length > 0) {\n return response.peac_receipt;\n }\n return null;\n}\n\n/**\n * Check if MCP tool response has a PEAC receipt\n *\n * @param response - MCP tool response\n * @returns true if receipt is present\n */\nexport function hasReceipt(response: MCPToolResponseWithReceipt): boolean {\n return extractReceipt(response) !== null;\n}\n\n/**\n * Create MCP tool response with PEAC receipt for paid API calls\n *\n * @param tool - Tool name\n * @param result - Tool execution result\n * @param receiptJWS - PEAC receipt JWS\n * @param metadata - Optional metadata\n * @returns MCP tool response with receipt\n */\nexport function createPaidToolResponse(\n tool: string,\n result: unknown,\n receiptJWS: string,\n metadata?: Record<string, unknown>\n): MCPToolResponseWithReceipt {\n const response: MCPToolResponse = {\n tool,\n result,\n ...(metadata && { metadata }),\n };\n\n return attachReceipt(response, receiptJWS);\n}\n"]}
1
+ {"version":3,"sources":["../src/budget.ts","../src/meta.ts","../src/guard.ts","../src/index.ts"],"names":["CARRIER_TRANSPORT_LIMITS","validateCarrierConstraints","PeacEvidenceCarrierSchema","verifyReceiptRefConsistency","computeReceiptRef"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Budget enforcement utilities for MCP tool calls\n * Uses bigint for all currency calculations to avoid floating-point errors\n */\n\nexport interface BudgetConfig {\n /** Maximum cost per single call in minor currency units (e.g., cents for USD) */\n maxPerCallMinor?: bigint;\n /** Maximum total cost per day in minor currency units */\n maxDailyMinor?: bigint;\n /** Maximum total cost per month in minor currency units */\n maxMonthlyMinor?: bigint;\n /** Currency code (must match at runtime, e.g., 'USD', 'INR') */\n currency: string;\n}\n\nexport interface BudgetResult {\n /** Whether the cost is allowed within budget limits */\n allowed: boolean;\n /**\n * Remaining budget in minor units (after this operation).\n * - When allowed: the minimum remaining across all applicable limits\n * - When denied: 0n (never negative)\n * - When unbounded: undefined (check `unbounded` flag instead)\n */\n remainingMinor?: bigint;\n /**\n * True when no budget limits are configured.\n * In this case, remainingMinor is undefined.\n */\n unbounded?: boolean;\n /** Error code if not allowed */\n code?: 'budget_exceeded' | 'currency_mismatch';\n /** Human-readable reason if not allowed */\n reason?: string;\n}\n\n/**\n * Check if a cost is within budget.\n * Pure, deterministic function - caller tracks time windows externally.\n *\n * @param spentMinor - Amount already spent in minor units (e.g., cents)\n * @param costMinor - Cost of current operation in minor units\n * @param currency - Currency code (must match config.currency)\n * @param config - Budget configuration\n * @returns Result indicating whether cost is allowed and remaining budget\n *\n * @example\n * ```typescript\n * const config: BudgetConfig = {\n * maxPerCallMinor: 500n, // $5.00 max per call\n * maxDailyMinor: 10000n, // $100.00 max per day\n * currency: 'USD'\n * };\n *\n * const result = checkBudget(\n * 8500n, // Already spent $85.00 today\n * 1000n, // Current call costs $10.00\n * 'USD',\n * config\n * );\n *\n * if (result.allowed) {\n * console.log(`Allowed. Remaining: ${result.remainingMinor} cents`);\n * } else {\n * console.log(`Denied: ${result.reason}`);\n * }\n * ```\n */\nexport function checkBudget(\n spentMinor: bigint,\n costMinor: bigint,\n currency: string,\n config: BudgetConfig\n): BudgetResult {\n // Currency mismatch check\n if (currency !== config.currency) {\n return {\n allowed: false,\n remainingMinor: 0n,\n code: 'currency_mismatch',\n reason: `Currency mismatch: expected ${config.currency}, got ${currency}`,\n };\n }\n\n // Check per-call limit\n if (config.maxPerCallMinor !== undefined && costMinor > config.maxPerCallMinor) {\n return {\n allowed: false,\n remainingMinor: 0n, // Never negative - clamp to 0\n code: 'budget_exceeded',\n reason: `Per-call limit exceeded: cost ${costMinor} > limit ${config.maxPerCallMinor}`,\n };\n }\n\n // Check daily limit (if configured)\n if (config.maxDailyMinor !== undefined) {\n const newTotal = spentMinor + costMinor;\n if (newTotal > config.maxDailyMinor) {\n return {\n allowed: false,\n remainingMinor: config.maxDailyMinor - spentMinor,\n code: 'budget_exceeded',\n reason: `Daily limit exceeded: total ${newTotal} > limit ${config.maxDailyMinor}`,\n };\n }\n }\n\n // Check monthly limit (if configured)\n if (config.maxMonthlyMinor !== undefined) {\n const newTotal = spentMinor + costMinor;\n if (newTotal > config.maxMonthlyMinor) {\n return {\n allowed: false,\n remainingMinor: config.maxMonthlyMinor - spentMinor,\n code: 'budget_exceeded',\n reason: `Monthly limit exceeded: total ${newTotal} > limit ${config.maxMonthlyMinor}`,\n };\n }\n }\n\n // Calculate remaining budget (how much can be spent in future operations)\n // This is the minimum of:\n // 1. Per-call limit (max single operation)\n // 2. Remaining daily budget\n // 3. Remaining monthly budget\n const remainingCandidates: bigint[] = [];\n\n if (config.maxPerCallMinor !== undefined) {\n remainingCandidates.push(config.maxPerCallMinor);\n }\n\n if (config.maxDailyMinor !== undefined) {\n const dailyRemaining = config.maxDailyMinor - (spentMinor + costMinor);\n remainingCandidates.push(dailyRemaining);\n }\n\n if (config.maxMonthlyMinor !== undefined) {\n const monthlyRemaining = config.maxMonthlyMinor - (spentMinor + costMinor);\n remainingCandidates.push(monthlyRemaining);\n }\n\n // Return the most restrictive limit\n if (remainingCandidates.length === 0) {\n // No limits configured - unbounded\n return {\n allowed: true,\n unbounded: true,\n };\n }\n\n const remainingMinor = remainingCandidates.reduce((min, val) => (val < min ? val : min));\n\n return {\n allowed: true,\n remainingMinor,\n };\n}\n","/**\n * MCP _meta carrier format (DD-125).\n *\n * Attach and extract PEAC evidence carriers using MCP _meta keys.\n * Uses reverse-DNS keys under \"org.peacprotocol/\" prefix.\n *\n * New format (v0.11.1+):\n * _meta[\"org.peacprotocol/receipt_ref\"] = \"sha256:...\"\n * _meta[\"org.peacprotocol/receipt_jws\"] = \"eyJ...\"\n *\n * Legacy format (v0.10.13):\n * _meta[\"org.peacprotocol/receipt\"] = \"eyJ...\" (JWS only, no receipt_ref)\n */\n\nimport type {\n PeacEvidenceCarrier,\n CarrierMeta,\n CarrierValidationResult,\n CarrierAdapter,\n} from '@peac/kernel';\nimport {\n PeacEvidenceCarrierSchema,\n computeReceiptRef,\n validateCarrierConstraints,\n verifyReceiptRefConsistency,\n CARRIER_TRANSPORT_LIMITS,\n} from '@peac/schema';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** _meta key for receipt reference (v0.11.1+) */\nexport const META_KEY_RECEIPT_REF = 'org.peacprotocol/receipt_ref' as const;\n\n/** _meta key for receipt JWS (v0.11.1+) */\nexport const META_KEY_RECEIPT_JWS = 'org.peacprotocol/receipt_jws' as const;\n\n/** _meta key for agent ID (v0.10.13+, unchanged) */\nexport const META_KEY_AGENT_ID = 'org.peacprotocol/agent_id' as const;\n\n/** _meta key for verification timestamp (v0.10.13+, unchanged) */\nexport const META_KEY_VERIFIED_AT = 'org.peacprotocol/verified_at' as const;\n\n/** _meta key for receipt URL locator hint (v0.11.2+, DD-135) */\nexport const META_KEY_RECEIPT_URL = 'org.peacprotocol/receipt_url' as const;\n\n/** Legacy _meta key for receipt JWS (v0.10.13, DD-125) */\nexport const META_KEY_LEGACY_RECEIPT = 'org.peacprotocol/receipt' as const;\n\n/** Maximum carrier size for MCP _meta (64 KB, DD-127) */\nexport const MCP_MAX_CARRIER_SIZE = CARRIER_TRANSPORT_LIMITS.mcp;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** MCP _meta object */\nexport type McpMeta = Record<string, unknown>;\n\n/** MCP JSON-RPC result with _meta */\nexport interface McpResultLike {\n _meta?: McpMeta;\n [key: string]: unknown;\n}\n\n/** Options for attachReceiptToMeta */\nexport interface AttachMetaOptions {\n /** Use legacy format (v0.10.13 peac_receipt key). Default: false */\n legacyFormat?: boolean;\n /** Agent ID to include in _meta */\n agentId?: string;\n /** Verification timestamp (ISO 8601) */\n verifiedAt?: string;\n}\n\n/** Result of extraction */\nexport interface McpExtractResult {\n receipts: PeacEvidenceCarrier[];\n meta: CarrierMeta;\n}\n\n/** Async extraction result with consistency violations */\nexport interface McpExtractAsyncResult extends McpExtractResult {\n violations: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Attach\n// ---------------------------------------------------------------------------\n\n/**\n * Attach PEAC evidence carrier to MCP _meta.\n *\n * Default format (v0.11.1+): writes receipt_ref and receipt_jws as separate keys.\n * Legacy format (opts.legacyFormat): writes single \"receipt\" key (DD-125 phase 1).\n */\nexport function attachReceiptToMeta(\n result: McpResultLike,\n carrier: PeacEvidenceCarrier,\n opts?: AttachMetaOptions\n): McpResultLike {\n const meta: CarrierMeta = {\n transport: 'mcp',\n format: 'embed',\n max_size: MCP_MAX_CARRIER_SIZE,\n };\n\n const validation = validateCarrierConstraints(carrier, meta);\n if (!validation.valid) {\n throw new Error(`Carrier constraint violation: ${validation.violations.join('; ')}`);\n }\n\n if (!result._meta) {\n result._meta = {};\n }\n\n if (opts?.legacyFormat) {\n // DD-125 legacy format: single JWS string\n if (carrier.receipt_jws) {\n result._meta[META_KEY_LEGACY_RECEIPT] = carrier.receipt_jws;\n }\n } else {\n // v0.11.1+ carrier format\n result._meta[META_KEY_RECEIPT_REF] = carrier.receipt_ref;\n if (carrier.receipt_jws) {\n result._meta[META_KEY_RECEIPT_JWS] = carrier.receipt_jws;\n }\n if (carrier.receipt_url) {\n result._meta[META_KEY_RECEIPT_URL] = carrier.receipt_url;\n }\n }\n\n // Optional metadata fields (unchanged from v0.10.13)\n if (opts?.agentId) {\n result._meta[META_KEY_AGENT_ID] = opts.agentId;\n }\n if (opts?.verifiedAt) {\n result._meta[META_KEY_VERIFIED_AT] = opts.verifiedAt;\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Extract (sync: structural validation only)\n// ---------------------------------------------------------------------------\n\n/**\n * Extract PEAC evidence carrier from MCP _meta (sync).\n *\n * Reads in order (DD-125):\n * 1. New carrier keys (receipt_ref + receipt_jws)\n * 2. Legacy key (org.peacprotocol/receipt, v0.10.13)\n *\n * For legacy format, computes receipt_ref from the JWS to return a proper\n * PeacEvidenceCarrier (Polish B). Note: this requires async for the hash,\n * so the sync version uses a placeholder receipt_ref for legacy and marks\n * it in the meta. Use extractReceiptFromMetaAsync for full validation.\n */\nexport function extractReceiptFromMeta(result: McpResultLike): McpExtractResult | null {\n if (!result._meta) return null;\n\n const meta: CarrierMeta = {\n transport: 'mcp',\n format: 'embed',\n max_size: MCP_MAX_CARRIER_SIZE,\n };\n\n // 1. Try new format (v0.11.1+)\n const receiptRef = result._meta[META_KEY_RECEIPT_REF];\n if (typeof receiptRef === 'string') {\n const carrier: Record<string, unknown> = { receipt_ref: receiptRef };\n const receiptJws = result._meta[META_KEY_RECEIPT_JWS];\n if (typeof receiptJws === 'string') {\n carrier.receipt_jws = receiptJws;\n }\n const receiptUrl = result._meta[META_KEY_RECEIPT_URL];\n if (typeof receiptUrl === 'string') {\n carrier.receipt_url = receiptUrl;\n }\n\n const parsed = PeacEvidenceCarrierSchema.safeParse(carrier);\n if (parsed.success) {\n return { receipts: [parsed.data as PeacEvidenceCarrier], meta };\n }\n }\n\n // 2. Try legacy format (v0.10.13, DD-125 + Polish B)\n const legacyReceipt = result._meta[META_KEY_LEGACY_RECEIPT];\n if (typeof legacyReceipt === 'string' && legacyReceipt.length > 0) {\n // Legacy format has JWS only; we can't compute receipt_ref synchronously.\n // Return the JWS in a carrier with a placeholder ref that extractAsync will fix.\n // For sync extraction, we skip schema validation of receipt_ref since we can't\n // compute it without async. Return the raw data and let async handle integrity.\n return {\n receipts: [\n {\n receipt_ref:\n 'sha256:0000000000000000000000000000000000000000000000000000000000000000' as PeacEvidenceCarrier['receipt_ref'],\n receipt_jws: legacyReceipt,\n },\n ],\n meta: { ...meta, redaction: ['legacy_receipt_ref_pending'] },\n };\n }\n\n return null;\n}\n\n/**\n * Extract PEAC evidence carrier from MCP _meta (async, DD-129).\n *\n * Performs structural validation AND receipt_ref consistency check.\n * For legacy format (v0.10.13), computes receipt_ref from JWS (Polish B).\n */\nexport async function extractReceiptFromMetaAsync(\n result: McpResultLike\n): Promise<McpExtractAsyncResult | null> {\n if (!result._meta) return null;\n\n const meta: CarrierMeta = {\n transport: 'mcp',\n format: 'embed',\n max_size: MCP_MAX_CARRIER_SIZE,\n };\n\n // 1. Try new format (v0.11.1+)\n const receiptRef = result._meta[META_KEY_RECEIPT_REF];\n if (typeof receiptRef === 'string') {\n const carrier: Record<string, unknown> = { receipt_ref: receiptRef };\n const receiptJws = result._meta[META_KEY_RECEIPT_JWS];\n if (typeof receiptJws === 'string') {\n carrier.receipt_jws = receiptJws;\n }\n const receiptUrl = result._meta[META_KEY_RECEIPT_URL];\n if (typeof receiptUrl === 'string') {\n carrier.receipt_url = receiptUrl;\n }\n\n const parsed = PeacEvidenceCarrierSchema.safeParse(carrier);\n if (parsed.success) {\n const validCarrier = parsed.data as PeacEvidenceCarrier;\n const error = await verifyReceiptRefConsistency(validCarrier);\n const violations = error ? [error] : [];\n return {\n receipts: error ? [] : [validCarrier],\n meta,\n violations,\n };\n }\n }\n\n // 2. Try legacy format (v0.10.13, DD-125 + Polish B)\n const legacyReceipt = result._meta[META_KEY_LEGACY_RECEIPT];\n if (typeof legacyReceipt === 'string' && legacyReceipt.length > 0) {\n // Compute receipt_ref from legacy JWS\n const computedRef = await computeReceiptRef(legacyReceipt);\n const carrier: PeacEvidenceCarrier = {\n receipt_ref: computedRef,\n receipt_jws: legacyReceipt,\n };\n return { receipts: [carrier], meta, violations: [] };\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// McpCarrierAdapter\n// ---------------------------------------------------------------------------\n\n/**\n * CarrierAdapter implementation for MCP _meta format.\n */\nexport class McpCarrierAdapter implements CarrierAdapter<McpResultLike, McpResultLike> {\n extract(input: McpResultLike): { receipts: PeacEvidenceCarrier[]; meta: CarrierMeta } | null {\n return extractReceiptFromMeta(input);\n }\n\n attach(\n output: McpResultLike,\n carriers: PeacEvidenceCarrier[],\n meta?: CarrierMeta\n ): McpResultLike {\n // Attach first carrier (MCP typically carries one receipt per response)\n if (carriers.length > 0) {\n attachReceiptToMeta(output, carriers[0]);\n }\n return output;\n }\n\n validateConstraints(carrier: PeacEvidenceCarrier, meta: CarrierMeta): CarrierValidationResult {\n return validateCarrierConstraints(carrier, meta);\n }\n}\n","/**\n * MCP _meta reserved key guard.\n *\n * Per MCP specification (2025-11-25), the reserved prefix rule is:\n * \"Any prefix where the second label is `modelcontextprotocol` or `mcp`\n * is reserved.\"\n *\n * The prefix is the segment before the first `/`, split by `.` into labels.\n * Only the second label (labels[1], 0-indexed) determines reservation.\n *\n * Examples:\n * \"dev.mcp/anything\" -> RESERVED (2nd label = mcp)\n * \"io.modelcontextprotocol/data\" -> RESERVED (2nd label = modelcontextprotocol)\n * \"com.mcp.tools/data\" -> RESERVED (2nd label = mcp)\n * \"tools.mcp.com/data\" -> RESERVED (2nd label = mcp)\n * \"mcp.dev/anything\" -> NOT reserved (2nd label = dev)\n * \"modelcontextprotocol.io/data\" -> NOT reserved (2nd label = io)\n * \"com.example.mcp/data\" -> NOT reserved (2nd label = example)\n * \"org.peacprotocol/receipt_ref\" -> NOT reserved (2nd label = peacprotocol)\n */\n\nconst MCP_RESERVED_SECOND_LABELS = ['modelcontextprotocol', 'mcp'];\n\n/**\n * Assert that a _meta key does not use an MCP-reserved prefix.\n *\n * A prefix is reserved when its second label (labels[1], 0-indexed)\n * equals \"mcp\" or \"modelcontextprotocol\" (case-insensitive).\n *\n * @throws Error if the key uses a reserved prefix\n */\nexport function assertNotMcpReservedKey(key: string): void {\n const slashIndex = key.indexOf('/');\n if (slashIndex === -1) return; // No prefix = not reserved\n\n const prefix = key.substring(0, slashIndex);\n const labels = prefix.split('.');\n\n if (labels.length < 2) return; // Single label = no second label = not reserved\n\n if (MCP_RESERVED_SECOND_LABELS.includes(labels[1].toLowerCase())) {\n throw new Error(\n `Reserved MCP _meta key prefix: ${key} (second label \"${labels[1]}\" is reserved per MCP spec)`\n );\n }\n}\n\n/**\n * Check if a _meta key uses an MCP-reserved prefix (non-throwing).\n */\nexport function isMcpReservedKey(key: string): boolean {\n try {\n assertNotMcpReservedKey(key);\n return false;\n } catch {\n return true;\n }\n}\n","/**\n * @peac/mappings-mcp\n *\n * Model Context Protocol (MCP) integration for PEAC.\n * Attach and extract PEAC evidence carriers via MCP _meta keys (DD-125).\n */\n\n// Budget enforcement\nexport * from './budget';\n\n// Carrier _meta format (v0.11.1+ Evidence Carrier Contract)\nexport type {\n McpMeta,\n McpResultLike,\n AttachMetaOptions,\n McpExtractResult,\n McpExtractAsyncResult,\n} from './meta';\n\nexport {\n attachReceiptToMeta,\n extractReceiptFromMeta,\n extractReceiptFromMetaAsync,\n McpCarrierAdapter,\n META_KEY_RECEIPT_REF,\n META_KEY_RECEIPT_JWS,\n META_KEY_RECEIPT_URL,\n META_KEY_AGENT_ID,\n META_KEY_VERIFIED_AT,\n META_KEY_LEGACY_RECEIPT,\n MCP_MAX_CARRIER_SIZE,\n} from './meta';\n\n// MCP _meta reserved key guard\nexport { assertNotMcpReservedKey, isMcpReservedKey } from './guard';\n\n// ---------------------------------------------------------------------------\n// Legacy API (backward compatible)\n// ---------------------------------------------------------------------------\n\n/**\n * MCP Tool Response\n */\nexport interface MCPToolResponse {\n tool: string;\n result: unknown;\n metadata?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * MCP Tool Response with PEAC receipt (legacy format)\n */\nexport interface MCPToolResponseWithReceipt extends MCPToolResponse {\n peac_receipt?: string;\n}\n\n/**\n * Attach PEAC receipt to MCP tool response (legacy format).\n *\n * Uses `peac_receipt` top-level key. For new integrations, prefer\n * `attachReceiptToMeta()` which uses the _meta carrier format (DD-125).\n *\n * @param response - MCP tool response\n * @param receiptJWS - PEAC receipt JWS\n * @returns MCP tool response with PEAC receipt attached\n */\nexport function attachReceipt(\n response: MCPToolResponse,\n receiptJWS: string\n): MCPToolResponseWithReceipt {\n return {\n ...response,\n peac_receipt: receiptJWS,\n };\n}\n\n/**\n * Extract PEAC receipt from MCP tool response.\n *\n * Reads BOTH legacy `peac_receipt` field AND `_meta` carrier format (DD-125).\n * Prefers _meta format when both are present.\n *\n * @param response - MCP tool response (possibly with receipt)\n * @returns PEAC receipt JWS or null if not present\n */\nexport function extractReceipt(response: MCPToolResponseWithReceipt): string | null {\n // 1. Try _meta carrier format (v0.11.1+)\n if (response._meta) {\n const meta = response._meta as Record<string, unknown>;\n // New format: receipt_jws key\n const jws = meta['org.peacprotocol/receipt_jws'];\n if (typeof jws === 'string' && jws.length > 0) {\n return jws;\n }\n // Legacy _meta format: receipt key (v0.10.13)\n const legacyJws = meta['org.peacprotocol/receipt'];\n if (typeof legacyJws === 'string' && legacyJws.length > 0) {\n return legacyJws;\n }\n }\n\n // 2. Fall back to legacy peac_receipt field\n if (typeof response.peac_receipt === 'string' && response.peac_receipt.length > 0) {\n return response.peac_receipt;\n }\n return null;\n}\n\n/**\n * Check if MCP tool response has a PEAC receipt\n *\n * @param response - MCP tool response\n * @returns true if receipt is present\n */\nexport function hasReceipt(response: MCPToolResponseWithReceipt): boolean {\n return extractReceipt(response) !== null;\n}\n\n/**\n * Create MCP tool response with PEAC receipt for paid API calls\n *\n * @param tool - Tool name\n * @param result - Tool execution result\n * @param receiptJWS - PEAC receipt JWS\n * @param metadata - Optional metadata\n * @returns MCP tool response with receipt\n */\nexport function createPaidToolResponse(\n tool: string,\n result: unknown,\n receiptJWS: string,\n metadata?: Record<string, unknown>\n): MCPToolResponseWithReceipt {\n const response: MCPToolResponse = {\n tool,\n result,\n ...(metadata && { metadata }),\n };\n\n return attachReceipt(response, receiptJWS);\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,13 @@
1
1
  /**
2
- * Model Context Protocol (MCP) integration
3
- * Attach PEAC receipts to MCP tool responses
2
+ * @peac/mappings-mcp
3
+ *
4
+ * Model Context Protocol (MCP) integration for PEAC.
5
+ * Attach and extract PEAC evidence carriers via MCP _meta keys (DD-125).
4
6
  */
5
7
  export * from './budget';
8
+ export type { McpMeta, McpResultLike, AttachMetaOptions, McpExtractResult, McpExtractAsyncResult, } from './meta';
9
+ export { attachReceiptToMeta, extractReceiptFromMeta, extractReceiptFromMetaAsync, McpCarrierAdapter, META_KEY_RECEIPT_REF, META_KEY_RECEIPT_JWS, META_KEY_RECEIPT_URL, META_KEY_AGENT_ID, META_KEY_VERIFIED_AT, META_KEY_LEGACY_RECEIPT, MCP_MAX_CARRIER_SIZE, } from './meta';
10
+ export { assertNotMcpReservedKey, isMcpReservedKey } from './guard';
6
11
  /**
7
12
  * MCP Tool Response
8
13
  */
@@ -13,13 +18,16 @@ export interface MCPToolResponse {
13
18
  [key: string]: unknown;
14
19
  }
15
20
  /**
16
- * MCP Tool Response with PEAC receipt
21
+ * MCP Tool Response with PEAC receipt (legacy format)
17
22
  */
18
23
  export interface MCPToolResponseWithReceipt extends MCPToolResponse {
19
24
  peac_receipt?: string;
20
25
  }
21
26
  /**
22
- * Attach PEAC receipt to MCP tool response
27
+ * Attach PEAC receipt to MCP tool response (legacy format).
28
+ *
29
+ * Uses `peac_receipt` top-level key. For new integrations, prefer
30
+ * `attachReceiptToMeta()` which uses the _meta carrier format (DD-125).
23
31
  *
24
32
  * @param response - MCP tool response
25
33
  * @param receiptJWS - PEAC receipt JWS
@@ -27,7 +35,10 @@ export interface MCPToolResponseWithReceipt extends MCPToolResponse {
27
35
  */
28
36
  export declare function attachReceipt(response: MCPToolResponse, receiptJWS: string): MCPToolResponseWithReceipt;
29
37
  /**
30
- * Extract PEAC receipt from MCP tool response
38
+ * Extract PEAC receipt from MCP tool response.
39
+ *
40
+ * Reads BOTH legacy `peac_receipt` field AND `_meta` carrier format (DD-125).
41
+ * Prefers _meta format when both are present.
31
42
  *
32
43
  * @param response - MCP tool response (possibly with receipt)
33
44
  * @returns PEAC receipt JWS or null if not present
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC;AAEzB;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA2B,SAAQ,eAAe;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,eAAe,EACzB,UAAU,EAAE,MAAM,GACjB,0BAA0B,CAK5B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,0BAA0B,GAAG,MAAM,GAAG,IAAI,CAKlF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,0BAA0B,GAAG,OAAO,CAExE;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,EACf,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,0BAA0B,CAQ5B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,UAAU,CAAC;AAGzB,YAAY,EACV,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,2BAA2B,EAC3B,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,QAAQ,CAAC;AAGhB,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAMpE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA2B,SAAQ,eAAe;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,eAAe,EACzB,UAAU,EAAE,MAAM,GACjB,0BAA0B,CAK5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,0BAA0B,GAAG,MAAM,GAAG,IAAI,CAqBlF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,0BAA0B,GAAG,OAAO,CAExE;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,EACf,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,0BAA0B,CAQ5B"}
package/dist/index.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ import { CARRIER_TRANSPORT_LIMITS, validateCarrierConstraints, PeacEvidenceCarrierSchema, verifyReceiptRefConsistency, computeReceiptRef } from '@peac/schema';
2
+
1
3
  // src/budget.ts
2
4
  function checkBudget(spentMinor, costMinor, currency, config) {
3
5
  if (currency !== config.currency) {
@@ -63,6 +65,162 @@ function checkBudget(spentMinor, costMinor, currency, config) {
63
65
  remainingMinor
64
66
  };
65
67
  }
68
+ var META_KEY_RECEIPT_REF = "org.peacprotocol/receipt_ref";
69
+ var META_KEY_RECEIPT_JWS = "org.peacprotocol/receipt_jws";
70
+ var META_KEY_AGENT_ID = "org.peacprotocol/agent_id";
71
+ var META_KEY_VERIFIED_AT = "org.peacprotocol/verified_at";
72
+ var META_KEY_RECEIPT_URL = "org.peacprotocol/receipt_url";
73
+ var META_KEY_LEGACY_RECEIPT = "org.peacprotocol/receipt";
74
+ var MCP_MAX_CARRIER_SIZE = CARRIER_TRANSPORT_LIMITS.mcp;
75
+ function attachReceiptToMeta(result, carrier, opts) {
76
+ const meta = {
77
+ transport: "mcp",
78
+ format: "embed",
79
+ max_size: MCP_MAX_CARRIER_SIZE
80
+ };
81
+ const validation = validateCarrierConstraints(carrier, meta);
82
+ if (!validation.valid) {
83
+ throw new Error(`Carrier constraint violation: ${validation.violations.join("; ")}`);
84
+ }
85
+ if (!result._meta) {
86
+ result._meta = {};
87
+ }
88
+ if (opts?.legacyFormat) {
89
+ if (carrier.receipt_jws) {
90
+ result._meta[META_KEY_LEGACY_RECEIPT] = carrier.receipt_jws;
91
+ }
92
+ } else {
93
+ result._meta[META_KEY_RECEIPT_REF] = carrier.receipt_ref;
94
+ if (carrier.receipt_jws) {
95
+ result._meta[META_KEY_RECEIPT_JWS] = carrier.receipt_jws;
96
+ }
97
+ if (carrier.receipt_url) {
98
+ result._meta[META_KEY_RECEIPT_URL] = carrier.receipt_url;
99
+ }
100
+ }
101
+ if (opts?.agentId) {
102
+ result._meta[META_KEY_AGENT_ID] = opts.agentId;
103
+ }
104
+ if (opts?.verifiedAt) {
105
+ result._meta[META_KEY_VERIFIED_AT] = opts.verifiedAt;
106
+ }
107
+ return result;
108
+ }
109
+ function extractReceiptFromMeta(result) {
110
+ if (!result._meta) return null;
111
+ const meta = {
112
+ transport: "mcp",
113
+ format: "embed",
114
+ max_size: MCP_MAX_CARRIER_SIZE
115
+ };
116
+ const receiptRef = result._meta[META_KEY_RECEIPT_REF];
117
+ if (typeof receiptRef === "string") {
118
+ const carrier = { receipt_ref: receiptRef };
119
+ const receiptJws = result._meta[META_KEY_RECEIPT_JWS];
120
+ if (typeof receiptJws === "string") {
121
+ carrier.receipt_jws = receiptJws;
122
+ }
123
+ const receiptUrl = result._meta[META_KEY_RECEIPT_URL];
124
+ if (typeof receiptUrl === "string") {
125
+ carrier.receipt_url = receiptUrl;
126
+ }
127
+ const parsed = PeacEvidenceCarrierSchema.safeParse(carrier);
128
+ if (parsed.success) {
129
+ return { receipts: [parsed.data], meta };
130
+ }
131
+ }
132
+ const legacyReceipt = result._meta[META_KEY_LEGACY_RECEIPT];
133
+ if (typeof legacyReceipt === "string" && legacyReceipt.length > 0) {
134
+ return {
135
+ receipts: [
136
+ {
137
+ receipt_ref: "sha256:0000000000000000000000000000000000000000000000000000000000000000",
138
+ receipt_jws: legacyReceipt
139
+ }
140
+ ],
141
+ meta: { ...meta, redaction: ["legacy_receipt_ref_pending"] }
142
+ };
143
+ }
144
+ return null;
145
+ }
146
+ async function extractReceiptFromMetaAsync(result) {
147
+ if (!result._meta) return null;
148
+ const meta = {
149
+ transport: "mcp",
150
+ format: "embed",
151
+ max_size: MCP_MAX_CARRIER_SIZE
152
+ };
153
+ const receiptRef = result._meta[META_KEY_RECEIPT_REF];
154
+ if (typeof receiptRef === "string") {
155
+ const carrier = { receipt_ref: receiptRef };
156
+ const receiptJws = result._meta[META_KEY_RECEIPT_JWS];
157
+ if (typeof receiptJws === "string") {
158
+ carrier.receipt_jws = receiptJws;
159
+ }
160
+ const receiptUrl = result._meta[META_KEY_RECEIPT_URL];
161
+ if (typeof receiptUrl === "string") {
162
+ carrier.receipt_url = receiptUrl;
163
+ }
164
+ const parsed = PeacEvidenceCarrierSchema.safeParse(carrier);
165
+ if (parsed.success) {
166
+ const validCarrier = parsed.data;
167
+ const error = await verifyReceiptRefConsistency(validCarrier);
168
+ const violations = error ? [error] : [];
169
+ return {
170
+ receipts: error ? [] : [validCarrier],
171
+ meta,
172
+ violations
173
+ };
174
+ }
175
+ }
176
+ const legacyReceipt = result._meta[META_KEY_LEGACY_RECEIPT];
177
+ if (typeof legacyReceipt === "string" && legacyReceipt.length > 0) {
178
+ const computedRef = await computeReceiptRef(legacyReceipt);
179
+ const carrier = {
180
+ receipt_ref: computedRef,
181
+ receipt_jws: legacyReceipt
182
+ };
183
+ return { receipts: [carrier], meta, violations: [] };
184
+ }
185
+ return null;
186
+ }
187
+ var McpCarrierAdapter = class {
188
+ extract(input) {
189
+ return extractReceiptFromMeta(input);
190
+ }
191
+ attach(output, carriers, meta) {
192
+ if (carriers.length > 0) {
193
+ attachReceiptToMeta(output, carriers[0]);
194
+ }
195
+ return output;
196
+ }
197
+ validateConstraints(carrier, meta) {
198
+ return validateCarrierConstraints(carrier, meta);
199
+ }
200
+ };
201
+
202
+ // src/guard.ts
203
+ var MCP_RESERVED_SECOND_LABELS = ["modelcontextprotocol", "mcp"];
204
+ function assertNotMcpReservedKey(key) {
205
+ const slashIndex = key.indexOf("/");
206
+ if (slashIndex === -1) return;
207
+ const prefix = key.substring(0, slashIndex);
208
+ const labels = prefix.split(".");
209
+ if (labels.length < 2) return;
210
+ if (MCP_RESERVED_SECOND_LABELS.includes(labels[1].toLowerCase())) {
211
+ throw new Error(
212
+ `Reserved MCP _meta key prefix: ${key} (second label "${labels[1]}" is reserved per MCP spec)`
213
+ );
214
+ }
215
+ }
216
+ function isMcpReservedKey(key) {
217
+ try {
218
+ assertNotMcpReservedKey(key);
219
+ return false;
220
+ } catch {
221
+ return true;
222
+ }
223
+ }
66
224
 
67
225
  // src/index.ts
68
226
  function attachReceipt(response, receiptJWS) {
@@ -72,6 +230,17 @@ function attachReceipt(response, receiptJWS) {
72
230
  };
73
231
  }
74
232
  function extractReceipt(response) {
233
+ if (response._meta) {
234
+ const meta = response._meta;
235
+ const jws = meta["org.peacprotocol/receipt_jws"];
236
+ if (typeof jws === "string" && jws.length > 0) {
237
+ return jws;
238
+ }
239
+ const legacyJws = meta["org.peacprotocol/receipt"];
240
+ if (typeof legacyJws === "string" && legacyJws.length > 0) {
241
+ return legacyJws;
242
+ }
243
+ }
75
244
  if (typeof response.peac_receipt === "string" && response.peac_receipt.length > 0) {
76
245
  return response.peac_receipt;
77
246
  }
@@ -89,6 +258,6 @@ function createPaidToolResponse(tool, result, receiptJWS, metadata) {
89
258
  return attachReceipt(response, receiptJWS);
90
259
  }
91
260
 
92
- export { attachReceipt, checkBudget, createPaidToolResponse, extractReceipt, hasReceipt };
261
+ export { MCP_MAX_CARRIER_SIZE, META_KEY_AGENT_ID, META_KEY_LEGACY_RECEIPT, META_KEY_RECEIPT_JWS, META_KEY_RECEIPT_REF, META_KEY_RECEIPT_URL, META_KEY_VERIFIED_AT, McpCarrierAdapter, assertNotMcpReservedKey, attachReceipt, attachReceiptToMeta, checkBudget, createPaidToolResponse, extractReceipt, extractReceiptFromMeta, extractReceiptFromMetaAsync, hasReceipt, isMcpReservedKey };
93
262
  //# sourceMappingURL=index.mjs.map
94
263
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/budget.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Budget enforcement utilities for MCP tool calls\n * Uses bigint for all currency calculations to avoid floating-point errors\n */\n\nexport interface BudgetConfig {\n /** Maximum cost per single call in minor currency units (e.g., cents for USD) */\n maxPerCallMinor?: bigint;\n /** Maximum total cost per day in minor currency units */\n maxDailyMinor?: bigint;\n /** Maximum total cost per month in minor currency units */\n maxMonthlyMinor?: bigint;\n /** Currency code (must match at runtime, e.g., 'USD', 'INR') */\n currency: string;\n}\n\nexport interface BudgetResult {\n /** Whether the cost is allowed within budget limits */\n allowed: boolean;\n /**\n * Remaining budget in minor units (after this operation).\n * - When allowed: the minimum remaining across all applicable limits\n * - When denied: 0n (never negative)\n * - When unbounded: undefined (check `unbounded` flag instead)\n */\n remainingMinor?: bigint;\n /**\n * True when no budget limits are configured.\n * In this case, remainingMinor is undefined.\n */\n unbounded?: boolean;\n /** Error code if not allowed */\n code?: 'budget_exceeded' | 'currency_mismatch';\n /** Human-readable reason if not allowed */\n reason?: string;\n}\n\n/**\n * Check if a cost is within budget.\n * Pure, deterministic function - caller tracks time windows externally.\n *\n * @param spentMinor - Amount already spent in minor units (e.g., cents)\n * @param costMinor - Cost of current operation in minor units\n * @param currency - Currency code (must match config.currency)\n * @param config - Budget configuration\n * @returns Result indicating whether cost is allowed and remaining budget\n *\n * @example\n * ```typescript\n * const config: BudgetConfig = {\n * maxPerCallMinor: 500n, // $5.00 max per call\n * maxDailyMinor: 10000n, // $100.00 max per day\n * currency: 'USD'\n * };\n *\n * const result = checkBudget(\n * 8500n, // Already spent $85.00 today\n * 1000n, // Current call costs $10.00\n * 'USD',\n * config\n * );\n *\n * if (result.allowed) {\n * console.log(`Allowed. Remaining: ${result.remainingMinor} cents`);\n * } else {\n * console.log(`Denied: ${result.reason}`);\n * }\n * ```\n */\nexport function checkBudget(\n spentMinor: bigint,\n costMinor: bigint,\n currency: string,\n config: BudgetConfig\n): BudgetResult {\n // Currency mismatch check\n if (currency !== config.currency) {\n return {\n allowed: false,\n remainingMinor: 0n,\n code: 'currency_mismatch',\n reason: `Currency mismatch: expected ${config.currency}, got ${currency}`,\n };\n }\n\n // Check per-call limit\n if (config.maxPerCallMinor !== undefined && costMinor > config.maxPerCallMinor) {\n return {\n allowed: false,\n remainingMinor: 0n, // Never negative - clamp to 0\n code: 'budget_exceeded',\n reason: `Per-call limit exceeded: cost ${costMinor} > limit ${config.maxPerCallMinor}`,\n };\n }\n\n // Check daily limit (if configured)\n if (config.maxDailyMinor !== undefined) {\n const newTotal = spentMinor + costMinor;\n if (newTotal > config.maxDailyMinor) {\n return {\n allowed: false,\n remainingMinor: config.maxDailyMinor - spentMinor,\n code: 'budget_exceeded',\n reason: `Daily limit exceeded: total ${newTotal} > limit ${config.maxDailyMinor}`,\n };\n }\n }\n\n // Check monthly limit (if configured)\n if (config.maxMonthlyMinor !== undefined) {\n const newTotal = spentMinor + costMinor;\n if (newTotal > config.maxMonthlyMinor) {\n return {\n allowed: false,\n remainingMinor: config.maxMonthlyMinor - spentMinor,\n code: 'budget_exceeded',\n reason: `Monthly limit exceeded: total ${newTotal} > limit ${config.maxMonthlyMinor}`,\n };\n }\n }\n\n // Calculate remaining budget (how much can be spent in future operations)\n // This is the minimum of:\n // 1. Per-call limit (max single operation)\n // 2. Remaining daily budget\n // 3. Remaining monthly budget\n const remainingCandidates: bigint[] = [];\n\n if (config.maxPerCallMinor !== undefined) {\n remainingCandidates.push(config.maxPerCallMinor);\n }\n\n if (config.maxDailyMinor !== undefined) {\n const dailyRemaining = config.maxDailyMinor - (spentMinor + costMinor);\n remainingCandidates.push(dailyRemaining);\n }\n\n if (config.maxMonthlyMinor !== undefined) {\n const monthlyRemaining = config.maxMonthlyMinor - (spentMinor + costMinor);\n remainingCandidates.push(monthlyRemaining);\n }\n\n // Return the most restrictive limit\n if (remainingCandidates.length === 0) {\n // No limits configured - unbounded\n return {\n allowed: true,\n unbounded: true,\n };\n }\n\n const remainingMinor = remainingCandidates.reduce((min, val) => (val < min ? val : min));\n\n return {\n allowed: true,\n remainingMinor,\n };\n}\n","/**\n * Model Context Protocol (MCP) integration\n * Attach PEAC receipts to MCP tool responses\n */\n\nexport * from './budget';\n\n/**\n * MCP Tool Response\n */\nexport interface MCPToolResponse {\n tool: string;\n result: unknown;\n metadata?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * MCP Tool Response with PEAC receipt\n */\nexport interface MCPToolResponseWithReceipt extends MCPToolResponse {\n peac_receipt?: string;\n}\n\n/**\n * Attach PEAC receipt to MCP tool response\n *\n * @param response - MCP tool response\n * @param receiptJWS - PEAC receipt JWS\n * @returns MCP tool response with PEAC receipt attached\n */\nexport function attachReceipt(\n response: MCPToolResponse,\n receiptJWS: string\n): MCPToolResponseWithReceipt {\n return {\n ...response,\n peac_receipt: receiptJWS,\n };\n}\n\n/**\n * Extract PEAC receipt from MCP tool response\n *\n * @param response - MCP tool response (possibly with receipt)\n * @returns PEAC receipt JWS or null if not present\n */\nexport function extractReceipt(response: MCPToolResponseWithReceipt): string | null {\n if (typeof response.peac_receipt === 'string' && response.peac_receipt.length > 0) {\n return response.peac_receipt;\n }\n return null;\n}\n\n/**\n * Check if MCP tool response has a PEAC receipt\n *\n * @param response - MCP tool response\n * @returns true if receipt is present\n */\nexport function hasReceipt(response: MCPToolResponseWithReceipt): boolean {\n return extractReceipt(response) !== null;\n}\n\n/**\n * Create MCP tool response with PEAC receipt for paid API calls\n *\n * @param tool - Tool name\n * @param result - Tool execution result\n * @param receiptJWS - PEAC receipt JWS\n * @param metadata - Optional metadata\n * @returns MCP tool response with receipt\n */\nexport function createPaidToolResponse(\n tool: string,\n result: unknown,\n receiptJWS: string,\n metadata?: Record<string, unknown>\n): MCPToolResponseWithReceipt {\n const response: MCPToolResponse = {\n tool,\n result,\n ...(metadata && { metadata }),\n };\n\n return attachReceipt(response, receiptJWS);\n}\n"]}
1
+ {"version":3,"sources":["../src/budget.ts","../src/meta.ts","../src/guard.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Budget enforcement utilities for MCP tool calls\n * Uses bigint for all currency calculations to avoid floating-point errors\n */\n\nexport interface BudgetConfig {\n /** Maximum cost per single call in minor currency units (e.g., cents for USD) */\n maxPerCallMinor?: bigint;\n /** Maximum total cost per day in minor currency units */\n maxDailyMinor?: bigint;\n /** Maximum total cost per month in minor currency units */\n maxMonthlyMinor?: bigint;\n /** Currency code (must match at runtime, e.g., 'USD', 'INR') */\n currency: string;\n}\n\nexport interface BudgetResult {\n /** Whether the cost is allowed within budget limits */\n allowed: boolean;\n /**\n * Remaining budget in minor units (after this operation).\n * - When allowed: the minimum remaining across all applicable limits\n * - When denied: 0n (never negative)\n * - When unbounded: undefined (check `unbounded` flag instead)\n */\n remainingMinor?: bigint;\n /**\n * True when no budget limits are configured.\n * In this case, remainingMinor is undefined.\n */\n unbounded?: boolean;\n /** Error code if not allowed */\n code?: 'budget_exceeded' | 'currency_mismatch';\n /** Human-readable reason if not allowed */\n reason?: string;\n}\n\n/**\n * Check if a cost is within budget.\n * Pure, deterministic function - caller tracks time windows externally.\n *\n * @param spentMinor - Amount already spent in minor units (e.g., cents)\n * @param costMinor - Cost of current operation in minor units\n * @param currency - Currency code (must match config.currency)\n * @param config - Budget configuration\n * @returns Result indicating whether cost is allowed and remaining budget\n *\n * @example\n * ```typescript\n * const config: BudgetConfig = {\n * maxPerCallMinor: 500n, // $5.00 max per call\n * maxDailyMinor: 10000n, // $100.00 max per day\n * currency: 'USD'\n * };\n *\n * const result = checkBudget(\n * 8500n, // Already spent $85.00 today\n * 1000n, // Current call costs $10.00\n * 'USD',\n * config\n * );\n *\n * if (result.allowed) {\n * console.log(`Allowed. Remaining: ${result.remainingMinor} cents`);\n * } else {\n * console.log(`Denied: ${result.reason}`);\n * }\n * ```\n */\nexport function checkBudget(\n spentMinor: bigint,\n costMinor: bigint,\n currency: string,\n config: BudgetConfig\n): BudgetResult {\n // Currency mismatch check\n if (currency !== config.currency) {\n return {\n allowed: false,\n remainingMinor: 0n,\n code: 'currency_mismatch',\n reason: `Currency mismatch: expected ${config.currency}, got ${currency}`,\n };\n }\n\n // Check per-call limit\n if (config.maxPerCallMinor !== undefined && costMinor > config.maxPerCallMinor) {\n return {\n allowed: false,\n remainingMinor: 0n, // Never negative - clamp to 0\n code: 'budget_exceeded',\n reason: `Per-call limit exceeded: cost ${costMinor} > limit ${config.maxPerCallMinor}`,\n };\n }\n\n // Check daily limit (if configured)\n if (config.maxDailyMinor !== undefined) {\n const newTotal = spentMinor + costMinor;\n if (newTotal > config.maxDailyMinor) {\n return {\n allowed: false,\n remainingMinor: config.maxDailyMinor - spentMinor,\n code: 'budget_exceeded',\n reason: `Daily limit exceeded: total ${newTotal} > limit ${config.maxDailyMinor}`,\n };\n }\n }\n\n // Check monthly limit (if configured)\n if (config.maxMonthlyMinor !== undefined) {\n const newTotal = spentMinor + costMinor;\n if (newTotal > config.maxMonthlyMinor) {\n return {\n allowed: false,\n remainingMinor: config.maxMonthlyMinor - spentMinor,\n code: 'budget_exceeded',\n reason: `Monthly limit exceeded: total ${newTotal} > limit ${config.maxMonthlyMinor}`,\n };\n }\n }\n\n // Calculate remaining budget (how much can be spent in future operations)\n // This is the minimum of:\n // 1. Per-call limit (max single operation)\n // 2. Remaining daily budget\n // 3. Remaining monthly budget\n const remainingCandidates: bigint[] = [];\n\n if (config.maxPerCallMinor !== undefined) {\n remainingCandidates.push(config.maxPerCallMinor);\n }\n\n if (config.maxDailyMinor !== undefined) {\n const dailyRemaining = config.maxDailyMinor - (spentMinor + costMinor);\n remainingCandidates.push(dailyRemaining);\n }\n\n if (config.maxMonthlyMinor !== undefined) {\n const monthlyRemaining = config.maxMonthlyMinor - (spentMinor + costMinor);\n remainingCandidates.push(monthlyRemaining);\n }\n\n // Return the most restrictive limit\n if (remainingCandidates.length === 0) {\n // No limits configured - unbounded\n return {\n allowed: true,\n unbounded: true,\n };\n }\n\n const remainingMinor = remainingCandidates.reduce((min, val) => (val < min ? val : min));\n\n return {\n allowed: true,\n remainingMinor,\n };\n}\n","/**\n * MCP _meta carrier format (DD-125).\n *\n * Attach and extract PEAC evidence carriers using MCP _meta keys.\n * Uses reverse-DNS keys under \"org.peacprotocol/\" prefix.\n *\n * New format (v0.11.1+):\n * _meta[\"org.peacprotocol/receipt_ref\"] = \"sha256:...\"\n * _meta[\"org.peacprotocol/receipt_jws\"] = \"eyJ...\"\n *\n * Legacy format (v0.10.13):\n * _meta[\"org.peacprotocol/receipt\"] = \"eyJ...\" (JWS only, no receipt_ref)\n */\n\nimport type {\n PeacEvidenceCarrier,\n CarrierMeta,\n CarrierValidationResult,\n CarrierAdapter,\n} from '@peac/kernel';\nimport {\n PeacEvidenceCarrierSchema,\n computeReceiptRef,\n validateCarrierConstraints,\n verifyReceiptRefConsistency,\n CARRIER_TRANSPORT_LIMITS,\n} from '@peac/schema';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** _meta key for receipt reference (v0.11.1+) */\nexport const META_KEY_RECEIPT_REF = 'org.peacprotocol/receipt_ref' as const;\n\n/** _meta key for receipt JWS (v0.11.1+) */\nexport const META_KEY_RECEIPT_JWS = 'org.peacprotocol/receipt_jws' as const;\n\n/** _meta key for agent ID (v0.10.13+, unchanged) */\nexport const META_KEY_AGENT_ID = 'org.peacprotocol/agent_id' as const;\n\n/** _meta key for verification timestamp (v0.10.13+, unchanged) */\nexport const META_KEY_VERIFIED_AT = 'org.peacprotocol/verified_at' as const;\n\n/** _meta key for receipt URL locator hint (v0.11.2+, DD-135) */\nexport const META_KEY_RECEIPT_URL = 'org.peacprotocol/receipt_url' as const;\n\n/** Legacy _meta key for receipt JWS (v0.10.13, DD-125) */\nexport const META_KEY_LEGACY_RECEIPT = 'org.peacprotocol/receipt' as const;\n\n/** Maximum carrier size for MCP _meta (64 KB, DD-127) */\nexport const MCP_MAX_CARRIER_SIZE = CARRIER_TRANSPORT_LIMITS.mcp;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** MCP _meta object */\nexport type McpMeta = Record<string, unknown>;\n\n/** MCP JSON-RPC result with _meta */\nexport interface McpResultLike {\n _meta?: McpMeta;\n [key: string]: unknown;\n}\n\n/** Options for attachReceiptToMeta */\nexport interface AttachMetaOptions {\n /** Use legacy format (v0.10.13 peac_receipt key). Default: false */\n legacyFormat?: boolean;\n /** Agent ID to include in _meta */\n agentId?: string;\n /** Verification timestamp (ISO 8601) */\n verifiedAt?: string;\n}\n\n/** Result of extraction */\nexport interface McpExtractResult {\n receipts: PeacEvidenceCarrier[];\n meta: CarrierMeta;\n}\n\n/** Async extraction result with consistency violations */\nexport interface McpExtractAsyncResult extends McpExtractResult {\n violations: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Attach\n// ---------------------------------------------------------------------------\n\n/**\n * Attach PEAC evidence carrier to MCP _meta.\n *\n * Default format (v0.11.1+): writes receipt_ref and receipt_jws as separate keys.\n * Legacy format (opts.legacyFormat): writes single \"receipt\" key (DD-125 phase 1).\n */\nexport function attachReceiptToMeta(\n result: McpResultLike,\n carrier: PeacEvidenceCarrier,\n opts?: AttachMetaOptions\n): McpResultLike {\n const meta: CarrierMeta = {\n transport: 'mcp',\n format: 'embed',\n max_size: MCP_MAX_CARRIER_SIZE,\n };\n\n const validation = validateCarrierConstraints(carrier, meta);\n if (!validation.valid) {\n throw new Error(`Carrier constraint violation: ${validation.violations.join('; ')}`);\n }\n\n if (!result._meta) {\n result._meta = {};\n }\n\n if (opts?.legacyFormat) {\n // DD-125 legacy format: single JWS string\n if (carrier.receipt_jws) {\n result._meta[META_KEY_LEGACY_RECEIPT] = carrier.receipt_jws;\n }\n } else {\n // v0.11.1+ carrier format\n result._meta[META_KEY_RECEIPT_REF] = carrier.receipt_ref;\n if (carrier.receipt_jws) {\n result._meta[META_KEY_RECEIPT_JWS] = carrier.receipt_jws;\n }\n if (carrier.receipt_url) {\n result._meta[META_KEY_RECEIPT_URL] = carrier.receipt_url;\n }\n }\n\n // Optional metadata fields (unchanged from v0.10.13)\n if (opts?.agentId) {\n result._meta[META_KEY_AGENT_ID] = opts.agentId;\n }\n if (opts?.verifiedAt) {\n result._meta[META_KEY_VERIFIED_AT] = opts.verifiedAt;\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Extract (sync: structural validation only)\n// ---------------------------------------------------------------------------\n\n/**\n * Extract PEAC evidence carrier from MCP _meta (sync).\n *\n * Reads in order (DD-125):\n * 1. New carrier keys (receipt_ref + receipt_jws)\n * 2. Legacy key (org.peacprotocol/receipt, v0.10.13)\n *\n * For legacy format, computes receipt_ref from the JWS to return a proper\n * PeacEvidenceCarrier (Polish B). Note: this requires async for the hash,\n * so the sync version uses a placeholder receipt_ref for legacy and marks\n * it in the meta. Use extractReceiptFromMetaAsync for full validation.\n */\nexport function extractReceiptFromMeta(result: McpResultLike): McpExtractResult | null {\n if (!result._meta) return null;\n\n const meta: CarrierMeta = {\n transport: 'mcp',\n format: 'embed',\n max_size: MCP_MAX_CARRIER_SIZE,\n };\n\n // 1. Try new format (v0.11.1+)\n const receiptRef = result._meta[META_KEY_RECEIPT_REF];\n if (typeof receiptRef === 'string') {\n const carrier: Record<string, unknown> = { receipt_ref: receiptRef };\n const receiptJws = result._meta[META_KEY_RECEIPT_JWS];\n if (typeof receiptJws === 'string') {\n carrier.receipt_jws = receiptJws;\n }\n const receiptUrl = result._meta[META_KEY_RECEIPT_URL];\n if (typeof receiptUrl === 'string') {\n carrier.receipt_url = receiptUrl;\n }\n\n const parsed = PeacEvidenceCarrierSchema.safeParse(carrier);\n if (parsed.success) {\n return { receipts: [parsed.data as PeacEvidenceCarrier], meta };\n }\n }\n\n // 2. Try legacy format (v0.10.13, DD-125 + Polish B)\n const legacyReceipt = result._meta[META_KEY_LEGACY_RECEIPT];\n if (typeof legacyReceipt === 'string' && legacyReceipt.length > 0) {\n // Legacy format has JWS only; we can't compute receipt_ref synchronously.\n // Return the JWS in a carrier with a placeholder ref that extractAsync will fix.\n // For sync extraction, we skip schema validation of receipt_ref since we can't\n // compute it without async. Return the raw data and let async handle integrity.\n return {\n receipts: [\n {\n receipt_ref:\n 'sha256:0000000000000000000000000000000000000000000000000000000000000000' as PeacEvidenceCarrier['receipt_ref'],\n receipt_jws: legacyReceipt,\n },\n ],\n meta: { ...meta, redaction: ['legacy_receipt_ref_pending'] },\n };\n }\n\n return null;\n}\n\n/**\n * Extract PEAC evidence carrier from MCP _meta (async, DD-129).\n *\n * Performs structural validation AND receipt_ref consistency check.\n * For legacy format (v0.10.13), computes receipt_ref from JWS (Polish B).\n */\nexport async function extractReceiptFromMetaAsync(\n result: McpResultLike\n): Promise<McpExtractAsyncResult | null> {\n if (!result._meta) return null;\n\n const meta: CarrierMeta = {\n transport: 'mcp',\n format: 'embed',\n max_size: MCP_MAX_CARRIER_SIZE,\n };\n\n // 1. Try new format (v0.11.1+)\n const receiptRef = result._meta[META_KEY_RECEIPT_REF];\n if (typeof receiptRef === 'string') {\n const carrier: Record<string, unknown> = { receipt_ref: receiptRef };\n const receiptJws = result._meta[META_KEY_RECEIPT_JWS];\n if (typeof receiptJws === 'string') {\n carrier.receipt_jws = receiptJws;\n }\n const receiptUrl = result._meta[META_KEY_RECEIPT_URL];\n if (typeof receiptUrl === 'string') {\n carrier.receipt_url = receiptUrl;\n }\n\n const parsed = PeacEvidenceCarrierSchema.safeParse(carrier);\n if (parsed.success) {\n const validCarrier = parsed.data as PeacEvidenceCarrier;\n const error = await verifyReceiptRefConsistency(validCarrier);\n const violations = error ? [error] : [];\n return {\n receipts: error ? [] : [validCarrier],\n meta,\n violations,\n };\n }\n }\n\n // 2. Try legacy format (v0.10.13, DD-125 + Polish B)\n const legacyReceipt = result._meta[META_KEY_LEGACY_RECEIPT];\n if (typeof legacyReceipt === 'string' && legacyReceipt.length > 0) {\n // Compute receipt_ref from legacy JWS\n const computedRef = await computeReceiptRef(legacyReceipt);\n const carrier: PeacEvidenceCarrier = {\n receipt_ref: computedRef,\n receipt_jws: legacyReceipt,\n };\n return { receipts: [carrier], meta, violations: [] };\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// McpCarrierAdapter\n// ---------------------------------------------------------------------------\n\n/**\n * CarrierAdapter implementation for MCP _meta format.\n */\nexport class McpCarrierAdapter implements CarrierAdapter<McpResultLike, McpResultLike> {\n extract(input: McpResultLike): { receipts: PeacEvidenceCarrier[]; meta: CarrierMeta } | null {\n return extractReceiptFromMeta(input);\n }\n\n attach(\n output: McpResultLike,\n carriers: PeacEvidenceCarrier[],\n meta?: CarrierMeta\n ): McpResultLike {\n // Attach first carrier (MCP typically carries one receipt per response)\n if (carriers.length > 0) {\n attachReceiptToMeta(output, carriers[0]);\n }\n return output;\n }\n\n validateConstraints(carrier: PeacEvidenceCarrier, meta: CarrierMeta): CarrierValidationResult {\n return validateCarrierConstraints(carrier, meta);\n }\n}\n","/**\n * MCP _meta reserved key guard.\n *\n * Per MCP specification (2025-11-25), the reserved prefix rule is:\n * \"Any prefix where the second label is `modelcontextprotocol` or `mcp`\n * is reserved.\"\n *\n * The prefix is the segment before the first `/`, split by `.` into labels.\n * Only the second label (labels[1], 0-indexed) determines reservation.\n *\n * Examples:\n * \"dev.mcp/anything\" -> RESERVED (2nd label = mcp)\n * \"io.modelcontextprotocol/data\" -> RESERVED (2nd label = modelcontextprotocol)\n * \"com.mcp.tools/data\" -> RESERVED (2nd label = mcp)\n * \"tools.mcp.com/data\" -> RESERVED (2nd label = mcp)\n * \"mcp.dev/anything\" -> NOT reserved (2nd label = dev)\n * \"modelcontextprotocol.io/data\" -> NOT reserved (2nd label = io)\n * \"com.example.mcp/data\" -> NOT reserved (2nd label = example)\n * \"org.peacprotocol/receipt_ref\" -> NOT reserved (2nd label = peacprotocol)\n */\n\nconst MCP_RESERVED_SECOND_LABELS = ['modelcontextprotocol', 'mcp'];\n\n/**\n * Assert that a _meta key does not use an MCP-reserved prefix.\n *\n * A prefix is reserved when its second label (labels[1], 0-indexed)\n * equals \"mcp\" or \"modelcontextprotocol\" (case-insensitive).\n *\n * @throws Error if the key uses a reserved prefix\n */\nexport function assertNotMcpReservedKey(key: string): void {\n const slashIndex = key.indexOf('/');\n if (slashIndex === -1) return; // No prefix = not reserved\n\n const prefix = key.substring(0, slashIndex);\n const labels = prefix.split('.');\n\n if (labels.length < 2) return; // Single label = no second label = not reserved\n\n if (MCP_RESERVED_SECOND_LABELS.includes(labels[1].toLowerCase())) {\n throw new Error(\n `Reserved MCP _meta key prefix: ${key} (second label \"${labels[1]}\" is reserved per MCP spec)`\n );\n }\n}\n\n/**\n * Check if a _meta key uses an MCP-reserved prefix (non-throwing).\n */\nexport function isMcpReservedKey(key: string): boolean {\n try {\n assertNotMcpReservedKey(key);\n return false;\n } catch {\n return true;\n }\n}\n","/**\n * @peac/mappings-mcp\n *\n * Model Context Protocol (MCP) integration for PEAC.\n * Attach and extract PEAC evidence carriers via MCP _meta keys (DD-125).\n */\n\n// Budget enforcement\nexport * from './budget';\n\n// Carrier _meta format (v0.11.1+ Evidence Carrier Contract)\nexport type {\n McpMeta,\n McpResultLike,\n AttachMetaOptions,\n McpExtractResult,\n McpExtractAsyncResult,\n} from './meta';\n\nexport {\n attachReceiptToMeta,\n extractReceiptFromMeta,\n extractReceiptFromMetaAsync,\n McpCarrierAdapter,\n META_KEY_RECEIPT_REF,\n META_KEY_RECEIPT_JWS,\n META_KEY_RECEIPT_URL,\n META_KEY_AGENT_ID,\n META_KEY_VERIFIED_AT,\n META_KEY_LEGACY_RECEIPT,\n MCP_MAX_CARRIER_SIZE,\n} from './meta';\n\n// MCP _meta reserved key guard\nexport { assertNotMcpReservedKey, isMcpReservedKey } from './guard';\n\n// ---------------------------------------------------------------------------\n// Legacy API (backward compatible)\n// ---------------------------------------------------------------------------\n\n/**\n * MCP Tool Response\n */\nexport interface MCPToolResponse {\n tool: string;\n result: unknown;\n metadata?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * MCP Tool Response with PEAC receipt (legacy format)\n */\nexport interface MCPToolResponseWithReceipt extends MCPToolResponse {\n peac_receipt?: string;\n}\n\n/**\n * Attach PEAC receipt to MCP tool response (legacy format).\n *\n * Uses `peac_receipt` top-level key. For new integrations, prefer\n * `attachReceiptToMeta()` which uses the _meta carrier format (DD-125).\n *\n * @param response - MCP tool response\n * @param receiptJWS - PEAC receipt JWS\n * @returns MCP tool response with PEAC receipt attached\n */\nexport function attachReceipt(\n response: MCPToolResponse,\n receiptJWS: string\n): MCPToolResponseWithReceipt {\n return {\n ...response,\n peac_receipt: receiptJWS,\n };\n}\n\n/**\n * Extract PEAC receipt from MCP tool response.\n *\n * Reads BOTH legacy `peac_receipt` field AND `_meta` carrier format (DD-125).\n * Prefers _meta format when both are present.\n *\n * @param response - MCP tool response (possibly with receipt)\n * @returns PEAC receipt JWS or null if not present\n */\nexport function extractReceipt(response: MCPToolResponseWithReceipt): string | null {\n // 1. Try _meta carrier format (v0.11.1+)\n if (response._meta) {\n const meta = response._meta as Record<string, unknown>;\n // New format: receipt_jws key\n const jws = meta['org.peacprotocol/receipt_jws'];\n if (typeof jws === 'string' && jws.length > 0) {\n return jws;\n }\n // Legacy _meta format: receipt key (v0.10.13)\n const legacyJws = meta['org.peacprotocol/receipt'];\n if (typeof legacyJws === 'string' && legacyJws.length > 0) {\n return legacyJws;\n }\n }\n\n // 2. Fall back to legacy peac_receipt field\n if (typeof response.peac_receipt === 'string' && response.peac_receipt.length > 0) {\n return response.peac_receipt;\n }\n return null;\n}\n\n/**\n * Check if MCP tool response has a PEAC receipt\n *\n * @param response - MCP tool response\n * @returns true if receipt is present\n */\nexport function hasReceipt(response: MCPToolResponseWithReceipt): boolean {\n return extractReceipt(response) !== null;\n}\n\n/**\n * Create MCP tool response with PEAC receipt for paid API calls\n *\n * @param tool - Tool name\n * @param result - Tool execution result\n * @param receiptJWS - PEAC receipt JWS\n * @param metadata - Optional metadata\n * @returns MCP tool response with receipt\n */\nexport function createPaidToolResponse(\n tool: string,\n result: unknown,\n receiptJWS: string,\n metadata?: Record<string, unknown>\n): MCPToolResponseWithReceipt {\n const response: MCPToolResponse = {\n tool,\n result,\n ...(metadata && { metadata }),\n };\n\n return attachReceipt(response, receiptJWS);\n}\n"]}
package/dist/meta.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * MCP _meta carrier format (DD-125).
3
+ *
4
+ * Attach and extract PEAC evidence carriers using MCP _meta keys.
5
+ * Uses reverse-DNS keys under "org.peacprotocol/" prefix.
6
+ *
7
+ * New format (v0.11.1+):
8
+ * _meta["org.peacprotocol/receipt_ref"] = "sha256:..."
9
+ * _meta["org.peacprotocol/receipt_jws"] = "eyJ..."
10
+ *
11
+ * Legacy format (v0.10.13):
12
+ * _meta["org.peacprotocol/receipt"] = "eyJ..." (JWS only, no receipt_ref)
13
+ */
14
+ import type { PeacEvidenceCarrier, CarrierMeta, CarrierValidationResult, CarrierAdapter } from '@peac/kernel';
15
+ /** _meta key for receipt reference (v0.11.1+) */
16
+ export declare const META_KEY_RECEIPT_REF: "org.peacprotocol/receipt_ref";
17
+ /** _meta key for receipt JWS (v0.11.1+) */
18
+ export declare const META_KEY_RECEIPT_JWS: "org.peacprotocol/receipt_jws";
19
+ /** _meta key for agent ID (v0.10.13+, unchanged) */
20
+ export declare const META_KEY_AGENT_ID: "org.peacprotocol/agent_id";
21
+ /** _meta key for verification timestamp (v0.10.13+, unchanged) */
22
+ export declare const META_KEY_VERIFIED_AT: "org.peacprotocol/verified_at";
23
+ /** _meta key for receipt URL locator hint (v0.11.2+, DD-135) */
24
+ export declare const META_KEY_RECEIPT_URL: "org.peacprotocol/receipt_url";
25
+ /** Legacy _meta key for receipt JWS (v0.10.13, DD-125) */
26
+ export declare const META_KEY_LEGACY_RECEIPT: "org.peacprotocol/receipt";
27
+ /** Maximum carrier size for MCP _meta (64 KB, DD-127) */
28
+ export declare const MCP_MAX_CARRIER_SIZE: 65536;
29
+ /** MCP _meta object */
30
+ export type McpMeta = Record<string, unknown>;
31
+ /** MCP JSON-RPC result with _meta */
32
+ export interface McpResultLike {
33
+ _meta?: McpMeta;
34
+ [key: string]: unknown;
35
+ }
36
+ /** Options for attachReceiptToMeta */
37
+ export interface AttachMetaOptions {
38
+ /** Use legacy format (v0.10.13 peac_receipt key). Default: false */
39
+ legacyFormat?: boolean;
40
+ /** Agent ID to include in _meta */
41
+ agentId?: string;
42
+ /** Verification timestamp (ISO 8601) */
43
+ verifiedAt?: string;
44
+ }
45
+ /** Result of extraction */
46
+ export interface McpExtractResult {
47
+ receipts: PeacEvidenceCarrier[];
48
+ meta: CarrierMeta;
49
+ }
50
+ /** Async extraction result with consistency violations */
51
+ export interface McpExtractAsyncResult extends McpExtractResult {
52
+ violations: string[];
53
+ }
54
+ /**
55
+ * Attach PEAC evidence carrier to MCP _meta.
56
+ *
57
+ * Default format (v0.11.1+): writes receipt_ref and receipt_jws as separate keys.
58
+ * Legacy format (opts.legacyFormat): writes single "receipt" key (DD-125 phase 1).
59
+ */
60
+ export declare function attachReceiptToMeta(result: McpResultLike, carrier: PeacEvidenceCarrier, opts?: AttachMetaOptions): McpResultLike;
61
+ /**
62
+ * Extract PEAC evidence carrier from MCP _meta (sync).
63
+ *
64
+ * Reads in order (DD-125):
65
+ * 1. New carrier keys (receipt_ref + receipt_jws)
66
+ * 2. Legacy key (org.peacprotocol/receipt, v0.10.13)
67
+ *
68
+ * For legacy format, computes receipt_ref from the JWS to return a proper
69
+ * PeacEvidenceCarrier (Polish B). Note: this requires async for the hash,
70
+ * so the sync version uses a placeholder receipt_ref for legacy and marks
71
+ * it in the meta. Use extractReceiptFromMetaAsync for full validation.
72
+ */
73
+ export declare function extractReceiptFromMeta(result: McpResultLike): McpExtractResult | null;
74
+ /**
75
+ * Extract PEAC evidence carrier from MCP _meta (async, DD-129).
76
+ *
77
+ * Performs structural validation AND receipt_ref consistency check.
78
+ * For legacy format (v0.10.13), computes receipt_ref from JWS (Polish B).
79
+ */
80
+ export declare function extractReceiptFromMetaAsync(result: McpResultLike): Promise<McpExtractAsyncResult | null>;
81
+ /**
82
+ * CarrierAdapter implementation for MCP _meta format.
83
+ */
84
+ export declare class McpCarrierAdapter implements CarrierAdapter<McpResultLike, McpResultLike> {
85
+ extract(input: McpResultLike): {
86
+ receipts: PeacEvidenceCarrier[];
87
+ meta: CarrierMeta;
88
+ } | null;
89
+ attach(output: McpResultLike, carriers: PeacEvidenceCarrier[], meta?: CarrierMeta): McpResultLike;
90
+ validateConstraints(carrier: PeacEvidenceCarrier, meta: CarrierMeta): CarrierValidationResult;
91
+ }
92
+ //# sourceMappingURL=meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../src/meta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,WAAW,EACX,uBAAuB,EACvB,cAAc,EACf,MAAM,cAAc,CAAC;AAatB,iDAAiD;AACjD,eAAO,MAAM,oBAAoB,EAAG,8BAAuC,CAAC;AAE5E,2CAA2C;AAC3C,eAAO,MAAM,oBAAoB,EAAG,8BAAuC,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,iBAAiB,EAAG,2BAAoC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,oBAAoB,EAAG,8BAAuC,CAAC;AAE5E,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,EAAG,8BAAuC,CAAC;AAE5E,0DAA0D;AAC1D,eAAO,MAAM,uBAAuB,EAAG,0BAAmC,CAAC;AAE3E,yDAAyD;AACzD,eAAO,MAAM,oBAAoB,OAA+B,CAAC;AAMjE,uBAAuB;AACvB,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE9C,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,2BAA2B;AAC3B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,0DAA0D;AAC1D,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC7D,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAMD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,mBAAmB,EAC5B,IAAI,CAAC,EAAE,iBAAiB,GACvB,aAAa,CAyCf;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,aAAa,GAAG,gBAAgB,GAAG,IAAI,CAgDrF;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAgDvC;AAMD;;GAEG;AACH,qBAAa,iBAAkB,YAAW,cAAc,CAAC,aAAa,EAAE,aAAa,CAAC;IACpF,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG;QAAE,QAAQ,EAAE,mBAAmB,EAAE,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,GAAG,IAAI;IAI5F,MAAM,CACJ,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,IAAI,CAAC,EAAE,WAAW,GACjB,aAAa;IAQhB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,WAAW,GAAG,uBAAuB;CAG9F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peac/mappings-mcp",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Model Context Protocol (MCP) integration for PEAC",
5
5
  "main": "dist/index.cjs",
6
6
  "types": "dist/index.d.ts",
@@ -23,7 +23,8 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@peac/schema": "0.11.0"
26
+ "@peac/kernel": "0.11.2",
27
+ "@peac/schema": "0.11.2"
27
28
  },
28
29
  "devDependencies": {
29
30
  "@types/node": "^22.19.11",