@emilia-protocol/mcp-server 0.1.0 → 1.0.1

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