@emilia-protocol/mcp-server 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +967 -34
  2. package/auto-receipt.js +402 -0
  3. package/index.js +1411 -245
  4. package/package.json +40 -10
package/index.js CHANGED
@@ -2,36 +2,54 @@
2
2
 
3
3
  /**
4
4
  * EMILIA Protocol — MCP Server
5
+ * @license Apache-2.0
5
6
  *
6
- * Trust layer tools for AI agents.
7
- * Add this server to any MCP-compatible client (Claude, etc.)
8
- * to give your agent access to EMILIA Scores.
7
+ * Trust evaluation tools for AI agents.
8
+ * Add this server to any MCP-compatible client to give your agent
9
+ * access to EP trust profiles and policy evaluation.
9
10
  *
10
- * Tools provided:
11
- * ep_score_lookupCheck any entity's EMILIA Score (no auth)
12
- * ep_submit_receipt Submit a transaction receipt (requires API key)
13
- * ep_verify_receipt Verify a receipt against on-chain Merkle root (no auth)
14
- * ep_search_entities — Search for entities by name or capability
15
- * ep_register_entity — Register a new entity in the EMILIA network
16
- * ep_leaderboard — Get the top-scored entities
11
+ * PRIMARY TOOLS (trust-profile-first):
12
+ * ep_trust_profileGet an entity's full trust profile (canonical)
13
+ * ep_trust_evaluate Evaluate an entity against a trust policy
14
+ * ep_submit_receipt Submit a transaction receipt
17
15
  *
18
- * Setup:
19
- * EP_BASE_URL=https://emiliaprotocol.ai (or your self-hosted instance)
20
- * EP_API_KEY=ep_live_... (for write operations)
16
+ * SECONDARY TOOLS:
17
+ * ep_search_entities Search for entities
18
+ * ep_verify_receipt — Verify receipt against Merkle root
19
+ * ep_register_entity — Register a new entity
20
+ * ep_leaderboard — Get top entities
21
+ *
22
+ * V1.0 ADDITIONS:
23
+ * ep_create_delegation — Create a delegation record (principal → agent)
24
+ * ep_verify_delegation — Verify an agent's delegation for an action
25
+ * ep_trust_gate — Pre-action trust check (canonical gate)
26
+ * ep_batch_submit — Batch receipt submission (up to 50)
27
+ * ep_domain_score — Per-domain trust breakdown
28
+ *
29
+ * SPRINT 4A: Attribution Chain
30
+ * ep_delegation_judgment — Principal delegation authority (how well they choose agents)
21
31
  *
22
- * Claude Desktop config (~/.claude/claude_desktop_config.json):
23
- * {
24
- * "mcpServers": {
25
- * "emilia": {
26
- * "command": "npx",
27
- * "args": ["@emilia-protocol/mcp-server"],
28
- * "env": {
29
- * "EP_BASE_URL": "https://emiliaprotocol.ai",
30
- * "EP_API_KEY": "ep_live_your_key_here"
31
- * }
32
- * }
33
- * }
34
- * }
32
+ * SPRINT 5A: Privacy-Preserving Commitment Proof layer
33
+ * ep_generate_zk_proof — Prove trust threshold without revealing receipt contents or counterparties
34
+ * ep_verify_zk_proof — Verify a commitment trust proof by proof_id (public, no transaction history revealed)
35
+ *
36
+ * EP HANDSHAKE (structured identity exchange):
37
+ * ep_initiate_handshake — Initiate a structured identity exchange between parties
38
+ * ep_add_presentation — Add an identity presentation (proof) to a handshake
39
+ * ep_verify_handshake — Evaluate handshake presentations against policy
40
+ * ep_get_handshake — Get full handshake state
41
+ * ep_revoke_handshake — Revoke an active handshake
42
+ *
43
+ * EP COMMIT (signed authorization token for high-stakes machine actions):
44
+ * ep_issue_commit — Issue a signed EP Commit before a high-stakes action
45
+ * ep_verify_commit — Verify a commit's signature, status, and validity
46
+ * ep_get_commit_status — Get current state of a commit
47
+ * ep_revoke_commit — Revoke an active commit
48
+ * ep_bind_receipt_to_commit — Bind a post-action receipt to a commit
49
+ *
50
+ * Setup:
51
+ * EP_BASE_URL=https://emiliaprotocol.ai
52
+ * EP_API_KEY=ep_live_... (for authenticated writes; registration is public)
35
53
  *
36
54
  * @license Apache-2.0
37
55
  */
@@ -41,214 +59,693 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
41
59
  import {
42
60
  CallToolRequestSchema,
43
61
  ListToolsRequestSchema,
62
+ ListResourcesRequestSchema,
63
+ ReadResourceRequestSchema,
64
+ ListPromptsRequestSchema,
65
+ GetPromptRequestSchema,
44
66
  } from '@modelcontextprotocol/sdk/types.js';
67
+ import { AutoReceiptMiddleware } from './auto-receipt.js';
45
68
 
46
69
  const BASE_URL = process.env.EP_BASE_URL || 'https://emiliaprotocol.ai';
47
70
  const API_KEY = process.env.EP_API_KEY || '';
48
71
 
49
72
  // =============================================================================
50
- // HTTP helpers
73
+ // Auto-Receipt Middleware (Sprint 2)
51
74
  // =============================================================================
52
75
 
