@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.
- package/LICENSE +190 -0
- package/README.md +969 -34
- package/auto-receipt.js +402 -0
- package/index.js +1552 -246
- 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
|
|
7
|
-
* Add this server to any MCP-compatible client
|
|
8
|
-
* to
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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_profile — Get 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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
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: '
|
|
117
|
+
name: 'ep_guard_action',
|
|
80
118
|
description:
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'
|
|
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: '
|
|
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: '
|
|
177
|
+
name: 'ep_trust_evaluate',
|
|
97
178
|
description:
|
|
98
|
-
'
|
|
99
|
-
'
|
|
100
|
-
'
|
|
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: '
|
|
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
|
-
|
|
218
|
+
agent_behavior: {
|
|
115
219
|
type: 'string',
|
|
116
|
-
|
|
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
|
-
|
|
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: '
|
|
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
|
|
150
|
-
'
|
|
151
|
-
'
|
|
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: '
|
|
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
|
-
'
|
|
167
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
348
|
+
enum: ['wrongly_downgraded', 'harmed_by_trusted_entity', 'fraudulent_entity', 'inaccurate_profile', 'other'],
|
|
174
349
|
},
|
|
175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
181
|
-
type: '
|
|
182
|
-
description: '
|
|
388
|
+
context: {
|
|
389
|
+
type: 'object',
|
|
390
|
+
description: 'Install context: { host, install_scope, permission_class, data_sensitivity, execution_mode }',
|
|
183
391
|
},
|
|
184
392
|
},
|
|
185
|
-
required: ['
|
|
393
|
+
required: ['entity_id'],
|
|
186
394
|
},
|
|
187
395
|
},
|
|
396
|
+
// EP-IX Identity Continuity
|
|
188
397
|
{
|
|
189
|
-
name: '
|
|
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
|
-
'
|
|
192
|
-
'
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
532
|
+
entity_id: {
|
|
210
533
|
type: 'string',
|
|
211
|
-
description: '
|
|
534
|
+
description: 'Entity ID to attribute auto-generated receipts to (your EP entity slug)',
|
|
212
535
|
},
|
|
213
|
-
|
|
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: '
|
|
556
|
+
description: 'Domains to query. If omitted, returns all domains.',
|
|
217
557
|
},
|
|
218
|
-
|
|
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: '
|
|
580
|
+
description: 'Your EP entity ID. Must match the API key used to authenticate.',
|
|
221
581
|
},
|
|
222
|
-
|
|
582
|
+
claim_type: {
|
|
223
583
|
type: 'string',
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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', '
|
|
605
|
+
required: ['entity_id', 'claim_type', 'threshold'],
|
|
232
606
|
},
|
|
233
607
|
},
|
|
234
608
|
{
|
|
235
|
-
name: '
|
|
609
|
+
name: 'ep_verify_zk_proof',
|
|
236
610
|
description:
|
|
237
|
-
'
|
|
238
|
-
'Returns
|
|
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
|
-
|
|
243
|
-
type: '
|
|
244
|
-
description: '
|
|
620
|
+
proof_id: {
|
|
621
|
+
type: 'string',
|
|
622
|
+
description: 'The proof identifier (ep_zkp_...) shared by the proving entity.',
|
|
245
623
|
},
|
|
246
|
-
|
|
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
|
-
|
|
249
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
287
|
-
`
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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 '
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
|
341
|
-
let out = `
|
|
1465
|
+
function formatTrustProfile(data) {
|
|
1466
|
+
let out = `Trust Profile: ${data.display_name} (${data.entity_id})\n`;
|
|
342
1467
|
out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
343
|
-
out += `
|
|
344
|
-
out += data.
|
|
345
|
-
out +=
|
|
346
|
-
out += `
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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.
|
|
360
|
-
out += `\
|
|
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
|
|
367
|
-
let out = `
|
|
1510
|
+
function formatEvaluation(data) {
|
|
1511
|
+
let out = `Trust Evaluation: ${data.display_name} (${data.entity_id})\n`;
|
|
368
1512
|
out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
369
|
-
out += `
|
|
370
|
-
out += `
|
|
371
|
-
out += `
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
out +=
|
|
376
|
-
out += `
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return 'No entities found matching your query.';
|
|
390
|
-
}
|
|
1530
|
+
// =============================================================================
|
|
1531
|
+
// Server
|
|
1532
|
+
// =============================================================================
|
|
391
1533
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
414
|
-
}
|
|
1580
|
+
});
|
|
415
1581
|
|
|
416
1582
|
// =============================================================================
|
|
417
|
-
//
|
|
1583
|
+
// MCP Resources
|
|
418
1584
|
// =============================================================================
|
|
419
1585
|
|
|
420
|
-
const
|
|
1586
|
+
const RESOURCES = [
|
|
421
1587
|
{
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
427
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
}));
|
|
1646
|
+
return { contents: [{ uri, mimeType: 'application/json', text }] };
|
|
1647
|
+
});
|
|
435
1648
|
|
|
436
|
-
|
|
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
|
-
|
|
439
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
446
|
-
|
|
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 };
|