arcid-mcp 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.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/client.js +57 -0
- package/dist/config.js +25 -0
- package/dist/index.js +16 -0
- package/dist/server.js +13 -0
- package/dist/tools.js +71 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ArcID
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# arcid-mcp
|
|
2
|
+
|
|
3
|
+
[MCP](https://modelcontextprotocol.io) server for **ArcID**, the identity and reputation layer for AI agents on
|
|
4
|
+
[Arc](https://arc.network). It lets Claude and other MCP clients look up agents registered in the
|
|
5
|
+
**ERC-8004** registries: profiles, owners, feedback, validations and USDC-weighted trust scores.
|
|
6
|
+
|
|
7
|
+
Read-only. No wallet or API key needed.
|
|
8
|
+
|
|
9
|
+
## Tools
|
|
10
|
+
|
|
11
|
+
| Tool | What it does |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `search_agents` | Full-text search, owner filter, sort by `newest` / `score` / `feedback` / `settled` / `relevance` |
|
|
14
|
+
| `get_agent` | Full record: profile, reputation, validations, trust score, settlement evidence |
|
|
15
|
+
| `get_agent_feedback` | Reputation Registry feedback, newest first, classified and normalised |
|
|
16
|
+
| `get_agent_validations` | Latest 100 Validation Registry entries |
|
|
17
|
+
| `get_registry_stats` | Index totals |
|
|
18
|
+
| `get_indexer_health` | How far the indexer is behind the chain head |
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
No install step needed; clients run it with `npx`.
|
|
23
|
+
|
|
24
|
+
### Claude Code
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
claude mcp add arcid -- npx -y arcid-mcp
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Claude Desktop / Cursor / other clients
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"arcid": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "arcid-mcp"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
| Variable | Default | |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `ARCID_API_URL` | `https://api.arcusid.com` | ArcID indexer API. Use `http://127.0.0.1:4000` for a local indexer |
|
|
48
|
+
| `ARCID_TIMEOUT_MS` | `10000` | Per-request timeout, 1000 to 60000 |
|
|
49
|
+
|
|
50
|
+
## Development
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git clone https://github.com/arcusid/arcid-mcp.git && cd arcid-mcp && npm install
|
|
54
|
+
npm run dev # run from source (tsx)
|
|
55
|
+
npm test # vitest
|
|
56
|
+
npm run test:coverage # 80% threshold enforced
|
|
57
|
+
npm run inspect # MCP Inspector against dist/
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Every ArcID endpoint answers with this envelope.
|
|
3
|
+
const envelope = z.object({
|
|
4
|
+
success: z.boolean(),
|
|
5
|
+
data: z.unknown(),
|
|
6
|
+
error: z.string().nullable(),
|
|
7
|
+
meta: z.record(z.string(), z.unknown()).optional(),
|
|
8
|
+
});
|
|
9
|
+
export class ArcIdApiError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
constructor(message, status) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.name = "ArcIdApiError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function createClient(config, fetchImpl = fetch) {
|
|
18
|
+
async function get(path, query = {}) {
|
|
19
|
+
const url = new URL(config.apiUrl + path);
|
|
20
|
+
for (const [k, v] of Object.entries(query))
|
|
21
|
+
if (v !== undefined)
|
|
22
|
+
url.searchParams.set(k, String(v));
|
|
23
|
+
let res;
|
|
24
|
+
try {
|
|
25
|
+
res = await fetchImpl(url, {
|
|
26
|
+
headers: { accept: "application/json", "user-agent": "arcid-mcp" },
|
|
27
|
+
signal: AbortSignal.timeout(config.timeoutMs),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const reason = err instanceof Error && err.name === "TimeoutError" ? `timed out after ${config.timeoutMs} ms` : "network error";
|
|
32
|
+
throw new ArcIdApiError(`ArcID API unreachable (${reason})`);
|
|
33
|
+
}
|
|
34
|
+
let body;
|
|
35
|
+
try {
|
|
36
|
+
body = await res.json();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new ArcIdApiError(`ArcID API returned a non-JSON response (HTTP ${res.status})`, res.status);
|
|
40
|
+
}
|
|
41
|
+
const parsed = envelope.safeParse(body);
|
|
42
|
+
if (!parsed.success)
|
|
43
|
+
throw new ArcIdApiError(`ArcID API returned an unexpected payload (HTTP ${res.status})`, res.status);
|
|
44
|
+
if (!res.ok || !parsed.data.success) {
|
|
45
|
+
throw new ArcIdApiError(parsed.data.error ?? `ArcID API error (HTTP ${res.status})`, res.status);
|
|
46
|
+
}
|
|
47
|
+
return { data: parsed.data.data, ...(parsed.data.meta ? { meta: parsed.data.meta } : {}) };
|
|
48
|
+
}
|
|
49
|
+
return Object.freeze({
|
|
50
|
+
health: () => get("/health"),
|
|
51
|
+
stats: () => get("/v1/stats"),
|
|
52
|
+
listAgents: (p) => get("/v1/agents", { ...p }),
|
|
53
|
+
getAgent: (id) => get(`/v1/agents/${id}`),
|
|
54
|
+
listFeedback: (id, page, limit) => get(`/v1/agents/${id}/feedback`, { page, limit }),
|
|
55
|
+
listValidations: (id) => get(`/v1/agents/${id}/validations`),
|
|
56
|
+
});
|
|
57
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const DEFAULT_API_URL = "https://api.arcusid.com";
|
|
2
|
+
export const DEFAULT_TIMEOUT_MS = 10_000;
|
|
3
|
+
const MIN_TIMEOUT_MS = 1_000;
|
|
4
|
+
const MAX_TIMEOUT_MS = 60_000;
|
|
5
|
+
// Reads ARCID_API_URL / ARCID_TIMEOUT_MS. Fails fast on bad values so a misconfigured
|
|
6
|
+
// client sees the problem at startup instead of on the first tool call.
|
|
7
|
+
export function loadConfig(env = process.env) {
|
|
8
|
+
const raw = env.ARCID_API_URL?.trim() || DEFAULT_API_URL;
|
|
9
|
+
let url;
|
|
10
|
+
try {
|
|
11
|
+
url = new URL(raw);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
throw new Error(`ARCID_API_URL is not a valid URL: ${raw}`);
|
|
15
|
+
}
|
|
16
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
17
|
+
throw new Error(`ARCID_API_URL must be http(s), got ${url.protocol}`);
|
|
18
|
+
}
|
|
19
|
+
const timeoutRaw = env.ARCID_TIMEOUT_MS?.trim();
|
|
20
|
+
const timeoutMs = timeoutRaw ? Number(timeoutRaw) : DEFAULT_TIMEOUT_MS;
|
|
21
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < MIN_TIMEOUT_MS || timeoutMs > MAX_TIMEOUT_MS) {
|
|
22
|
+
throw new Error(`ARCID_TIMEOUT_MS must be an integer between ${MIN_TIMEOUT_MS} and ${MAX_TIMEOUT_MS}`);
|
|
23
|
+
}
|
|
24
|
+
return Object.freeze({ apiUrl: url.origin + url.pathname.replace(/\/+$/, ""), timeoutMs });
|
|
25
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createClient } from "./client.js";
|
|
4
|
+
import { loadConfig } from "./config.js";
|
|
5
|
+
import { createServer } from "./server.js";
|
|
6
|
+
async function main() {
|
|
7
|
+
const config = loadConfig();
|
|
8
|
+
const server = createServer(createClient(config));
|
|
9
|
+
await server.connect(new StdioServerTransport());
|
|
10
|
+
// stdout carries the MCP protocol, so logs go to stderr.
|
|
11
|
+
console.error(`[arcid-mcp] ready, API ${config.apiUrl}`);
|
|
12
|
+
}
|
|
13
|
+
main().catch((err) => {
|
|
14
|
+
console.error("[arcid-mcp] failed to start:", err instanceof Error ? err.message : err);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { registerTools } from "./tools.js";
|
|
3
|
+
export const SERVER_NAME = "arcid";
|
|
4
|
+
export const SERVER_VERSION = "0.1.0";
|
|
5
|
+
export function createServer(client) {
|
|
6
|
+
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION }, {
|
|
7
|
+
instructions: "ArcID indexes AI agent identities and reputation from the ERC-8004 registries on Arc. " +
|
|
8
|
+
"Use search_agents to find agents, then get_agent for the full record. If results are empty, " +
|
|
9
|
+
"call get_indexer_health: the indexer may still be catching up with the chain.",
|
|
10
|
+
});
|
|
11
|
+
registerTools(server, client);
|
|
12
|
+
return server;
|
|
13
|
+
}
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ArcIdApiError } from "./client.js";
|
|
3
|
+
const READ_ONLY = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
4
|
+
const agentId = z.number().int().min(0).max(999_999_999_999_999).describe("ERC-8004 agent ID (token ID in the Identity Registry)");
|
|
5
|
+
const page = z.number().int().min(1).max(10_000).optional().describe("Page number, starting at 1");
|
|
6
|
+
const limit = z.number().int().min(1).max(100).optional();
|
|
7
|
+
export function toResult(result) {
|
|
8
|
+
const payload = result.meta ? { data: result.data, meta: result.meta } : { data: result.data };
|
|
9
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
10
|
+
}
|
|
11
|
+
export function toError(err) {
|
|
12
|
+
const message = err instanceof ArcIdApiError ? err.message : "Unexpected error while calling the ArcID API";
|
|
13
|
+
if (!(err instanceof ArcIdApiError))
|
|
14
|
+
console.error("[arcid-mcp]", err);
|
|
15
|
+
return { isError: true, content: [{ type: "text", text: message }] };
|
|
16
|
+
}
|
|
17
|
+
async function run(call) {
|
|
18
|
+
try {
|
|
19
|
+
return toResult(await call());
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return toError(err);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function registerTools(server, client) {
|
|
26
|
+
server.registerTool("search_agents", {
|
|
27
|
+
title: "Search agents",
|
|
28
|
+
description: "Search the ArcID directory of AI agents registered on Arc (ERC-8004). Supports full-text search over names and " +
|
|
29
|
+
"descriptions, filtering by owner address, and sorting by trust score, feedback count or settled USDC.",
|
|
30
|
+
inputSchema: {
|
|
31
|
+
query: z.string().trim().max(120).optional().describe("Full-text search (prefix match on words)"),
|
|
32
|
+
owner: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "owner must be a 0x address").optional().describe("Owner wallet address"),
|
|
33
|
+
readable_only: z.boolean().optional().describe("Only agents whose profile could be parsed"),
|
|
34
|
+
sort: z.enum(["newest", "score", "feedback", "settled", "relevance"]).optional()
|
|
35
|
+
.describe("Default: relevance when a query is given, otherwise newest"),
|
|
36
|
+
page,
|
|
37
|
+
limit: limit.describe("Results per page, 1-100 (default 24)"),
|
|
38
|
+
},
|
|
39
|
+
annotations: { title: "Search agents", ...READ_ONLY },
|
|
40
|
+
}, ({ query, owner, readable_only, sort, page, limit }) => run(() => client.listAgents({ q: query, owner, readable: readable_only, sort, page, limit })));
|
|
41
|
+
server.registerTool("get_agent", {
|
|
42
|
+
title: "Get agent",
|
|
43
|
+
description: "Full ArcID record for one agent: profile, owner, agent wallet, reputation summary, validations, trust score " +
|
|
44
|
+
"and the USDC settlement evidence behind it.",
|
|
45
|
+
inputSchema: { agent_id: agentId },
|
|
46
|
+
annotations: { title: "Get agent", ...READ_ONLY },
|
|
47
|
+
}, ({ agent_id }) => run(() => client.getAgent(agent_id)));
|
|
48
|
+
server.registerTool("get_agent_feedback", {
|
|
49
|
+
title: "Get agent feedback",
|
|
50
|
+
description: "Feedback left for an agent in the ERC-8004 Reputation Registry, newest first. Each entry is classified " +
|
|
51
|
+
"(receipt, rating, metric, self, revoked) with a normalised 0-100 rating where applicable.",
|
|
52
|
+
inputSchema: { agent_id: agentId, page, limit: limit.describe("Results per page, 1-100 (default 50)") },
|
|
53
|
+
annotations: { title: "Get agent feedback", ...READ_ONLY },
|
|
54
|
+
}, ({ agent_id, page, limit }) => run(() => client.listFeedback(agent_id, page, limit)));
|
|
55
|
+
server.registerTool("get_agent_validations", {
|
|
56
|
+
title: "Get agent validations",
|
|
57
|
+
description: "The latest 100 validation requests and responses for an agent from the ERC-8004 Validation Registry.",
|
|
58
|
+
inputSchema: { agent_id: agentId },
|
|
59
|
+
annotations: { title: "Get agent validations", ...READ_ONLY },
|
|
60
|
+
}, ({ agent_id }) => run(() => client.listValidations(agent_id)));
|
|
61
|
+
server.registerTool("get_registry_stats", {
|
|
62
|
+
title: "Get registry stats",
|
|
63
|
+
description: "Totals across the ArcID index: agents, owners, feedback, validations and settled USDC.",
|
|
64
|
+
annotations: { title: "Get registry stats", ...READ_ONLY },
|
|
65
|
+
}, () => run(() => client.stats()));
|
|
66
|
+
server.registerTool("get_indexer_health", {
|
|
67
|
+
title: "Get indexer health",
|
|
68
|
+
description: "How far the ArcID indexer is behind the Arc chain head. Check this when results look empty or stale.",
|
|
69
|
+
annotations: { title: "Get indexer health", ...READ_ONLY },
|
|
70
|
+
}, () => run(() => client.health()));
|
|
71
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "arcid-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for ArcID: look up AI agent identity and reputation (ERC-8004) on Arc",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"arcid-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=20"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "tsx src/index.ts",
|
|
17
|
+
"build": "tsc -p tsconfig.build.json",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:coverage": "vitest run --coverage",
|
|
21
|
+
"inspect": "npx @modelcontextprotocol/inspector node dist/index.js",
|
|
22
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"arc",
|
|
27
|
+
"erc-8004",
|
|
28
|
+
"ai-agents",
|
|
29
|
+
"reputation"
|
|
30
|
+
],
|
|
31
|
+
"author": "arcusid",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/arcusid/arcid-mcp.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/arcusid/arcid-mcp#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/arcusid/arcid-mcp/issues"
|
|
40
|
+
},
|
|
41
|
+
"mcpName": "io.github.arcusid/arcid-mcp",
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public",
|
|
44
|
+
"provenance": false
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
48
|
+
"zod": "^4.6.5"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^26.6.3",
|
|
52
|
+
"@vitest/coverage-v8": "^5.0.2",
|
|
53
|
+
"tsx": "^4.23.15",
|
|
54
|
+
"typescript": "^5.9.3",
|
|
55
|
+
"vitest": "^5.0.2"
|
|
56
|
+
}
|
|
57
|
+
}
|