76
+ /**
77
+ * Global middleware instance. Shared across all tool calls in this process.
78
+ *
79
+ * Opt-in is false by default — agents must call ep_configure_auto_receipt
80
+ * to enable. The entity_id and epApiKey are pre-populated from environment
81
+ * variables so the operator doesn't need to expose them to the agent.
82
+ */
83
+ const autoReceipt = new AutoReceiptMiddleware({
84
+ epApiUrl: process.env.EP_AUTO_RECEIPT_URL || 'https://emiliaprotocol.ai',
85
+ epApiKey: process.env.EP_AUTO_RECEIPT_KEY || API_KEY,
86
+ optIn: process.env.EP_AUTO_RECEIPT_OPT_IN === 'true',
87
+ entityId: process.env.EP_AUTO_RECEIPT_ENTITY_ID || '',
88
+ });
89
+
53
90
  async function epFetch(path, options = {}) {
54
91
  const url = `${BASE_URL}${path}`;
55
92
  const headers = {
56
93
  'Content-Type': 'application/json',
57
94
  ...(options.auth && API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
58
95
  };
59
-
60
96
  const res = await fetch(url, {
61
97
  method: options.method || 'GET',
62
98
  headers,
63
99
  body: options.body ? JSON.stringify(options.body) : undefined,
64
100
  });
65
-
66
101
  const data = await res.json();
67
- if (!res.ok) {
68
- throw new Error(data.error || `EP API error: ${res.status}`);
69
- }
102
+ if (!res.ok) throw new Error(data.error || `EP API error: ${res.status}`);
70
103
  return data;
71
104
  }
72
105
 
73
106
  // =============================================================================
74
- // Tool definitions
107
+ // Tool definitions — trust-profile-first
75
108
  // =============================================================================
76
109
 
77
110
  const TOOLS = [
111
+ // PRIMARY: Trust profile (canonical read surface)
78
112
  {
79
- name: 'ep_score_lookup',
113
+ name: 'ep_trust_profile',
80
114
  description:
81
- 'Look up an entity\'s EMILIA Score. Scores are public no authentication required. ' +
82
- 'Returns the trust score (0-100), breakdown by signal, receipt count, and verification status. ' +
83
- 'Use this before transacting with any agent, merchant, or service provider.',
115
+ 'Get an entity\'s full trust profile. This is the CANONICAL way to check trust in EP. ' +
116
+ 'Returns behavioral rates (completion, retry, abandon, dispute), signal breakdowns, ' +
117
+ 'provenance composition, consistency, anomaly alerts, current confidence, historical establishment, ' +
118
+ 'and dispute summary. Use this before transacting with any counterparty or installing any software.',
84
119
  inputSchema: {
85
120
  type: 'object',
86
121
  properties: {
87
122
  entity_id: {
88
123
  type: 'string',
89
- description: 'The entity ID (slug like "rex-booking-v1") or UUID to look up',
124
+ description: 'Entity ID (slug like "merchant-xyz") or UUID',
90
125
  },
91
126
  },
92
127
  required: ['entity_id'],
93
128
  },
94
129
  },
130
+
131
+ // PRIMARY: Trust evaluation (policy consumption)
95
132
  {
96
- name: 'ep_submit_receipt',
133
+ name: 'ep_trust_evaluate',
97
134
  description:
98
- 'Submit a transaction receipt to the EMILIA ledger. Requires an EP API key. ' +
99
- 'Receipts are append-only, cryptographically hashed, and chain-linked. ' +
100
- 'Each signal is 0-100: delivery_accuracy, product_accuracy, price_integrity, ' +
101
- 'return_processing, agent_satisfaction. At least one signal is required.',
135
+ 'Evaluate an entity against a trust policy. Returns a Trust Decision (allow/review/deny) with specific failure reasons. ' +
136
+ 'Built-in policies: "strict" (high-value), "standard" (normal), "permissive" (low-risk), "discovery" (allow unevaluated). ' +
137
+ 'Accepts optional context for context-aware evaluation. Use this to make routing and payment decisions.',
102
138
  inputSchema: {
103
139
  type: 'object',
104
140
  properties: {
105
141
  entity_id: {
106
142
  type: 'string',
107
- description: 'UUID of the entity being scored',
143
+ description: 'Entity ID to evaluate',
144
+ },
145
+ policy: {
146
+ type: 'string',
147
+ description: 'Policy name: "strict", "standard", "permissive", "discovery"',
148
+ },
149
+ context: {
150
+ type: 'object',
151
+ description: 'Context key for context-aware evaluation: { task_type, category, geo, modality, value_band }',
108
152
  },
153
+ },
154
+ required: ['entity_id'],
155
+ },
156
+ },
157
+
158
+ // PRIMARY: Submit receipt
159
+ {
160
+ name: 'ep_submit_receipt',
161
+ description:
162
+ 'Submit a transaction receipt to the EP ledger. Requires an API key. ' +
163
+ 'Receipts are append-only, cryptographically hashed, and chain-linked. ' +
164
+ 'transaction_ref is REQUIRED. agent_behavior is the strongest signal.',
165
+ inputSchema: {
166
+ type: 'object',
167
+ properties: {
168
+ entity_id: { type: 'string', description: 'Entity to evaluate' },
169
+ transaction_ref: { type: 'string', description: 'External transaction reference (required)' },
109
170
  transaction_type: {
110
171
  type: 'string',
111
172
  enum: ['purchase', 'service', 'task_completion', 'delivery', 'return'],
112
- description: 'Type of transaction',
113
173
  },
114
- transaction_ref: {
174
+ agent_behavior: {
115
175
  type: 'string',
116
- description: 'External reference (UCP order ID, A2A task ID, etc.)',
117
- },
118
- delivery_accuracy: {
119
- type: 'number',
120
- description: '0-100: Did it arrive when promised?',
176
+ enum: ['completed', 'retried_same', 'retried_different', 'abandoned', 'disputed'],
177
+ description: 'Observable behavioral outcome (strongest Phase 1 signal)',
121
178
  },
122
- product_accuracy: {
123
- type: 'number',
124
- description: '0-100: Did the listing match reality?',
125
- },
126
- price_integrity: {
127
- type: 'number',
128
- description: '0-100: Was the price honored?',
129
- },
130
- return_processing: {
131
- type: 'number',
132
- description: '0-100: Was the return policy followed?',
133
- },
134
- agent_satisfaction: {
135
- type: 'number',
136
- description: '0-100: Was the purchasing agent satisfied?',
137
- },
138
- evidence: {
179
+ delivery_accuracy: { type: 'number', description: '0-100' },
180
+ product_accuracy: { type: 'number', description: '0-100' },
181
+ price_integrity: { type: 'number', description: '0-100' },
182
+ return_processing: { type: 'number', description: '0-100' },
183
+ claims: { type: 'object', description: 'Structured claims (delivered, on_time, price_honored, as_described)' },
184
+ evidence: { type: 'object', description: 'Supporting evidence references' },
185
+ context: {
139
186
  type: 'object',
140
- description: 'Structured evidence e.g. { promised_delivery: "2d", actual_delivery: "3d" }',
187
+ description: 'Context key: { task_type, category, geo, modality, value_band, risk_class }',
141
188
  },
142
189
  },
143
- required: ['entity_id', 'transaction_type'],
190
+ required: ['entity_id', 'transaction_type', 'transaction_ref'],
191
+ },
192
+ },
193
+
194
+ // SECONDARY
195
+ {
196
+ name: 'ep_search_entities',
197
+ description: 'Search for entities by name, capability, or category.',
198
+ inputSchema: {
199
+ type: 'object',
200
+ properties: {
201
+ query: { type: 'string', description: 'Search query' },
202
+ entity_type: { type: 'string', enum: ['agent','merchant','service_provider','github_app','github_action','mcp_server','npm_package','chrome_extension','shopify_app','marketplace_plugin','agent_tool'] },
203
+ },
204
+ required: ['query'],
144
205
  },
145
206
  },
146
207
  {
147
208
  name: 'ep_verify_receipt',
209
+ description: 'Verify a receipt against the on-chain Merkle root.',
210
+ inputSchema: {
211
+ type: 'object',
212
+ properties: {
213
+ receipt_id: { type: 'string', description: 'Receipt ID (ep_rcpt_...)' },
214
+ },
215
+ required: ['receipt_id'],
216
+ },
217
+ },
218
+ {
219
+ name: 'ep_register_entity',
220
+ description: 'Register a new entity. Public — returns the first API key.',
221
+ inputSchema: {
222
+ type: 'object',
223
+ properties: {
224
+ entity_id: { type: 'string', description: 'Slug (lowercase, hyphens)' },
225
+ display_name: { type: 'string' },
226
+ entity_type: { type: 'string', enum: ['agent','merchant','service_provider','github_app','github_action','mcp_server','npm_package','chrome_extension','shopify_app','marketplace_plugin','agent_tool'] },
227
+ description: { type: 'string' },
228
+ capabilities: { type: 'array', items: { type: 'string' } },
229
+ },
230
+ required: ['entity_id', 'display_name', 'entity_type', 'description'],
231
+ },
232
+ },
233
+ {
234
+ name: 'ep_leaderboard',
235
+ description: 'Get top entities ranked by trust confidence.',
236
+ inputSchema: {
237
+ type: 'object',
238
+ properties: {
239
+ limit: { type: 'number', description: 'Max entities (default 10, max 50)' },
240
+ entity_type: { type: 'string', enum: ['agent','merchant','service_provider','github_app','github_action','mcp_server','npm_package','chrome_extension','shopify_app','marketplace_plugin','agent_tool'] },
241
+ },
242
+ },
243
+ },
244
+ // DUE PROCESS
245
+ {
246
+ name: 'ep_dispute_file',
148
247
  description:
149
- 'Verify a receipt against the on-chain Merkle root. No auth required. ' +
150
- 'Returns the Merkle proof, verification status, and a link to the Base L2 transaction. ' +
151
- '"Don\'t trust EMILIA. Verify the math yourself."',
248
+ 'File a dispute against a receipt. Any affected party can challenge. ' +
249
+ 'Reasons: fraudulent_receipt, inaccurate_signals, identity_dispute, context_mismatch, duplicate_transaction, coerced_receipt, other. ' +
250
+ 'The receipt submitter has 7 days to respond.',
251
+ inputSchema: {
252
+ type: 'object',
253
+ properties: {
254
+ receipt_id: { type: 'string', description: 'Receipt ID to dispute (ep_rcpt_...)' },
255
+ reason: { type: 'string', description: 'Reason for dispute' },
256
+ description: { type: 'string', description: 'Explanation of the dispute' },
257
+ evidence: { type: 'object', description: 'Supporting evidence' },
258
+ },
259
+ required: ['receipt_id', 'reason'],
260
+ },
261
+ },
262
+ {
263
+ name: 'ep_dispute_status',
264
+ description: 'Check the status of a dispute. Public — transparency is a protocol value.',
152
265
  inputSchema: {
153
266
  type: 'object',
154
267
  properties: {
155
- receipt_id: {
268
+ dispute_id: { type: 'string', description: 'Dispute ID (ep_disp_...)' },
269
+ },
270
+ required: ['dispute_id'],
271
+ },
272
+ },
273
+ {
274
+ name: 'ep_report_trust_issue',
275
+ description:
276
+ 'Report a trust issue as a human. No authentication required. ' +
277
+ 'For when someone is wrongly downgraded, harmed by a trusted entity, or sees fraud. ' +
278
+ 'EP must never make trust more powerful than appeal.',
279
+ inputSchema: {
280
+ type: 'object',
281
+ properties: {
282
+ entity_id: { type: 'string', description: 'Entity the report is about' },
283
+ report_type: {
156
284
  type: 'string',
157
- description: 'The receipt ID (e.g. "ep_rcpt_abc123...")',
285
+ enum: ['wrongly_downgraded', 'harmed_by_trusted_entity', 'fraudulent_entity', 'inaccurate_profile', 'other'],
158
286
  },
287
+ description: { type: 'string', description: 'What happened' },
288
+ contact_email: { type: 'string', description: 'Email for follow-up (optional)' },
159
289
  },
160
- required: ['receipt_id'],
290
+ required: ['entity_id', 'report_type', 'description'],
161
291
  },
162
292
  },
293
+ // EP-SX: Software Trust
163
294
  {
164
- name: 'ep_search_entities',
295
+ name: 'ep_appeal_dispute',
296
+ description:
297
+ 'Appeal a dispute resolution. Only dispute participants can appeal. ' +
298
+ 'Requires the dispute to be in upheld, reversed, or dismissed state. ' +
299
+ '"Trust must never be more powerful than appeal."',
300
+ inputSchema: {
301
+ type: 'object',
302
+ properties: {
303
+ dispute_id: { type: 'string', description: 'The dispute ID to appeal' },
304
+ reason: { type: 'string', description: 'Why the resolution should be reconsidered (min 10 chars)' },
305
+ evidence: { type: 'object', description: 'Optional supporting evidence for the appeal' },
306
+ },
307
+ required: ['dispute_id', 'reason'],
308
+ },
309
+ },
310
+ {
311
+ name: 'ep_install_preflight',
165
312
  description:
166
- 'Search for entities in the EMILIA network by name, capability, or category. ' +
167
- 'Returns matching entities with their scores and capabilities.',
313
+ 'EP-SX: Should I install this plugin/app/package/extension? ' +
314
+ 'Evaluates a software entity against a software-specific trust policy with context. ' +
315
+ 'Returns allow/review/deny with specific reasons covering publisher, permissions, provenance, and trust history.',
168
316
  inputSchema: {
169
317
  type: 'object',
170
318
  properties: {
171
- query: {
319
+ entity_id: { type: 'string', description: 'Software entity ID (e.g. github_app:acme/code-helper)' },
320
+ policy: {
172
321
  type: 'string',
173
- description: 'Search query entity name, capability, or category',
322
+ description: 'Software policy: github_private_repo_safe_v1, npm_buildtime_safe_v1, browser_extension_safe_v1, mcp_server_safe_v1, or standard EP policies',
174
323
  },
175
- entity_type: {
176
- type: 'string',
177
- enum: ['agent', 'merchant', 'service_provider'],
178
- description: 'Filter by entity type',
324
+ context: {
325
+ type: 'object',
326
+ description: 'Install context: { host, install_scope, permission_class, data_sensitivity, execution_mode }',
179
327
  },
180
- min_score: {
181
- type: 'number',
182
- description: 'Minimum EMILIA Score (0-100)',
328
+ },
329
+ required: ['entity_id'],
330
+ },
331
+ },
332
+ // EP-IX Identity Continuity
333
+ {
334
+ name: 'ep_principal_lookup',
335
+ description: 'Look up a principal — the enduring actor behind entities. Returns bindings, controlled entities, and continuity history.',
336
+ inputSchema: {
337
+ type: 'object',
338
+ properties: {
339
+ principal_id: { type: 'string', description: 'Principal ID (e.g. ep_principal_abc)' },
340
+ },
341
+ required: ['principal_id'],
342
+ },
343
+ },
344
+ {
345
+ name: 'ep_lineage',
346
+ description: 'View entity lineage — predecessors, successors, continuity decisions, and whitewashing flags. Use to check if an entity has suspicious continuity gaps.',
347
+ inputSchema: {
348
+ type: 'object',
349
+ properties: {
350
+ entity_id: { type: 'string', description: 'Entity ID to check lineage for' },
351
+ },
352
+ required: ['entity_id'],
353
+ },
354
+ },
355
+ {
356
+ name: 'ep_list_policies',
357
+ description: 'List all available trust policies with their requirements and families. Use to discover which policy to evaluate against.',
358
+ inputSchema: { type: 'object', properties: {} },
359
+ },
360
+
361
+ // V1.0: Delegation
362
+ {
363
+ name: 'ep_create_delegation',
364
+ description:
365
+ 'Create a delegation record: a human or principal authorizes an agent to act on their behalf. ' +
366
+ 'The delegation is recorded in the EP ledger with scope, expiry, and optional constraints. ' +
367
+ 'Every delegated action can reference this delegation to prove authorization.',
368
+ inputSchema: {
369
+ type: 'object',
370
+ properties: {
371
+ principal_id: { type: 'string', description: 'The principal (human/org) granting the delegation' },
372
+ agent_entity_id: { type: 'string', description: 'The agent entity being authorized' },
373
+ scope: { type: 'array', items: { type: 'string' }, description: 'List of permitted action types (e.g. ["purchase", "book", "send_email"])' },
374
+ max_value_usd: { type: 'number', description: 'Maximum transaction value in USD this delegation authorizes (optional)' },
375
+ expires_at: { type: 'string', description: 'ISO8601 expiry timestamp. If omitted, defaults to 24 hours.' },
376
+ constraints: { type: 'object', description: 'Additional constraints (geo, merchant_category, etc.)' },
377
+ },
378
+ required: ['principal_id', 'agent_entity_id', 'scope'],
379
+ },
380
+ },
381
+
382
+ // V1.0: Verify delegation
383
+ {
384
+ name: 'ep_verify_delegation',
385
+ description:
386
+ 'Verify that an agent currently holds a valid delegation from a principal for a specific action. ' +
387
+ 'Use this before accepting a task from an agent claiming to act on behalf of a human. ' +
388
+ 'Returns: valid/expired/not_found with scope details.',
389
+ inputSchema: {
390
+ type: 'object',
391
+ properties: {
392
+ delegation_id: { type: 'string', description: 'Delegation ID (ep_dlg_...)' },
393
+ action_type: { type: 'string', description: 'The action type to check (must be in delegation scope)' },
394
+ },
395
+ required: ['delegation_id'],
396
+ },
397
+ },
398
+
399
+ // V1.0: Trust gate
400
+ {
401
+ name: 'ep_trust_gate',
402
+ description:
403
+ 'Trust gate: check if an entity meets the required trust threshold BEFORE executing a high-stakes action. ' +
404
+ 'This is the canonical pre-action check. Always call this before: payments, sending messages on behalf of users, ' +
405
+ 'installing software, or any irreversible action. Returns allow/review/deny with reason.',
406
+ inputSchema: {
407
+ type: 'object',
408
+ properties: {
409
+ entity_id: { type: 'string', description: 'Entity requesting to perform the action' },
410
+ action: { type: 'string', description: 'Action being requested (e.g. "process_payment", "send_email", "install_package")' },
411
+ policy: { type: 'string', description: 'Policy to enforce: "strict", "standard", "permissive". Default: "standard"' },
412
+ value_usd: { type: 'number', description: 'Transaction value in USD (used for risk calibration)' },
413
+ delegation_id: { type: 'string', description: 'If agent is acting on behalf of a human, the delegation ID' },
414
+ },
415
+ required: ['entity_id', 'action'],
416
+ },
417
+ },
418
+
419
+ // V1.0: Batch receipt submission
420
+ {
421
+ name: 'ep_batch_submit',
422
+ description:
423
+ 'Submit multiple receipts atomically. All receipts share the same submitter (your API key). ' +
424
+ 'Useful for bulk reconciliation or recording a session of agent-entity interactions. ' +
425
+ 'Returns per-receipt success/failure. Max 50 receipts per batch.',
426
+ inputSchema: {
427
+ type: 'object',
428
+ properties: {
429
+ receipts: {
430
+ type: 'array',
431
+ description: 'Array of receipt objects (same schema as ep_submit_receipt)',
432
+ items: {
433
+ type: 'object',
434
+ properties: {
435
+ entity_id: { type: 'string' },
436
+ transaction_ref: { type: 'string' },
437
+ transaction_type: { type: 'string' },
438
+ agent_behavior: { type: 'string' },
439
+ delivery_accuracy: { type: 'number' },
440
+ product_accuracy: { type: 'number' },
441
+ price_integrity: { type: 'number' },
442
+ return_processing: { type: 'number' },
443
+ },
444
+ required: ['entity_id', 'transaction_ref', 'transaction_type'],
445
+ },
183
446
  },
184
447
  },
185
- required: ['query'],
448
+ required: ['receipts'],
186
449
  },
187
450
  },
451
+
452
+ // Sprint 2: Auto-receipt configuration
188
453
  {
189
- name: 'ep_register_entity',
454
+ name: 'ep_configure_auto_receipt',
190
455
  description:
191
- 'Register a new entity in the EMILIA network. Requires an EP API key. ' +
192
- 'Returns the entity ID, entity number, and a new API key for the entity.',
456
+ 'Enable or disable automatic receipt generation for this session. ' +
457
+ 'When enabled, every EP tool call generates a behavioral receipt automatically. ' +
458
+ 'Privacy-preserving: sensitive fields (passwords, tokens, API keys) are redacted before storage. ' +
459
+ 'Receipts are marked unilateral — they cannot be bilateral without counterparty confirmation. ' +
460
+ 'Auto-receipt is opt-in and disabled by default.',
193
461
  inputSchema: {
194
462
  type: 'object',
195
463
  properties: {
464
+ enabled: {
465
+ type: 'boolean',
466
+ description: 'Enable (true) or disable (false) auto-receipt generation for this session',
467
+ },
196
468
  entity_id: {
197
469
  type: 'string',
198
- description: 'Human-readable slug (e.g. "my-agent-v1"). Lowercase, hyphens, no spaces.',
470
+ description: 'Entity ID to attribute auto-generated receipts to (your EP entity slug)',
199
471
  },
200
- display_name: {
201
- type: 'string',
202
- description: 'Display name (e.g. "My AI Shopping Agent")',
472
+ },
473
+ required: ['enabled', 'entity_id'],
474
+ },
475
+ },
476
+
477
+ // V1.0: Domain score
478
+ {
479
+ name: 'ep_domain_score',
480
+ description:
481
+ 'Get an entity\'s trust score broken down by behavioral domain. ' +
482
+ 'Trust is not a scalar — an agent excellent at financial transactions may be unreliable at creative tasks. ' +
483
+ 'Domains: financial, code_execution, communication, delegation, infrastructure, content_creation, data_access. ' +
484
+ 'Returns per-domain confidence, evidence count, and behavioral rates.',
485
+ inputSchema: {
486
+ type: 'object',
487
+ properties: {
488
+ entity_id: { type: 'string', description: 'Entity to query' },
489
+ domains: {
490
+ type: 'array',
491
+ items: { type: 'string' },
492
+ description: 'Domains to query. If omitted, returns all domains.',
203
493
  },
204
- entity_type: {
494
+ },
495
+ required: ['entity_id'],
496
+ },
497
+ },
498
+
499
+ // Sprint 5A: Privacy-Preserving Commitment Proof layer
500
+ {
501
+ name: 'ep_generate_zk_proof',
502
+ description:
503
+ 'Generate a privacy-preserving commitment proof. Proves a trust claim (e.g., score > 0.85 in the ' +
504
+ 'financial domain, or > 50 verified receipts) WITHOUT revealing receipt contents, counterparty ' +
505
+ 'identities, or transaction details. Uses HMAC-SHA256 commitments + Merkle trees. ' +
506
+ 'Returns a proof_id you can share publicly — verifiers call ep_verify_zk_proof with only the ' +
507
+ 'proof_id and learn nothing about your transaction history. ' +
508
+ 'Essential for privacy-sensitive contexts: healthcare (HIPAA), legal (privilege), ' +
509
+ 'finance (NDA/MNPI), and any situation where sharing transaction history is not possible. ' +
510
+ 'Requires your EP API key (you can only prove claims about your own entity).',
511
+ inputSchema: {
512
+ type: 'object',
513
+ properties: {
514
+ entity_id: {
205
515
  type: 'string',
206
- enum: ['agent', 'merchant', 'service_provider'],
207
- description: 'Type of entity',
516
+ description: 'Your EP entity ID. Must match the API key used to authenticate.',
208
517
  },
209
- description: {
518
+ claim_type: {
210
519
  type: 'string',
211
- description: 'What this entity does',
520
+ enum: ['score_above', 'domain_score_above', 'receipt_count_above'],
521
+ description:
522
+ 'score_above: global behavioral score > threshold. ' +
523
+ 'domain_score_above: per-domain score > threshold (requires domain). ' +
524
+ 'receipt_count_above: total receipts > threshold (integer count).',
212
525
  },
213
- capabilities: {
214
- type: 'array',
215
- items: { type: 'string' },
216
- description: 'List of capabilities (e.g. ["price_comparison", "booking"])',
526
+ threshold: {
527
+ type: 'number',
528
+ description:
529
+ '0.0–1.0 for score claims (e.g. 0.85 = 85th percentile behavioral score). ' +
530
+ 'Positive integer for receipt_count_above (e.g. 50 = at least 50 receipts).',
217
531
  },
218
- website_url: {
532
+ domain: {
219
533
  type: 'string',
220
- description: 'Website URL',
534
+ enum: [
535
+ 'financial', 'code_execution', 'communication', 'delegation',
536
+ 'infrastructure', 'content_creation', 'data_access',
537
+ ],
538
+ description: 'Required for domain_score_above claims. Which behavioral domain to prove.',
221
539
  },
222
- a2a_endpoint: {
540
+ },
541
+ required: ['entity_id', 'claim_type', 'threshold'],
542
+ },
543
+ },
544
+ {
545
+ name: 'ep_verify_zk_proof',
546
+ description:
547
+ 'Verify a privacy-preserving commitment proof by proof_id. ' +
548
+ 'Returns whether the claim is currently valid — without revealing anything about the ' +
549
+ 'entity\'s transaction history, counterparties, or receipt contents. ' +
550
+ 'The proof holder shares only the proof_id. You verify without learning who they transacted with. ' +
551
+ 'Use this to accept trust claims from entities in privacy-sensitive industries ' +
552
+ '(healthcare, legal, finance) who cannot share raw transaction history.',
553
+ inputSchema: {
554
+ type: 'object',
555
+ properties: {
556
+ proof_id: {
223
557
  type: 'string',
224
- description: 'A2A Agent Card endpoint URL',
558
+ description: 'The proof identifier (ep_zkp_...) shared by the proving entity.',
225
559
  },
226
- ucp_profile_url: {
560
+ },
561
+ required: ['proof_id'],
562
+ },
563
+ },
564
+
565
+ // Sprint 4A: Delegation judgment
566
+ {
567
+ name: 'ep_delegation_judgment',
568
+ description:
569
+ 'Get a principal\'s delegation authority — how well they choose and authorize agents. ' +
570
+ 'High judgment principals consistently authorize well-behaved agents. ' +
571
+ 'Low judgment principals frequently authorize agents that fail, dispute, or abandon tasks. ' +
572
+ 'This signal is deliberately weak (0.15 weight per outcome): a single bad delegation should not ' +
573
+ 'define a principal, but a pattern of them should be legible. ' +
574
+ 'Returns: judgment_score (0–1), agents_authorized, good_outcome_rate, and signal counts.',
575
+ inputSchema: {
576
+ type: 'object',
577
+ properties: {
578
+ principal_id: {
227
579
  type: 'string',
228
- description: 'UCP merchant profile URL',
580
+ description: 'Principal entity ID or human identifier (e.g. ep_principal_abc or user@example.com)',
229
581
  },
230
582
  },
231
- required: ['entity_id', 'display_name', 'entity_type', 'description'],
583
+ required: ['principal_id'],
232
584
  },
233
585
  },
586
+
587
+ // EP Commit — signed authorization token for high-stakes machine actions
234
588
  {
235
- name: 'ep_leaderboard',
589
+ name: 'ep_issue_commit',
236
590
  description:
237
- 'Get the top-scored entities in the EMILIA network. ' +
238
- 'Returns entities ranked by EMILIA Score with their breakdowns.',
591
+ 'Issue a signed EP Commit before a high-stakes action. ' +
592
+ 'Returns a commit_id, decision (allow/deny/review), expiry, scope, and appeal path. ' +
593
+ 'The commit binds the agent to a specific action type, entity, and policy before execution.',
239
594
  inputSchema: {
240
595
  type: 'object',
241
596
  properties: {
242
- limit: {
243
- type: 'number',
244
- description: 'Number of entities to return (default: 10, max: 50)',
245
- },
246
- entity_type: {
247
- type: 'string',
248
- enum: ['agent', 'merchant', 'service_provider'],
249
- description: 'Filter by entity type',
597
+ action_type: { type: 'string', description: 'Action type: install, connect, delegate, transact' },
598
+ entity_id: { type: 'string', description: 'Entity requesting the commit' },
599
+ principal_id: { type: 'string', description: 'Principal authorizing the action (optional)' },
600
+ counterparty_entity_id: { type: 'string', description: 'Counterparty entity ID (optional)' },
601
+ delegation_id: { type: 'string', description: 'Delegation ID if acting under delegation (optional)' },
602
+ scope: { type: 'array', items: { type: 'string' }, description: 'Action scope list (optional)' },
603
+ max_value_usd: { type: 'number', description: 'Maximum value in USD (optional)' },
604
+ context: { type: 'object', description: 'Additional context metadata (optional)' },
605
+ policy: { type: 'string', description: 'Trust policy to enforce (default: standard)' },
606
+ },
607
+ required: ['action_type', 'entity_id'],
608
+ },
609
+ },
610
+ {
611
+ name: 'ep_verify_commit',
612
+ description:
613
+ 'Verify a commit\'s signature, status, and validity. ' +
614
+ 'Returns valid/invalid, current status, decision, and expiry.',
615
+ inputSchema: {
616
+ type: 'object',
617
+ properties: {
618
+ commit_id: { type: 'string', description: 'Commit ID (epc_...)' },
619
+ },
620
+ required: ['commit_id'],
621
+ },
622
+ },
623
+ {
624
+ name: 'ep_get_commit_status',
625
+ description: 'Get the current state of a commit (active, revoked, expired, fulfilled).',
626
+ inputSchema: {
627
+ type: 'object',
628
+ properties: {
629
+ commit_id: { type: 'string', description: 'Commit ID (epc_...)' },
630
+ },
631
+ required: ['commit_id'],
632
+ },
633
+ },
634
+ {
635
+ name: 'ep_revoke_commit',
636
+ description: 'Revoke an active commit before it is fulfilled or expires.',
637
+ inputSchema: {
638
+ type: 'object',
639
+ properties: {
640
+ commit_id: { type: 'string', description: 'Commit ID to revoke (epc_...)' },
641
+ reason: { type: 'string', description: 'Reason for revocation' },
642
+ },
643
+ required: ['commit_id', 'reason'],
644
+ },
645
+ },
646
+ {
647
+ name: 'ep_bind_receipt_to_commit',
648
+ description:
649
+ 'Bind a post-action receipt to a commit, completing the commit-execute-receipt cycle. ' +
650
+ 'Links the behavioral outcome back to the signed authorization token.',
651
+ inputSchema: {
652
+ type: 'object',
653
+ properties: {
654
+ commit_id: { type: 'string', description: 'Commit ID to bind to (epc_...)' },
655
+ receipt_id: { type: 'string', description: 'Receipt ID to bind (ep_rcpt_...)' },
656
+ },
657
+ required: ['commit_id', 'receipt_id'],
658
+ },
659
+ },
660
+
661
+ // EP Handshake — structured identity exchange
662
+ {
663
+ name: 'ep_initiate_handshake',
664
+ description:
665
+ 'Initiate an EP Handshake — a structured identity exchange between parties. ' +
666
+ 'The handshake coordinates mutual presentation of identity proofs before a trust decision. ' +
667
+ 'Requires at least 2 parties and a governing trust policy.',
668
+ inputSchema: {
669
+ type: 'object',
670
+ properties: {
671
+ mode: { type: 'string', description: 'Handshake mode: "mutual", "one-way", "delegated"' },
672
+ policy_id: { type: 'string', description: 'Trust policy governing the handshake' },
673
+ parties: {
674
+ type: 'array',
675
+ description: 'Parties in the handshake (min 2). Each: { entity_ref, role }',
676
+ items: {
677
+ type: 'object',
678
+ properties: {
679
+ entity_ref: { type: 'string' },
680
+ role: { type: 'string' },
681
+ },
682
+ required: ['entity_ref', 'role'],
683
+ },
250
684
  },
685
+ binding: { type: 'object', description: 'Optional binding constraints' },
686
+ interaction_id: { type: 'string', description: 'Optional external interaction reference' },
687
+ },
688
+ required: ['mode', 'policy_id', 'parties'],
689
+ },
690
+ },
691
+ {
692
+ name: 'ep_add_presentation',
693
+ description:
694
+ 'Add an identity presentation (proof) to an active handshake. ' +
695
+ 'Each party presents their identity claims for evaluation against the handshake policy. ' +
696
+ 'Supports full, selective, or zero-knowledge disclosure modes.',
697
+ inputSchema: {
698
+ type: 'object',
699
+ properties: {
700
+ handshake_id: { type: 'string', description: 'Handshake ID to present to' },
701
+ party_role: { type: 'string', description: 'Role of the presenting party' },
702
+ presentation_type: { type: 'string', description: 'Type: "verifiable_credential", "ep_trust_profile", "attestation"' },
703
+ issuer_ref: { type: 'string', description: 'Optional credential issuer reference' },
704
+ claims: { type: 'object', description: 'Identity claims being presented' },
705
+ disclosure_mode: { type: 'string', description: 'Disclosure mode: "full", "selective", "zk"' },
706
+ },
707
+ required: ['handshake_id', 'party_role', 'presentation_type', 'claims'],
708
+ },
709
+ },
710
+ {
711
+ name: 'ep_verify_handshake',
712
+ description:
713
+ 'Evaluate all presentations in a handshake against the governing policy. ' +
714
+ 'Returns: accepted (all requirements met), rejected (policy violations), or partial (awaiting presentations). ' +
715
+ 'Includes reason_codes explaining the outcome.',
716
+ inputSchema: {
717
+ type: 'object',
718
+ properties: {
719
+ handshake_id: { type: 'string', description: 'Handshake ID to verify' },
720
+ },
721
+ required: ['handshake_id'],
722
+ },
723
+ },
724
+ {
725
+ name: 'ep_get_handshake',
726
+ description:
727
+ 'Get the full state of a handshake including parties, presentations, binding, and result. ' +
728
+ 'Use this to check handshake progress or review completed exchanges.',
729
+ inputSchema: {
730
+ type: 'object',
731
+ properties: {
732
+ handshake_id: { type: 'string', description: 'Handshake ID to retrieve' },
733
+ },
734
+ required: ['handshake_id'],
735
+ },
736
+ },
737
+ {
738
+ name: 'ep_revoke_handshake',
739
+ description:
740
+ 'Revoke an active handshake. Only parties to the handshake may revoke it. ' +
741
+ 'Revocation is terminal — the handshake cannot be reopened.',
742
+ inputSchema: {
743
+ type: 'object',
744
+ properties: {
745
+ handshake_id: { type: 'string', description: 'Handshake ID to revoke' },
746
+ reason: { type: 'string', description: 'Reason for revocation' },
251
747
  },
748
+ required: ['handshake_id', 'reason'],
252
749
  },
253
750
  },
254
751
  ];
@@ -259,73 +756,582 @@ const TOOLS = [
259
756
 
260
757
  async function handleTool(name, args) {
261
758
  switch (name) {
262
- case 'ep_score_lookup': {
263
- const data = await epFetch(`/api/score/${encodeURIComponent(args.entity_id)}`);
264
- return formatScore(data);
759
+ case 'ep_trust_profile': {
760
+ const data = await epFetch(`/api/trust/profile/${encodeURIComponent(args.entity_id)}`);
761
+ return formatTrustProfile(data);
762
+ }
763
+
764
+ case 'ep_trust_evaluate': {
765
+ const body = { entity_id: args.entity_id, policy: args.policy || 'standard' };
766
+ if (args.context) body.context = args.context;
767
+ const data = await epFetch('/api/trust/evaluate', { method: 'POST', body });
768
+ return formatEvaluation(data);
265
769
  }
266
770
 
267
771
  case 'ep_submit_receipt': {
268
- if (!API_KEY) {
269
- return 'Error: EP_API_KEY is required for submitting receipts. Set it in your MCP server config.';
270
- }
271
- const data = await epFetch('/api/receipts/submit', {
272
- method: 'POST',
273
- auth: true,
274
- body: {
275
- entity_id: args.entity_id,
276
- transaction_type: args.transaction_type,
277
- transaction_ref: args.transaction_ref,
278
- delivery_accuracy: args.delivery_accuracy,
279
- product_accuracy: args.product_accuracy,
280
- price_integrity: args.price_integrity,
281
- return_processing: args.return_processing,
282
- agent_satisfaction: args.agent_satisfaction,
283
- evidence: args.evidence,
284
- },
285
- });
772
+ if (!API_KEY) return 'Error: EP_API_KEY required. Set it in MCP server config.';
773
+ const body = { ...args };
774
+ const data = await epFetch('/api/receipts/submit', { method: 'POST', auth: true, body });
286
775
  return `Receipt submitted.\n` +
287
- `Receipt ID: ${data.receipt.receipt_id}\n` +
288
- `Composite Score: ${data.receipt.composite_score}\n` +
776
+ `ID: ${data.receipt.receipt_id}\n` +
289
777
  `Hash: ${data.receipt.receipt_hash}\n` +
290
- `Entity Score Updated: ${data.entity_score.emilia_score} (${data.entity_score.total_receipts} receipts)`;
291
- }
292
-
293
- case 'ep_verify_receipt': {
294
- const data = await epFetch(`/api/verify/${encodeURIComponent(args.receipt_id)}`);
295
- return formatVerification(data);
778
+ `Entity trust profile updated. Query with ep_trust_profile for current state.`;
296
779
  }
297
780
 
298
781
  case 'ep_search_entities': {
299
782
  const params = new URLSearchParams({ q: args.query });
300
783
  if (args.entity_type) params.set('type', args.entity_type);
301
- if (args.min_score) params.set('min_score', args.min_score.toString());
302
784
  const data = await epFetch(`/api/entities/search?${params}`);
303
- return formatSearch(data);
785
+ const entities = data.entities || data.results || [];
786
+ if (!entities.length) return 'No entities found.';
787
+ return entities.map(e => {
788
+ const conf = e.confidence || 'pending';
789
+ const ee = e.effective_evidence != null ? ` · evidence: ${e.effective_evidence.toFixed(2)}` : '';
790
+ return `${e.display_name} (${e.entity_id})\n confidence: ${conf}${ee} · trust profile available`;
791
+ }).join('\n\n');
792
+ }
793
+
794
+ case 'ep_verify_receipt': {
795
+ const data = await epFetch(`/api/verify/${encodeURIComponent(args.receipt_id)}`);
796
+ return `Receipt: ${data.receipt_id}\nHash: ${data.receipt_hash}\nAnchored: ${data.anchored ? 'Yes' : 'No'}\nVerified: ${data.verified ? 'YES' : 'FAILED'}`;
304
797
  }
305
798
 
306
799
  case 'ep_register_entity': {
307
- if (!API_KEY) {
308
- return 'Error: EP_API_KEY is required for registration. Set it in your MCP server config.';
309
- }
310
- const data = await epFetch('/api/entities/register', {
311
- method: 'POST',
312
- auth: true,
313
- body: args,
314
- });
315
- return `Entity registered!\n` +
316
- `Entity ID: ${data.entity.entity_id}\n` +
317
- `Entity #: ${data.entity.entity_number || 'N/A'}\n` +
318
- `EMILIA Score: ${data.entity.emilia_score} (new entity — score starts at 50)\n` +
319
- `API Key: ${data.api_key}\n\n` +
320
- `⚠️ Save this API key — it won't be shown again.`;
800
+ const data = await epFetch('/api/entities/register', { method: 'POST', body: args });
801
+ return `Registered: ${data.entity.entity_id}\nAPI Key: ${data.api_key}\n⚠️ Save this key it won't be shown again.`;
321
802
  }
322
803
 
323
804
  case 'ep_leaderboard': {
324
805
  const params = new URLSearchParams();
325
- if (args?.limit) params.set('limit', Math.min(args.limit, 50).toString());
806
+ if (args?.limit) params.set('limit', String(Math.min(args.limit, 50)));
326
807
  if (args?.entity_type) params.set('type', args.entity_type);
327
808
  const data = await epFetch(`/api/leaderboard?${params}`);
328
- return formatLeaderboard(data);
809
+ const lb = data.leaderboard || [];
810
+ if (!lb.length) return 'No entities in leaderboard yet.';
811
+ return lb.map(e => {
812
+ const conf = e.confidence || 'pending';
813
+ return `#${e.rank} ${e.display_name} (${e.entity_id})\n confidence: ${conf} · trust profile available`;
814
+ }).join('\n\n');
815
+ }
816
+
817
+ case 'ep_dispute_file': {
818
+ if (!API_KEY) return 'Error: EP_API_KEY required to file disputes.';
819
+ const body = {
820
+ receipt_id: args.receipt_id,
821
+ reason: args.reason,
822
+ description: args.description || null,
823
+ evidence: args.evidence || null,
824
+ };
825
+ const data = await epFetch('/api/disputes/file', { method: 'POST', auth: true, body });
826
+ return `Dispute filed.\n` +
827
+ `Dispute ID: ${data.dispute_id}\n` +
828
+ `Receipt: ${data.receipt_id}\n` +
829
+ `Status: ${data.status}\n` +
830
+ `Response deadline: ${data.response_deadline}\n` +
831
+ `${data._message}`;
832
+ }
833
+
834
+ case 'ep_dispute_status': {
835
+ const data = await epFetch(`/api/disputes/${encodeURIComponent(args.dispute_id)}`);
836
+ let out = `Dispute: ${data.dispute_id}\n`;
837
+ out += `Status: ${data.status}\n`;
838
+ out += `Reason: ${data.reason}\n`;
839
+ out += `Entity: ${data.entity?.display_name} (${data.entity?.entity_id})\n`;
840
+ out += `Filed by: ${data.filed_by?.display_name} (${data.filed_by_type})\n`;
841
+ if (data.response) out += `Response: ${data.response}\n`;
842
+ if (data.resolution) out += `Resolution: ${data.resolution}\nRationale: ${data.resolution_rationale}\n`;
843
+ return out;
844
+ }
845
+
846
+ case 'ep_report_trust_issue': {
847
+ const body = {
848
+ entity_id: args.entity_id,
849
+ report_type: args.report_type,
850
+ description: args.description,
851
+ contact_email: args.contact_email || null,
852
+ };
853
+ const data = await epFetch('/api/disputes/report', { method: 'POST', body });
854
+ return `Report filed.\n` +
855
+ `Report ID: ${data.report_id}\n` +
856
+ `${data._message}\n` +
857
+ `${data._principle}`;
858
+ }
859
+
860
+ case 'ep_appeal_dispute': {
861
+ if (!API_KEY) return 'Error: EP_API_KEY required to file appeals.';
862
+ const body = {
863
+ dispute_id: args.dispute_id,
864
+ reason: args.reason,
865
+ evidence: args.evidence || null,
866
+ };
867
+ const data = await epFetch('/api/disputes/appeal', { method: 'POST', auth: true, body });
868
+ return `Appeal filed.\n` +
869
+ `Appeal ID: ${data.appeal_id || data.dispute_id}\n` +
870
+ `Status: ${data.status}\n` +
871
+ `${data._message || 'Your appeal has been submitted for review.'}`;
872
+ }
873
+
874
+ case 'ep_install_preflight': {
875
+ const body = { entity_id: args.entity_id, policy: args.policy || 'standard' };
876
+ if (args.context) body.context = args.context;
877
+ const data = await epFetch('/api/trust/install-preflight', { method: 'POST', body });
878
+ let out = `Pre-Action Enforcement: ${data.display_name} (${data.entity_id})\n`;
879
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
880
+ out += `Decision: ${data.decision === 'allow' ? '✓ ALLOW' : data.decision === 'deny' ? '✗ DENY' : '⚠ REVIEW'}\n`;
881
+ out += `Policy: ${data.policy_used}\n`;
882
+ out += `Confidence: ${data.confidence}\n\n`;
883
+ if (data.reasons?.length) {
884
+ out += `Reasons:\n`;
885
+ for (const r of data.reasons) out += ` ${r}\n`;
886
+ }
887
+ if (data.software_meta) {
888
+ out += `\nSoftware:\n`;
889
+ out += ` Publisher verified: ${data.software_meta.publisher_verified}\n`;
890
+ out += ` Provenance verified: ${data.software_meta.provenance_verified}\n`;
891
+ out += ` Permission class: ${data.software_meta.permission_class || 'unknown'}\n`;
892
+ }
893
+ out += `\n(Legacy compatibility score (fallback only): ${data.score}/100)\n`;
894
+ return out;
895
+ }
896
+
897
+ case 'ep_principal_lookup': {
898
+ const data = await epFetch(`/api/identity/principal/${encodeURIComponent(args.principal_id)}`);
899
+ if (data.error) return `Principal not found: ${args.principal_id}`;
900
+ const p = data.principal;
901
+ let out = `Principal: ${p.display_name} (${p.principal_id})\n`;
902
+ out += `Type: ${p.principal_type} · Status: ${p.status}\n`;
903
+ if (p.bootstrap_verified) out += `Bootstrap verified: yes\n`;
904
+ if (data.entities?.length) {
905
+ out += `\nControlled entities (${data.entities.length}):\n`;
906
+ for (const e of data.entities) out += ` ${e.display_name} (${e.entity_id}) — ${e.entity_type}\n`;
907
+ }
908
+ if (data.bindings?.length) {
909
+ out += `\nIdentity bindings (${data.bindings.length}):\n`;
910
+ for (const b of data.bindings) out += ` ${b.binding_type}: ${b.binding_target} [${b.status}] provenance: ${b.provenance}\n`;
911
+ }
912
+ if (data.continuity_claims?.length) {
913
+ out += `\nContinuity history (${data.continuity_claims.length}):\n`;
914
+ for (const c of data.continuity_claims) out += ` ${c.old_entity_id} → ${c.new_entity_id} (${c.reason}) [${c.status}]\n`;
915
+ }
916
+ return out;
917
+ }
918
+
919
+ case 'ep_lineage': {
920
+ const data = await epFetch(`/api/identity/lineage/${encodeURIComponent(args.entity_id)}`);
921
+ let out = `Lineage: ${data.entity_id}\n`;
922
+ if (data.predecessors?.length) {
923
+ out += `\nPredecessors:\n`;
924
+ for (const p of data.predecessors) out += ` ← ${p.from} (${p.reason}) [${p.status}] transfer: ${p.transfer_policy || 'pending'}\n`;
925
+ } else {
926
+ out += `\nNo predecessors — this is an original entity.\n`;
927
+ }
928
+ if (data.successors?.length) {
929
+ out += `\nSuccessors:\n`;
930
+ for (const s of data.successors) out += ` → ${s.to} (${s.reason}) [${s.status}] transfer: ${s.transfer_policy || 'pending'}\n`;
931
+ } else {
932
+ out += `No successors.\n`;
933
+ }
934
+ return out;
935
+ }
936
+
937
+ case 'ep_list_policies': {
938
+ const data = await epFetch('/api/policies');
939
+ let out = `Available Trust Policies (${data.policies.length})\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
940
+ for (const p of data.policies) {
941
+ out += `\n${p.name} [${p.family}]\n`;
942
+ out += ` ${p.description}\n`;
943
+ if (p.min_confidence) out += ` min confidence: ${p.min_confidence}\n`;
944
+ }
945
+ out += `\nUse ep_trust_evaluate with a policy name to evaluate an entity.`;
946
+ return out;
947
+ }
948
+
949
+ case 'ep_create_delegation': {
950
+ if (!API_KEY) return 'Error: EP_API_KEY required to create delegations.';
951
+ const body = {
952
+ principal_id: args.principal_id,
953
+ agent_entity_id: args.agent_entity_id,
954
+ scope: args.scope,
955
+ max_value_usd: args.max_value_usd || null,
956
+ expires_at: args.expires_at || null,
957
+ constraints: args.constraints || null,
958
+ };
959
+ const data = await epFetch('/api/delegations/create', { method: 'POST', auth: true, body });
960
+ return `Delegation created.\n` +
961
+ `ID: ${data.delegation_id}\n` +
962
+ `Principal: ${data.principal_id}\n` +
963
+ `Agent: ${data.agent_entity_id}\n` +
964
+ `Scope: ${typeof data.scope === 'object' ? JSON.stringify(data.scope) : String(data.scope)}\n` +
965
+ `Expires: ${data.expires_at}\n` +
966
+ `Status: ${data.status}`;
967
+ }
968
+
969
+ case 'ep_verify_delegation': {
970
+ const params = new URLSearchParams();
971
+ if (args.action_type) params.set('action_type', args.action_type);
972
+ const data = await epFetch(`/api/delegations/${encodeURIComponent(args.delegation_id)}/verify?${params}`);
973
+ let out = `Delegation: ${data.delegation_id}\n`;
974
+ out += `Status: ${data.valid ? '✓ VALID' : '✗ ' + (data.status || 'INVALID')}\n`;
975
+ if (data.principal_id) out += `Principal: ${data.principal_id}\n`;
976
+ if (data.agent_entity_id) out += `Agent: ${data.agent_entity_id}\n`;
977
+ if (data.scope) out += `Scope: ${typeof data.scope === 'object' ? JSON.stringify(data.scope) : String(data.scope)}\n`;
978
+ if (data.expires_at) out += `Expires: ${data.expires_at}\n`;
979
+ if (data.action_type && data.action_permitted != null) {
980
+ out += `Action "${data.action_type}": ${data.action_permitted ? '✓ Permitted' : '✗ Not in scope'}\n`;
981
+ }
982
+ if (data.reason) out += `Reason: ${data.reason}\n`;
983
+ return out;
984
+ }
985
+
986
+ case 'ep_trust_gate': {
987
+ const body = {
988
+ entity_id: args.entity_id,
989
+ action: args.action,
990
+ policy: args.policy || 'standard',
991
+ value_usd: args.value_usd || null,
992
+ delegation_id: args.delegation_id || null,
993
+ };
994
+ const data = await epFetch('/api/trust/gate', { method: 'POST', body });
995
+ let out = `Trust Gate: ${args.action}\n`;
996
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
997
+ out += `Entity: ${data.entity_id}\n`;
998
+ out += `Decision: ${data.decision === 'allow' ? '✓ ALLOW' : data.decision === 'review' ? '⚠ REVIEW' : '✗ DENY'}\n`;
999
+ out += `Policy: ${data.policy_used}\n`;
1000
+ out += `Confidence: ${data.confidence}\n`;
1001
+ if (data.delegation_verified != null) out += `Delegation: ${data.delegation_verified ? '✓ Verified' : '✗ Not verified'}\n`;
1002
+ if (data.reasons?.length) {
1003
+ out += `\nReasons:\n`;
1004
+ for (const r of data.reasons) out += ` ${data.decision === 'allow' ? '✓' : '✗'} ${r}\n`;
1005
+ }
1006
+ if (data.decision === 'deny' && data.appeal_path) {
1007
+ out += `\nAppeal path: ${data.appeal_path}\n`;
1008
+ out += `Trust must never be more powerful than appeal.\n`;
1009
+ }
1010
+ return out;
1011
+ }
1012
+
1013
+ case 'ep_batch_submit': {
1014
+ if (!API_KEY) return 'Error: EP_API_KEY required for batch submission.';
1015
+ const receipts = (args.receipts || []).slice(0, 50);
1016
+ if (!receipts.length) return 'Error: No receipts provided.';
1017
+ const data = await epFetch('/api/receipts/batch', { method: 'POST', auth: true, body: { receipts } });
1018
+ const results = data.results || [];
1019
+ const succeeded = results.filter(r => r.success).length;
1020
+ const failed = results.filter(r => !r.success).length;
1021
+ let out = `Batch submission: ${succeeded} succeeded, ${failed} failed\n`;
1022
+ for (const r of results) {
1023
+ out += r.success
1024
+ ? ` ✓ ${r.entity_id} — ${r.receipt_id}\n`
1025
+ : ` ✗ ${r.entity_id} — ${r.error}\n`;
1026
+ }
1027
+ return out;
1028
+ }
1029
+
1030
+ case 'ep_domain_score': {
1031
+ const params = new URLSearchParams();
1032
+ if (args.domains?.length) params.set('domains', args.domains.join(','));
1033
+ const data = await epFetch(`/api/trust/domain-score/${encodeURIComponent(args.entity_id)}?${params}`);
1034
+ let out = `Domain Scores: ${data.entity_id}\n`;
1035
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
1036
+ const domains = data.domains || {};
1037
+ for (const [domain, score] of Object.entries(domains)) {
1038
+ out += `\n${domain}:\n`;
1039
+ out += ` Confidence: ${score.confidence ?? 'pending'}\n`;
1040
+ out += ` Evidence: ${score.evidence_count ?? 0} receipts\n`;
1041
+ if (score.completion_rate != null) out += ` Completion: ${score.completion_rate}%\n`;
1042
+ if (score.dispute_rate != null) out += ` Dispute rate: ${score.dispute_rate}%\n`;
1043
+ }
1044
+ if (!Object.keys(domains).length) out += 'No domain data available yet.\n';
1045
+ return out;
1046
+ }
1047
+
1048
+ case 'ep_configure_auto_receipt': {
1049
+ const previousState = autoReceipt.optIn;
1050
+ autoReceipt.configure(args.enabled, args.entity_id);
1051
+ const stateChanged = previousState !== autoReceipt.optIn;
1052
+ if (args.enabled) {
1053
+ return (
1054
+ `Auto-receipt enabled for entity: ${args.entity_id}\n` +
1055
+ `Every EP tool call will now generate a behavioral receipt automatically.\n` +
1056
+ `Receipts are privacy-preserving (sensitive fields redacted) and marked unilateral.\n` +
1057
+ `${stateChanged ? 'Status changed from disabled → enabled.' : 'Was already enabled.'}\n` +
1058
+ `To disable: call ep_configure_auto_receipt with enabled: false.`
1059
+ );
1060
+ } else {
1061
+ return (
1062
+ `Auto-receipt disabled.\n` +
1063
+ `${stateChanged ? 'Status changed from enabled → disabled.' : 'Was already disabled.'}\n` +
1064
+ `No further automatic receipts will be generated for this session.`
1065
+ );
1066
+ }
1067
+ }
1068
+
1069
+ case 'ep_delegation_judgment': {
1070
+ const data = await epFetch(`/api/attribution/delegation-judgment/${encodeURIComponent(args.principal_id)}`);
1071
+ let out = `Delegation Authority: ${args.principal_id}\n`;
1072
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
1073
+ if (data.judgment_score == null) {
1074
+ out += `Score: pending (no delegation signals yet)\n`;
1075
+ } else {
1076
+ const pct = Math.round(data.judgment_score * 100);
1077
+ const label = pct >= 80 ? 'High' : pct >= 50 ? 'Moderate' : 'Low';
1078
+ out += `Score: ${pct}/100 (${label} judgment)\n`;
1079
+ }
1080
+ out += `Agents authorized: ${data.agents_authorized ?? 0}\n`;
1081
+ if (data.good_outcome_rate != null) {
1082
+ out += `Good outcome rate: ${Math.round(data.good_outcome_rate * 100)}%\n`;
1083
+ }
1084
+ out += `Total signals: ${data.total_signals ?? 0}`;
1085
+ if (data.total_signals > 0) {
1086
+ out += ` (${data.positive_signals} positive, ${data.negative_signals} negative)`;
1087
+ }
1088
+ out += `\n`;
1089
+ out += `\nSignal weight: 0.15 per delegation outcome (weak signal by design).\n`;
1090
+ out += `A pattern of low judgment signals that a principal repeatedly authorizes misbehaving agents.`;
1091
+ return out;
1092
+ }
1093
+
1094
+ case 'ep_generate_zk_proof': {
1095
+ if (!API_KEY) return 'Error: EP_API_KEY required to generate commitment proofs.';
1096
+ const body = {
1097
+ entity_id: args.entity_id,
1098
+ claim: {
1099
+ type: args.claim_type,
1100
+ threshold: args.threshold,
1101
+ ...(args.domain ? { domain: args.domain } : {}),
1102
+ },
1103
+ };
1104
+ let data;
1105
+ try {
1106
+ data = await epFetch('/api/trust/zk-proof', { method: 'POST', auth: true, body });
1107
+ } catch (err) {
1108
+ if (err.message?.includes('CLAIM_NOT_PROVABLE') || err.message?.includes('claim_not_provable')) {
1109
+ return (
1110
+ `Commitment Proof: Claim Not Provable\n` +
1111
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n` +
1112
+ `Your current trust data does not meet the specified threshold.\n` +
1113
+ `Claim: ${args.claim_type} > ${args.threshold}` +
1114
+ (args.domain ? ` in ${args.domain}` : '') + `\n` +
1115
+ `Accumulate more receipts or lower the threshold and try again.`
1116
+ );
1117
+ }
1118
+ throw err;
1119
+ }
1120
+ let out = `Commitment Proof Generated\n`;
1121
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
1122
+ out += `Proof ID: ${data.proof_id}\n`;
1123
+ out += `Entity: ${data.entity_id}\n`;
1124
+ out += `Claim: ${data.claim.type} > ${data.claim.threshold}`;
1125
+ if (data.claim.domain) out += ` (${data.claim.domain})`;
1126
+ out += `\n`;
1127
+ out += `Receipts: ${data.receipt_count} (hidden from verifiers)\n`;
1128
+ out += `Expires: ${data.expires_at}\n`;
1129
+ if (data.anchor_block) out += `Anchor: ${data.anchor_block}\n`;
1130
+ out += `\nShare this proof_id with any verifier:\n ${data.proof_id}\n`;
1131
+ out += `\nThe verifier calls ep_verify_zk_proof with only the proof_id.\n`;
1132
+ out += `They confirm your claim without seeing your receipt history, counterparties, or transaction details.`;
1133
+ return out;
1134
+ }
1135
+
1136
+ case 'ep_verify_zk_proof': {
1137
+ const params = new URLSearchParams({ proof_id: args.proof_id });
1138
+ const data = await epFetch(`/api/trust/zk-proof?${params}`);
1139
+ let out = `Commitment Proof Verification\n`;
1140
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
1141
+ out += `Proof ID: ${data.proof_id}\n`;
1142
+ out += `Entity: ${data.entity_id}\n`;
1143
+ out += `Result: ${data.valid ? '✓ VALID' : '✗ INVALID'}\n`;
1144
+ if (data.claim) {
1145
+ out += `Claim: ${data.claim.type} > ${data.claim.threshold}`;
1146
+ if (data.claim.domain) out += ` (${data.claim.domain})`;
1147
+ out += `\n`;
1148
+ }
1149
+ out += `Verified at: ${data.verified_at}\n`;
1150
+ if (data.valid) {
1151
+ out += `Receipt count: ${data.receipt_count} (counterparties and contents remain hidden)\n`;
1152
+ out += `Expires: ${data.expires_at}\n`;
1153
+ if (data.anchor_block) out += `Anchor: ${data.anchor_block}\n`;
1154
+ out += `\n✓ The entity has proven the stated trust claim.\n`;
1155
+ out += `You have learned nothing about their transaction history or counterparties.`;
1156
+ } else {
1157
+ out += `Reason: ${data.reason || 'unknown'}\n`;
1158
+ if (data.expired_at) out += `Expired: ${data.expired_at}\n`;
1159
+ out += `\n${data._note || 'Proof could not be verified.'}`;
1160
+ }
1161
+ return out;
1162
+ }
1163
+
1164
+ case 'ep_issue_commit': {
1165
+ if (!API_KEY) return 'Error: EP_API_KEY required to issue commits.';
1166
+ const body = {
1167
+ action_type: args.action_type,
1168
+ entity_id: args.entity_id,
1169
+ principal_id: args.principal_id || null,
1170
+ counterparty_entity_id: args.counterparty_entity_id || null,
1171
+ delegation_id: args.delegation_id || null,
1172
+ scope: args.scope || null,
1173
+ max_value_usd: args.max_value_usd || null,
1174
+ context: args.context || null,
1175
+ policy: args.policy || 'standard',
1176
+ };
1177
+ const data = await epFetch('/api/commit/issue', { method: 'POST', auth: true, body });
1178
+ const commit = data.commit;
1179
+ let out = `EP Commit Issued\n`;
1180
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
1181
+ out += `Commit ID: ${commit.commit_id}\n`;
1182
+ out += `Decision: ${data.decision === 'allow' ? '✓ ALLOW' : data.decision === 'deny' ? '✗ DENY' : '⚠ REVIEW'}\n`;
1183
+ out += `Expires: ${commit.expires_at}\n`;
1184
+ if (commit.scope) out += `Scope: ${typeof commit.scope === 'object' ? JSON.stringify(commit.scope) : commit.scope}\n`;
1185
+ if (commit.appeal_path) out += `Appeal: ${commit.appeal_path}\n`;
1186
+ return out;
1187
+ }
1188
+
1189
+ case 'ep_verify_commit': {
1190
+ const data = await epFetch('/api/commit/verify', { method: 'POST', body: { commit_id: args.commit_id } });
1191
+ let out = `Commit Verification\n`;
1192
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
1193
+ out += `Commit ID: ${args.commit_id}\n`;
1194
+ out += `Valid: ${data.valid ? '✓ YES' : '✗ NO'}\n`;
1195
+ out += `Status: ${data.status}\n`;
1196
+ out += `Decision: ${data.decision}\n`;
1197
+ if (data.expires_at) out += `Expires: ${data.expires_at}\n`;
1198
+ return out;
1199
+ }
1200
+
1201
+ case 'ep_get_commit_status': {
1202
+ if (!API_KEY) return 'Error: EP_API_KEY required to get commit status.';
1203
+ const data = await epFetch(`/api/commit/${encodeURIComponent(args.commit_id)}`, { auth: true });
1204
+ const commit = data.commit;
1205
+ let out = `Commit: ${commit.commit_id}\n`;
1206
+ out += `Status: ${commit.status}\n`;
1207
+ out += `Action type: ${commit.action_type}\n`;
1208
+ out += `Entity: ${commit.entity_id}\n`;
1209
+ if (commit.decision) out += `Decision: ${commit.decision}\n`;
1210
+ if (commit.expires_at) out += `Expires: ${commit.expires_at}\n`;
1211
+ if (commit.receipt_id) out += `Receipt: ${commit.receipt_id}\n`;
1212
+ return out;
1213
+ }
1214
+
1215
+ case 'ep_revoke_commit': {
1216
+ if (!API_KEY) return 'Error: EP_API_KEY required to revoke commits.';
1217
+ const data = await epFetch(`/api/commit/${encodeURIComponent(args.commit_id)}/revoke`, {
1218
+ method: 'POST', auth: true, body: { reason: args.reason },
1219
+ });
1220
+ return `Commit revoked.\n` +
1221
+ `Commit ID: ${data.commit_id}\n` +
1222
+ `Status: ${data.status}\n` +
1223
+ `Reason: ${args.reason}`;
1224
+ }
1225
+
1226
+ case 'ep_bind_receipt_to_commit': {
1227
+ if (!API_KEY) return 'Error: EP_API_KEY required to bind receipts.';
1228
+ const data = await epFetch(`/api/commit/${encodeURIComponent(args.commit_id)}/receipt`, {
1229
+ method: 'POST', auth: true, body: { receipt_id: args.receipt_id },
1230
+ });
1231
+ return `Receipt bound to commit.\n` +
1232
+ `Commit ID: ${data.commit_id}\n` +
1233
+ `Receipt ID: ${data.receipt_id}\n` +
1234
+ `Status: ${data.status}`;
1235
+ }
1236
+
1237
+ case 'ep_initiate_handshake': {
1238
+ if (!API_KEY) return 'Error: EP_API_KEY required to initiate handshakes.';
1239
+ const body = {
1240
+ mode: args.mode,
1241
+ policy_id: args.policy_id,
1242
+ parties: args.parties,
1243
+ binding: args.binding || null,
1244
+ interaction_id: args.interaction_id || null,
1245
+ };
1246
+ const data = await epFetch('/api/handshake', { method: 'POST', auth: true, body });
1247
+ let out = `Handshake Initiated\n`;
1248
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
1249
+ out += `Handshake ID: ${data.handshake_id}\n`;
1250
+ out += `Mode: ${data.mode}\n`;
1251
+ out += `Policy: ${data.policy_id}\n`;
1252
+ out += `Status: ${data.status}\n`;
1253
+ if (data.parties?.length) {
1254
+ out += `Parties: ${data.parties.map(p => `${p.entity_ref} (${p.role})`).join(', ')}\n`;
1255
+ }
1256
+ out += `\nNext: each party calls ep_add_presentation to submit identity proofs.`;
1257
+ return out;
1258
+ }
1259
+
1260
+ case 'ep_add_presentation': {
1261
+ if (!API_KEY) return 'Error: EP_API_KEY required to add presentations.';
1262
+ const body = {
1263
+ party_role: args.party_role,
1264
+ presentation_type: args.presentation_type,
1265
+ issuer_ref: args.issuer_ref || null,
1266
+ claims: args.claims,
1267
+ disclosure_mode: args.disclosure_mode || null,
1268
+ };
1269
+ const data = await epFetch(`/api/handshake/${encodeURIComponent(args.handshake_id)}/present`, {
1270
+ method: 'POST', auth: true, body,
1271
+ });
1272
+ return `Presentation added.\n` +
1273
+ `Presentation ID: ${data.presentation_id}\n` +
1274
+ `Handshake: ${data.handshake_id}\n` +
1275
+ `Party role: ${data.party_role}\n` +
1276
+ `Type: ${data.presentation_type}\n` +
1277
+ `When all parties have presented, call ep_verify_handshake to evaluate.`;
1278
+ }
1279
+
1280
+ case 'ep_verify_handshake': {
1281
+ if (!API_KEY) return 'Error: EP_API_KEY required to verify handshakes.';
1282
+ const data = await epFetch(`/api/handshake/${encodeURIComponent(args.handshake_id)}/verify`, {
1283
+ method: 'POST', auth: true,
1284
+ });
1285
+ let out = `Handshake Verification\n`;
1286
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
1287
+ out += `Handshake ID: ${data.handshake_id}\n`;
1288
+ out += `Result: ${data.result === 'accepted' ? '✓ ACCEPTED' : data.result === 'rejected' ? '✗ REJECTED' : '⚠ PARTIAL'}\n`;
1289
+ if (data.reason_codes?.length) {
1290
+ out += `Reasons:\n`;
1291
+ for (const r of data.reason_codes) out += ` ${r}\n`;
1292
+ }
1293
+ if (data.evaluated_at) out += `Evaluated at: ${data.evaluated_at}\n`;
1294
+ return out;
1295
+ }
1296
+
1297
+ case 'ep_get_handshake': {
1298
+ if (!API_KEY) return 'Error: EP_API_KEY required to get handshake details.';
1299
+ const data = await epFetch(`/api/handshake/${encodeURIComponent(args.handshake_id)}`, { auth: true });
1300
+ let out = `Handshake: ${data.handshake_id}\n`;
1301
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
1302
+ out += `Mode: ${data.mode}\n`;
1303
+ out += `Policy: ${data.policy_id}\n`;
1304
+ out += `Status: ${data.status}\n`;
1305
+ if (data.parties?.length) {
1306
+ out += `\nParties:\n`;
1307
+ for (const p of data.parties) {
1308
+ out += ` ${p.entity_ref} (${p.role}) — ${p.presented ? 'presented' : 'awaiting'}\n`;
1309
+ }
1310
+ }
1311
+ if (data.presentations?.length) {
1312
+ out += `\nPresentations (${data.presentations.length}):\n`;
1313
+ for (const pr of data.presentations) {
1314
+ out += ` ${pr.party_role}: ${pr.presentation_type} [${pr.disclosure_mode || 'full'}]\n`;
1315
+ }
1316
+ }
1317
+ if (data.result) {
1318
+ out += `\nResult: ${data.result.outcome}\n`;
1319
+ if (data.result.reason_codes?.length) {
1320
+ for (const r of data.result.reason_codes) out += ` ${r}\n`;
1321
+ }
1322
+ }
1323
+ return out;
1324
+ }
1325
+
1326
+ case 'ep_revoke_handshake': {
1327
+ if (!API_KEY) return 'Error: EP_API_KEY required to revoke handshakes.';
1328
+ const data = await epFetch(`/api/handshake/${encodeURIComponent(args.handshake_id)}/revoke`, {
1329
+ method: 'POST', auth: true, body: { reason: args.reason },
1330
+ });
1331
+ return `Handshake revoked.\n` +
1332
+ `Handshake ID: ${data.handshake_id}\n` +
1333
+ `Status: ${data.status}\n` +
1334
+ `Reason: ${args.reason}`;
329
1335
  }
330
1336
 
331
1337
  default:
@@ -337,117 +1343,277 @@ async function handleTool(name, args) {
337
1343
  // Formatters
338
1344
  // =============================================================================
339
1345
 
340
- function formatScore(data) {
341
- let out = `EMILIA Score for ${data.display_name} (${data.entity_id})\n`;
1346
+ function formatTrustProfile(data) {
1347
+ let out = `Trust Profile: ${data.display_name} (${data.entity_id})\n`;
342
1348
  out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
343
- out += `Score: ${data.emilia_score}/100`;
344
- out += data.established ? ' (established)' : ' (new entity — score dampened)';
345
- out += `\nType: ${data.entity_type}\n`;
346
- out += `Total Receipts: ${data.total_receipts}\n`;
347
- out += `Verified: ${data.verified ? 'Yes' : 'No'}\n`;
348
-
349
- if (data.breakdown) {
350
- out += `\nBreakdown:\n`;
351
- out += ` Delivery Accuracy: ${data.breakdown.delivery_accuracy ?? 'N/A'}\n`;
352
- out += ` Product Accuracy: ${data.breakdown.product_accuracy ?? 'N/A'}\n`;
353
- out += ` Price Integrity: ${data.breakdown.price_integrity ?? 'N/A'}\n`;
354
- out += ` Return Processing: ${data.breakdown.return_processing ?? 'N/A'}\n`;
355
- out += ` Agent Satisfaction: ${data.breakdown.agent_satisfaction ?? 'N/A'}\n`;
356
- out += ` Consistency: ${data.breakdown.consistency ?? 'N/A'}\n`;
1349
+ out += `Confidence: ${data.current_confidence}\n`;
1350
+ out += `Established: ${data.historical_establishment ? 'Yes' : 'No'}\n`;
1351
+ out += `Evidence: ${data.effective_evidence_current} (current) / ${data.effective_evidence_historical} (historical)\n`;
1352
+ out += `Receipts: ${data.receipt_count ?? 'N/A'} from ${data.unique_submitters ?? 'N/A'} submitters\n`;
1353
+
1354
+ const p = data.trust_profile;
1355
+ if (p) {
1356
+ if (p.behavioral) {
1357
+ out += `\nBehavioral:\n`;
1358
+ out += ` Completion rate: ${p.behavioral.completion_rate ?? 'N/A'}%\n`;
1359
+ out += ` Retry rate: ${p.behavioral.retry_rate ?? 'N/A'}%\n`;
1360
+ out += ` Abandon rate: ${p.behavioral.abandon_rate ?? 'N/A'}%\n`;
1361
+ out += ` Dispute rate: ${p.behavioral.dispute_rate ?? 'N/A'}%\n`;
1362
+ }
1363
+ if (p.signals) {
1364
+ out += `\nSignals:\n`;
1365
+ out += ` Delivery: ${p.signals.delivery_accuracy ?? 'N/A'}\n`;
1366
+ out += ` Product: ${p.signals.product_accuracy ?? 'N/A'}\n`;
1367
+ out += ` Price: ${p.signals.price_integrity ?? 'N/A'}\n`;
1368
+ out += ` Returns: ${p.signals.return_processing ?? 'N/A'}\n`;
1369
+ }
1370
+ out += ` Consistency: ${p.consistency ?? 'N/A'}\n`;
1371
+ if (p.provenance) {
1372
+ const bd = p.provenance.breakdown || {};
1373
+ const tiers = Object.entries(bd).map(([k,v]) => `${k}: ${v}`).join(', ');
1374
+ out += `\nProvenance: ${tiers}\n`;
1375
+ if (p.provenance.bilateral_rate != null) out += ` Bilateral rate: ${p.provenance.bilateral_rate}%\n`;
1376
+ }
1377
+ }
1378
+
1379
+ if (data.disputes && data.disputes.total > 0) {
1380
+ out += `\nDisputes: ${data.disputes.total} total, ${data.disputes.active} active, ${data.disputes.reversed} reversed\n`;
357
1381
  }
358
1382
 
359
- if (data.description) {
360
- out += `\n${data.description}\n`;
1383
+ if (data.anomaly) {
1384
+ out += `\n⚠️ ANOMALY: ${data.anomaly.type} (${data.anomaly.delta} points, ${data.anomaly.alert})\n`;
361
1385
  }
362
1386
 
1387
+ out += `\n(Legacy compatibility score (fallback only): ${data.compat_score}/100 — use trust profile for decisions)\n`;
363
1388
  return out;
364
1389
  }
365
1390
 
366
- function formatVerification(data) {
367
- let out = `Verification for ${data.receipt_id}\n`;
1391
+ function formatEvaluation(data) {
1392
+ let out = `Trust Evaluation: ${data.display_name} (${data.entity_id})\n`;
368
1393
  out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
369
- out += `Receipt Hash: ${data.receipt_hash}\n`;
370
- out += `Anchored: ${data.anchored ? 'Yes (on Base L2)' : 'No'}\n`;
371
- out += `Proof Valid: ${data.verified ? '✓ VERIFIED' : '✗ FAILED'}\n`;
372
-
373
- if (data.batch) {
374
- out += `\nBatch Details:\n`;
375
- out += ` Merkle Root: ${data.batch.merkle_root}\n`;
376
- out += ` Leaf Count: ${data.batch.leaf_count}\n`;
377
- if (data.batch.tx_hash) {
378
- out += ` TX Hash: ${data.batch.tx_hash}\n`;
379
- out += ` Explorer: https://basescan.org/tx/${data.batch.tx_hash}\n`;
380
- }
1394
+ out += `Policy: ${data.policy_used}\n`;
1395
+ out += `Decision: ${data.decision === 'allow' ? ' ALLOW' : data.decision === 'review' ? '⚠ REVIEW' : '✗ DENY'}\n`;
1396
+ out += `Confidence: ${data.confidence}\n`;
1397
+ out += `Context: ${JSON.stringify(data.context_used) || 'global'}\n`;
1398
+
1399
+ if (data.failures?.length > 0) {
1400
+ out += `\nFailures:\n`;
1401
+ for (const f of data.failures) out += ` ${f}\n`;
1402
+ }
1403
+ if (data.warnings?.length > 0) {
1404
+ out += `\nWarnings:\n`;
1405
+ for (const w of data.warnings) out += ` ⚠ ${w}\n`;
381
1406
  }
382
1407
 
383
1408
  return out;
384
1409
  }
385
1410
 
386
- function formatSearch(data) {
387
- const entities = data.entities || data.results || [];
388
- if (entities.length === 0) {
389
- return 'No entities found matching your query.';
390
- }
1411
+ // =============================================================================
1412
+ // Server
1413
+ // =============================================================================
391
1414
 
392
- let out = `Found ${entities.length} entities:\n\n`;
393
- for (const e of entities) {
394
- out += `${e.display_name} (${e.entity_id})\n`;
395
- out += ` Score: ${e.emilia_score}/100 | Type: ${e.entity_type} | Receipts: ${e.total_receipts}\n`;
396
- if (e.description) out += ` ${e.description}\n`;
397
- out += `\n`;
398
- }
399
- return out;
400
- }
1415
+ const server = new Server(
1416
+ { name: 'emilia-protocol', version: '1.0.0' },
1417
+ { capabilities: { tools: {}, resources: {}, prompts: {} } }
1418
+ );
401
1419
 
402
- function formatLeaderboard(data) {
403
- if (!data.entities || data.entities.length === 0) {
404
- return 'No entities in the leaderboard yet.';
405
- }
1420
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
406
1421
 
407
- let out = `EMILIA Leaderboard\n`;
408
- out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
409
- for (let i = 0; i < data.entities.length; i++) {
410
- const e = data.entities[i];
411
- out += `#${i + 1} ${e.display_name} ${e.emilia_score}/100 (${e.total_receipts} receipts)\n`;
1422
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1423
+ const { name, arguments: args } = request.params;
1424
+ try {
1425
+ // Wrap handleTool with auto-receipt instrumentation.
1426
+ // The middleware measures latency, generates a receipt draft from the outcome,
1427
+ // and submits it asynchronously — the tool response is never delayed.
1428
+ // ep_configure_auto_receipt itself is excluded from auto-receipt to avoid
1429
+ // a bootstrap loop where enabling the feature immediately records itself.
1430
+ const isMetaTool = name === 'ep_configure_auto_receipt';
1431
+ const invoker = isMetaTool
1432
+ ? (a) => handleTool(name, a)
1433
+ : autoReceipt.wrap(name, (a) => handleTool(name, a));
1434
+
1435
+ const result = await invoker(args || {});
1436
+ return { content: [{ type: 'text', text: result }] };
1437
+ } catch (err) {
1438
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
412
1439
  }
413
- return out;
414
- }
1440
+ });
415
1441
 
416
1442
  // =============================================================================
417
- // Server setup
1443
+ // MCP Resources
418
1444
  // =============================================================================
419
1445
 
420
- const server = new Server(
1446
+ const RESOURCES = [
421
1447
  {
422
- name: 'emilia-protocol',
423
- version: '0.1.0',
1448
+ uri: 'entity://{id}',
1449
+ name: 'Entity Trust Profile',
1450
+ description: 'Full trust profile for any entity by ID or slug. Equivalent to ep_trust_profile tool but as a resource.',
1451
+ mimeType: 'application/json',
424
1452
  },
425
1453
  {
426
- capabilities: {
427
- tools: {},
428
- },
1454
+ uri: 'score://{id}',
1455
+ name: 'Entity Trust Score',
1456
+ description: 'Current trust confidence and score breakdown for an entity.',
1457
+ mimeType: 'application/json',
1458
+ },
1459
+ {
1460
+ uri: 'receipt://{id}',
1461
+ name: 'Receipt',
1462
+ description: 'Full receipt data including hash, provenance, and verification status.',
1463
+ mimeType: 'application/json',
1464
+ },
1465
+ {
1466
+ uri: 'delegation://{id}',
1467
+ name: 'Delegation Record',
1468
+ description: 'Delegation details: principal, agent, scope, expiry, and status.',
1469
+ mimeType: 'application/json',
1470
+ },
1471
+ ];
1472
+
1473
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: RESOURCES }));
1474
+
1475
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1476
+ const { uri } = request.params;
1477
+ let data;
1478
+ let text;
1479
+
1480
+ if (uri.startsWith('entity://')) {
1481
+ const id = uri.slice('entity://'.length);
1482
+ data = await epFetch(`/api/trust/profile/${encodeURIComponent(id)}`);
1483
+ text = JSON.stringify(data, null, 2);
1484
+ } else if (uri.startsWith('score://')) {
1485
+ const id = uri.slice('score://'.length);
1486
+ data = await epFetch(`/api/trust/profile/${encodeURIComponent(id)}`);
1487
+ text = JSON.stringify({
1488
+ entity_id: data.entity_id,
1489
+ confidence: data.current_confidence,
1490
+ effective_evidence: data.effective_evidence_current,
1491
+ established: data.historical_establishment,
1492
+ compat_score: data.compat_score,
1493
+ }, null, 2);
1494
+ } else if (uri.startsWith('receipt://')) {
1495
+ const id = uri.slice('receipt://'.length);
1496
+ data = await epFetch(`/api/verify/${encodeURIComponent(id)}`);
1497
+ text = JSON.stringify(data, null, 2);
1498
+ } else if (uri.startsWith('delegation://')) {
1499
+ const id = uri.slice('delegation://'.length);
1500
+ data = await epFetch(`/api/delegations/${encodeURIComponent(id)}/verify`);
1501
+ text = JSON.stringify(data, null, 2);
1502
+ } else {
1503
+ throw new Error(`Unknown resource URI: ${uri}`);
429
1504
  }
430
- );
431
1505
 
432
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
433
- tools: TOOLS,
434
- }));
1506
+ return { contents: [{ uri, mimeType: 'application/json', text }] };
1507
+ });
435
1508
 
436
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
1509
+ // =============================================================================
1510
+ // MCP Prompts
1511
+ // =============================================================================
1512
+
1513
+ const PROMPTS = [
1514
+ {
1515
+ name: 'trust_decision',
1516
+ description: 'Get a structured prompt for making a trust-based routing or payment decision about an entity.',
1517
+ arguments: [
1518
+ { name: 'entity_id', description: 'Entity to evaluate', required: true },
1519
+ { name: 'action', description: 'Action being considered (e.g. process_payment, install_plugin)', required: true },
1520
+ { name: 'value_usd', description: 'Transaction value in USD', required: false },
1521
+ ],
1522
+ },
1523
+ {
1524
+ name: 'receipt_quality_check',
1525
+ description: 'Get a prompt for evaluating the quality and accuracy of a receipt before submission.',
1526
+ arguments: [
1527
+ { name: 'entity_id', description: 'Entity the receipt is about', required: true },
1528
+ { name: 'transaction_ref', description: 'Transaction reference', required: true },
1529
+ ],
1530
+ },
1531
+ {
1532
+ name: 'install_decision',
1533
+ description: 'Get a structured prompt for deciding whether to install a software package or plugin.',
1534
+ arguments: [
1535
+ { name: 'entity_id', description: 'Software entity ID', required: true },
1536
+ { name: 'install_context', description: 'Where it will be installed (e.g. private_repo, production_server)', required: false },
1537
+ ],
1538
+ },
1539
+ ];
1540
+
1541
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
1542
+
1543
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
437
1544
  const { name, arguments: args } = request.params;
438
- try {
439
- const result = await handleTool(name, args || {});
1545
+
1546
+ if (name === 'trust_decision') {
1547
+ const entity_id = args?.entity_id || '[entity_id]';
1548
+ const action = args?.action || '[action]';
1549
+ const value = args?.value_usd ? ` (value: $${args.value_usd})` : '';
1550
+ return {
1551
+ description: `Trust decision for ${entity_id}`,
1552
+ messages: [{
1553
+ role: 'user',
1554
+ content: {
1555
+ type: 'text',
1556
+ text: `I need to decide whether to allow entity "${entity_id}" to perform "${action}"${value}.\n\n` +
1557
+ `Please:\n` +
1558
+ `1. Call ep_trust_gate with entity_id="${entity_id}", action="${action}"${args?.value_usd ? `, value_usd=${args.value_usd}` : ''}\n` +
1559
+ `2. If the gate passes, call ep_trust_profile to get the full profile\n` +
1560
+ `3. Summarize: ALLOW, REVIEW, or DENY, with the key trust signals that drove the decision\n` +
1561
+ `4. If DENY, explain what the entity would need to do to qualify`,
1562
+ },
1563
+ }],
1564
+ };
1565
+ }
1566
+
1567
+ if (name === 'receipt_quality_check') {
1568
+ const entity_id = args?.entity_id || '[entity_id]';
1569
+ const ref = args?.transaction_ref || '[transaction_ref]';
440
1570
  return {
441
- content: [{ type: 'text', text: result }],
1571
+ description: `Receipt quality check for ${entity_id}`,
1572
+ messages: [{
1573
+ role: 'user',
1574
+ content: {
1575
+ type: 'text',
1576
+ text: `Before I submit a receipt for entity "${entity_id}" (ref: ${ref}), help me ensure it's accurate:\n\n` +
1577
+ `1. Call ep_trust_profile to see their current trust state\n` +
1578
+ `2. Ask me: What was the agent_behavior? (completed/retried_same/retried_different/abandoned/disputed)\n` +
1579
+ `3. Ask me: What signal scores should I set? (delivery_accuracy, product_accuracy, price_integrity, return_processing — each 0-100)\n` +
1580
+ `4. Warn me if any signals seem inconsistent with the agent_behavior\n` +
1581
+ `5. Only submit with ep_submit_receipt when I confirm the data is accurate`,
1582
+ },
1583
+ }],
442
1584
  };
443
- } catch (err) {
1585
+ }
1586
+
1587
+ if (name === 'install_decision') {
1588
+ const entity_id = args?.entity_id || '[entity_id]';
1589
+ const ctx = args?.install_context || 'production';
444
1590
  return {
445
- content: [{ type: 'text', text: `Error: ${err.message}` }],
446
- isError: true,
1591
+ description: `Install decision for ${entity_id}`,
1592
+ messages: [{
1593
+ role: 'user',
1594
+ content: {
1595
+ type: 'text',
1596
+ text: `Should I install "${entity_id}" in my ${ctx} environment?\n\n` +
1597
+ `Please:\n` +
1598
+ `1. Call ep_install_preflight with entity_id="${entity_id}" (pre-action enforcement)\n` +
1599
+ `2. Call ep_lineage to check for suspicious continuity gaps\n` +
1600
+ `3. Call ep_trust_profile for full behavioral history\n` +
1601
+ `4. Give me a clear INSTALL / REVIEW / DENY recommendation with reasons\n` +
1602
+ `5. If REVIEW or DENY, list specific questions to investigate before proceeding`,
1603
+ },
1604
+ }],
447
1605
  };
448
1606
  }
1607
+
1608
+ throw new Error(`Unknown prompt: ${name}`);
449
1609
  });
450
1610
 
1611
+ // =============================================================================
451
1612
  // Start
1613
+ // =============================================================================
1614
+
452
1615
  const transport = new StdioServerTransport();
453
1616
  await server.connect(transport);
1617
+
1618
+ // Exported for integration tests only.
1619
+ export { handleTool };