@emilia-protocol/mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +65 -0
  2. package/index.js +453 -0
  3. package/package.json +30 -0
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # EMILIA Protocol MCP Server
2
+
3
+ Trust layer tools for AI agents. Give any MCP-compatible client (Claude, etc.) the ability to check EMILIA Scores before transacting.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx @emilia-protocol/mcp-server
9
+ ```
10
+
11
+ ## Claude Desktop Config
12
+
13
+ Add to `~/.claude/claude_desktop_config.json`:
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "emilia": {
19
+ "command": "npx",
20
+ "args": ["@emilia-protocol/mcp-server"],
21
+ "env": {
22
+ "EP_BASE_URL": "https://emiliaprotocol.ai",
23
+ "EP_API_KEY": "ep_live_your_key_here"
24
+ }
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ `EP_API_KEY` is only required for write operations (submit receipt, register entity). Score lookup and verification are public.
31
+
32
+ ## Tools
33
+
34
+ | Tool | Auth | Description |
35
+ |------|------|-------------|
36
+ | `ep_score_lookup` | None | Check any entity's EMILIA Score |
37
+ | `ep_submit_receipt` | API Key | Submit a transaction receipt |
38
+ | `ep_verify_receipt` | None | Verify receipt against on-chain Merkle root |
39
+ | `ep_search_entities` | None | Search entities by name/capability |
40
+ | `ep_register_entity` | API Key | Register a new entity |
41
+ | `ep_leaderboard` | None | Get top-scored entities |
42
+
43
+ ## Example Usage (in Claude)
44
+
45
+ > "Check the EMILIA Score for rex-booking-v1 before I book with them."
46
+
47
+ > "Submit a receipt for my last purchase from entity abc-merchant-v1. Delivery was on time (95), product matched the listing (88), price was honored (100)."
48
+
49
+ > "Find me a booking agent with an EMILIA Score above 80."
50
+
51
+ ## Self-Hosted
52
+
53
+ Point `EP_BASE_URL` to your own EP implementation:
54
+
55
+ ```json
56
+ {
57
+ "env": {
58
+ "EP_BASE_URL": "https://your-instance.example.com"
59
+ }
60
+ }
61
+ ```
62
+
63
+ ## License
64
+
65
+ Apache-2.0
package/index.js ADDED
@@ -0,0 +1,453 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * EMILIA Protocol — MCP Server
5
+ *
6
+ * Trust layer tools for AI agents.
7
+ * Add this server to any MCP-compatible client (Claude, etc.)
8
+ * to give your agent access to EMILIA Scores.
9
+ *
10
+ * Tools provided:
11
+ * ep_score_lookup — Check any entity's EMILIA Score (no auth)
12
+ * ep_submit_receipt — Submit a transaction receipt (requires API key)
13
+ * ep_verify_receipt — Verify a receipt against on-chain Merkle root (no auth)
14
+ * ep_search_entities — Search for entities by name or capability
15
+ * ep_register_entity — Register a new entity in the EMILIA network
16
+ * ep_leaderboard — Get the top-scored entities
17
+ *
18
+ * Setup:
19
+ * EP_BASE_URL=https://emiliaprotocol.ai (or your self-hosted instance)
20
+ * EP_API_KEY=ep_live_... (for write operations)
21
+ *
22
+ * Claude Desktop config (~/.claude/claude_desktop_config.json):
23
+ * {
24
+ * "mcpServers": {
25
+ * "emilia": {
26
+ * "command": "npx",
27
+ * "args": ["@emilia-protocol/mcp-server"],
28
+ * "env": {
29
+ * "EP_BASE_URL": "https://emiliaprotocol.ai",
30
+ * "EP_API_KEY": "ep_live_your_key_here"
31
+ * }
32
+ * }
33
+ * }
34
+ * }
35
+ *
36
+ * @license Apache-2.0
37
+ */
38
+
39
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
40
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
41
+ import {
42
+ CallToolRequestSchema,
43
+ ListToolsRequestSchema,
44
+ } from '@modelcontextprotocol/sdk/types.js';
45
+
46
+ const BASE_URL = process.env.EP_BASE_URL || 'https://emiliaprotocol.ai';
47
+ const API_KEY = process.env.EP_API_KEY || '';
48
+
49
+ // =============================================================================
50
+ // HTTP helpers
51
+ // =============================================================================
52
+
53
+ async function epFetch(path, options = {}) {
54
+ const url = `${BASE_URL}${path}`;
55
+ const headers = {
56
+ 'Content-Type': 'application/json',
57
+ ...(options.auth && API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
58
+ };
59
+
60
+ const res = await fetch(url, {
61
+ method: options.method || 'GET',
62
+ headers,
63
+ body: options.body ? JSON.stringify(options.body) : undefined,
64
+ });
65
+
66
+ const data = await res.json();
67
+ if (!res.ok) {
68
+ throw new Error(data.error || `EP API error: ${res.status}`);
69
+ }
70
+ return data;
71
+ }
72
+
73
+ // =============================================================================
74
+ // Tool definitions
75
+ // =============================================================================
76
+
77
+ const TOOLS = [
78
+ {
79
+ name: 'ep_score_lookup',
80
+ description:
81
+ 'Look up an entity\'s EMILIA Score. Scores are public — no authentication required. ' +
82
+ 'Returns the trust score (0-100), breakdown by signal, receipt count, and verification status. ' +
83
+ 'Use this before transacting with any agent, merchant, or service provider.',
84
+ inputSchema: {
85
+ type: 'object',
86
+ properties: {
87
+ entity_id: {
88
+ type: 'string',
89
+ description: 'The entity ID (slug like "rex-booking-v1") or UUID to look up',
90
+ },
91
+ },
92
+ required: ['entity_id'],
93
+ },
94
+ },
95
+ {
96
+ name: 'ep_submit_receipt',
97
+ description:
98
+ 'Submit a transaction receipt to the EMILIA ledger. Requires an EP API key. ' +
99
+ 'Receipts are append-only, cryptographically hashed, and chain-linked. ' +
100
+ 'Each signal is 0-100: delivery_accuracy, product_accuracy, price_integrity, ' +
101
+ 'return_processing, agent_satisfaction. At least one signal is required.',
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ entity_id: {
106
+ type: 'string',
107
+ description: 'UUID of the entity being scored',
108
+ },
109
+ transaction_type: {
110
+ type: 'string',
111
+ enum: ['purchase', 'service', 'task_completion', 'delivery', 'return'],
112
+ description: 'Type of transaction',
113
+ },
114
+ transaction_ref: {
115
+ type: 'string',
116
+ description: 'External reference (UCP order ID, A2A task ID, etc.)',
117
+ },
118
+ delivery_accuracy: {
119
+ type: 'number',
120
+ description: '0-100: Did it arrive when promised?',
121
+ },
122
+ product_accuracy: {
123
+ type: 'number',
124
+ description: '0-100: Did the listing match reality?',
125
+ },
126
+ price_integrity: {
127
+ type: 'number',
128
+ description: '0-100: Was the price honored?',
129
+ },
130
+ return_processing: {
131
+ type: 'number',
132
+ description: '0-100: Was the return policy followed?',
133
+ },
134
+ agent_satisfaction: {
135
+ type: 'number',
136
+ description: '0-100: Was the purchasing agent satisfied?',
137
+ },
138
+ evidence: {
139
+ type: 'object',
140
+ description: 'Structured evidence — e.g. { promised_delivery: "2d", actual_delivery: "3d" }',
141
+ },
142
+ },
143
+ required: ['entity_id', 'transaction_type'],
144
+ },
145
+ },
146
+ {
147
+ name: 'ep_verify_receipt',
148
+ description:
149
+ 'Verify a receipt against the on-chain Merkle root. No auth required. ' +
150
+ 'Returns the Merkle proof, verification status, and a link to the Base L2 transaction. ' +
151
+ '"Don\'t trust EMILIA. Verify the math yourself."',
152
+ inputSchema: {
153
+ type: 'object',
154
+ properties: {
155
+ receipt_id: {
156
+ type: 'string',
157
+ description: 'The receipt ID (e.g. "ep_rcpt_abc123...")',
158
+ },
159
+ },
160
+ required: ['receipt_id'],
161
+ },
162
+ },
163
+ {
164
+ name: 'ep_search_entities',
165
+ description:
166
+ 'Search for entities in the EMILIA network by name, capability, or category. ' +
167
+ 'Returns matching entities with their scores and capabilities.',
168
+ inputSchema: {
169
+ type: 'object',
170
+ properties: {
171
+ query: {
172
+ type: 'string',
173
+ description: 'Search query — entity name, capability, or category',
174
+ },
175
+ entity_type: {
176
+ type: 'string',
177
+ enum: ['agent', 'merchant', 'service_provider'],
178
+ description: 'Filter by entity type',
179
+ },
180
+ min_score: {
181
+ type: 'number',
182
+ description: 'Minimum EMILIA Score (0-100)',
183
+ },
184
+ },
185
+ required: ['query'],
186
+ },
187
+ },
188
+ {
189
+ name: 'ep_register_entity',
190
+ description:
191
+ 'Register a new entity in the EMILIA network. Requires an EP API key. ' +
192
+ 'Returns the entity ID, entity number, and a new API key for the entity.',
193
+ inputSchema: {
194
+ type: 'object',
195
+ properties: {
196
+ entity_id: {
197
+ type: 'string',
198
+ description: 'Human-readable slug (e.g. "my-agent-v1"). Lowercase, hyphens, no spaces.',
199
+ },
200
+ display_name: {
201
+ type: 'string',
202
+ description: 'Display name (e.g. "My AI Shopping Agent")',
203
+ },
204
+ entity_type: {
205
+ type: 'string',
206
+ enum: ['agent', 'merchant', 'service_provider'],
207
+ description: 'Type of entity',
208
+ },
209
+ description: {
210
+ type: 'string',
211
+ description: 'What this entity does',
212
+ },
213
+ capabilities: {
214
+ type: 'array',
215
+ items: { type: 'string' },
216
+ description: 'List of capabilities (e.g. ["price_comparison", "booking"])',
217
+ },
218
+ website_url: {
219
+ type: 'string',
220
+ description: 'Website URL',
221
+ },
222
+ a2a_endpoint: {
223
+ type: 'string',
224
+ description: 'A2A Agent Card endpoint URL',
225
+ },
226
+ ucp_profile_url: {
227
+ type: 'string',
228
+ description: 'UCP merchant profile URL',
229
+ },
230
+ },
231
+ required: ['entity_id', 'display_name', 'entity_type', 'description'],
232
+ },
233
+ },
234
+ {
235
+ name: 'ep_leaderboard',
236
+ description:
237
+ 'Get the top-scored entities in the EMILIA network. ' +
238
+ 'Returns entities ranked by EMILIA Score with their breakdowns.',
239
+ inputSchema: {
240
+ type: 'object',
241
+ properties: {
242
+ limit: {
243
+ type: 'number',
244
+ description: 'Number of entities to return (default: 10, max: 50)',
245
+ },
246
+ entity_type: {
247
+ type: 'string',
248
+ enum: ['agent', 'merchant', 'service_provider'],
249
+ description: 'Filter by entity type',
250
+ },
251
+ },
252
+ },
253
+ },
254
+ ];
255
+
256
+ // =============================================================================
257
+ // Tool handlers
258
+ // =============================================================================
259
+
260
+ async function handleTool(name, args) {
261
+ switch (name) {
262
+ case 'ep_score_lookup': {
263
+ const data = await epFetch(`/api/score/${encodeURIComponent(args.entity_id)}`);
264
+ return formatScore(data);
265
+ }
266
+
267
+ case 'ep_submit_receipt': {
268
+ if (!API_KEY) {
269
+ return 'Error: EP_API_KEY is required for submitting receipts. Set it in your MCP server config.';
270
+ }
271
+ const data = await epFetch('/api/receipts/submit', {
272
+ method: 'POST',
273
+ auth: true,
274
+ body: {
275
+ entity_id: args.entity_id,
276
+ transaction_type: args.transaction_type,
277
+ transaction_ref: args.transaction_ref,
278
+ delivery_accuracy: args.delivery_accuracy,
279
+ product_accuracy: args.product_accuracy,
280
+ price_integrity: args.price_integrity,
281
+ return_processing: args.return_processing,
282
+ agent_satisfaction: args.agent_satisfaction,
283
+ evidence: args.evidence,
284
+ },
285
+ });
286
+ return `Receipt submitted.\n` +
287
+ `Receipt ID: ${data.receipt.receipt_id}\n` +
288
+ `Composite Score: ${data.receipt.composite_score}\n` +
289
+ `Hash: ${data.receipt.receipt_hash}\n` +
290
+ `Entity Score Updated: ${data.entity_score.emilia_score} (${data.entity_score.total_receipts} receipts)`;
291
+ }
292
+
293
+ case 'ep_verify_receipt': {
294
+ const data = await epFetch(`/api/verify/${encodeURIComponent(args.receipt_id)}`);
295
+ return formatVerification(data);
296
+ }
297
+
298
+ case 'ep_search_entities': {
299
+ const params = new URLSearchParams({ q: args.query });
300
+ if (args.entity_type) params.set('type', args.entity_type);
301
+ if (args.min_score) params.set('min_score', args.min_score.toString());
302
+ const data = await epFetch(`/api/entities/search?${params}`);
303
+ return formatSearch(data);
304
+ }
305
+
306
+ case 'ep_register_entity': {
307
+ if (!API_KEY) {
308
+ return 'Error: EP_API_KEY is required for registration. Set it in your MCP server config.';
309
+ }
310
+ const data = await epFetch('/api/entities/register', {
311
+ method: 'POST',
312
+ auth: true,
313
+ body: args,
314
+ });
315
+ return `Entity registered!\n` +
316
+ `Entity ID: ${data.entity.entity_id}\n` +
317
+ `Entity #: ${data.entity.entity_number || 'N/A'}\n` +
318
+ `EMILIA Score: ${data.entity.emilia_score} (new entity — score starts at 50)\n` +
319
+ `API Key: ${data.api_key}\n\n` +
320
+ `⚠️ Save this API key — it won't be shown again.`;
321
+ }
322
+
323
+ case 'ep_leaderboard': {
324
+ const params = new URLSearchParams();
325
+ if (args?.limit) params.set('limit', Math.min(args.limit, 50).toString());
326
+ if (args?.entity_type) params.set('type', args.entity_type);
327
+ const data = await epFetch(`/api/leaderboard?${params}`);
328
+ return formatLeaderboard(data);
329
+ }
330
+
331
+ default:
332
+ return `Unknown tool: ${name}`;
333
+ }
334
+ }
335
+
336
+ // =============================================================================
337
+ // Formatters
338
+ // =============================================================================
339
+
340
+ function formatScore(data) {
341
+ let out = `EMILIA Score for ${data.display_name} (${data.entity_id})\n`;
342
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
343
+ out += `Score: ${data.emilia_score}/100`;
344
+ out += data.established ? ' (established)' : ' (new entity — score dampened)';
345
+ out += `\nType: ${data.entity_type}\n`;
346
+ out += `Total Receipts: ${data.total_receipts}\n`;
347
+ out += `Verified: ${data.verified ? 'Yes' : 'No'}\n`;
348
+
349
+ if (data.breakdown) {
350
+ out += `\nBreakdown:\n`;
351
+ out += ` Delivery Accuracy: ${data.breakdown.delivery_accuracy ?? 'N/A'}\n`;
352
+ out += ` Product Accuracy: ${data.breakdown.product_accuracy ?? 'N/A'}\n`;
353
+ out += ` Price Integrity: ${data.breakdown.price_integrity ?? 'N/A'}\n`;
354
+ out += ` Return Processing: ${data.breakdown.return_processing ?? 'N/A'}\n`;
355
+ out += ` Agent Satisfaction: ${data.breakdown.agent_satisfaction ?? 'N/A'}\n`;
356
+ out += ` Consistency: ${data.breakdown.consistency ?? 'N/A'}\n`;
357
+ }
358
+
359
+ if (data.description) {
360
+ out += `\n${data.description}\n`;
361
+ }
362
+
363
+ return out;
364
+ }
365
+
366
+ function formatVerification(data) {
367
+ let out = `Verification for ${data.receipt_id}\n`;
368
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
369
+ out += `Receipt Hash: ${data.receipt_hash}\n`;
370
+ out += `Anchored: ${data.anchored ? 'Yes (on Base L2)' : 'No'}\n`;
371
+ out += `Proof Valid: ${data.verified ? '✓ VERIFIED' : '✗ FAILED'}\n`;
372
+
373
+ if (data.batch) {
374
+ out += `\nBatch Details:\n`;
375
+ out += ` Merkle Root: ${data.batch.merkle_root}\n`;
376
+ out += ` Leaf Count: ${data.batch.leaf_count}\n`;
377
+ if (data.batch.tx_hash) {
378
+ out += ` TX Hash: ${data.batch.tx_hash}\n`;
379
+ out += ` Explorer: https://basescan.org/tx/${data.batch.tx_hash}\n`;
380
+ }
381
+ }
382
+
383
+ return out;
384
+ }
385
+
386
+ function formatSearch(data) {
387
+ const entities = data.entities || data.results || [];
388
+ if (entities.length === 0) {
389
+ return 'No entities found matching your query.';
390
+ }
391
+
392
+ let out = `Found ${entities.length} entities:\n\n`;
393
+ for (const e of entities) {
394
+ out += `${e.display_name} (${e.entity_id})\n`;
395
+ out += ` Score: ${e.emilia_score}/100 | Type: ${e.entity_type} | Receipts: ${e.total_receipts}\n`;
396
+ if (e.description) out += ` ${e.description}\n`;
397
+ out += `\n`;
398
+ }
399
+ return out;
400
+ }
401
+
402
+ function formatLeaderboard(data) {
403
+ if (!data.entities || data.entities.length === 0) {
404
+ return 'No entities in the leaderboard yet.';
405
+ }
406
+
407
+ let out = `EMILIA Leaderboard\n`;
408
+ out += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
409
+ for (let i = 0; i < data.entities.length; i++) {
410
+ const e = data.entities[i];
411
+ out += `#${i + 1} ${e.display_name} — ${e.emilia_score}/100 (${e.total_receipts} receipts)\n`;
412
+ }
413
+ return out;
414
+ }
415
+
416
+ // =============================================================================
417
+ // Server setup
418
+ // =============================================================================
419
+
420
+ const server = new Server(
421
+ {
422
+ name: 'emilia-protocol',
423
+ version: '0.1.0',
424
+ },
425
+ {
426
+ capabilities: {
427
+ tools: {},
428
+ },
429
+ }
430
+ );
431
+
432
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
433
+ tools: TOOLS,
434
+ }));
435
+
436
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
437
+ const { name, arguments: args } = request.params;
438
+ try {
439
+ const result = await handleTool(name, args || {});
440
+ return {
441
+ content: [{ type: 'text', text: result }],
442
+ };
443
+ } catch (err) {
444
+ return {
445
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
446
+ isError: true,
447
+ };
448
+ }
449
+ });
450
+
451
+ // Start
452
+ const transport = new StdioServerTransport();
453
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@emilia-protocol/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "EMILIA Protocol MCP Server — Trust layer tools for AI agents. Check scores, submit receipts, verify transactions.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "bin": {
8
+ "emilia-mcp": "./index.js"
9
+ },
10
+ "main": "index.js",
11
+ "scripts": {
12
+ "start": "node index.js"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.0.0"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "emilia",
20
+ "trust",
21
+ "reputation",
22
+ "ai-agents",
23
+ "agentic-commerce"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/emiliaprotocol/emilia-protocol"
28
+ },
29
+ "homepage": "https://emiliaprotocol.ai"
30
+ }