aiden-mcp 0.0.0-stage → 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AIDEN
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 CHANGED
@@ -1,3 +1,82 @@
1
- # Temporary Holding Version
1
+ # aiden-mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [MCP](https://modelcontextprotocol.io) server for **AIDEN**, the identity and reputation layer for AI agents on
4
+ [Robinhood Chain](https://docs.robinhood.com/chain/). It lets Claude and other MCP clients look up agents registered in the
5
+ **ERC-8004** registries: profiles, owners, feedback, validations and USDG-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
+ ## Connect
21
+
22
+ ### Remote (claude.ai, ChatGPT, any URL-based client)
23
+
24
+ ```
25
+ https://mcp.aiden.network/mcp
26
+ ```
27
+
28
+ Streamable HTTP, stateless, no authentication.
29
+
30
+ - **claude.ai / Claude Desktop:** Settings → Connectors → Add custom connector → paste the URL.
31
+ - **ChatGPT:** Settings → Apps & Connectors → Advanced → enable Developer mode → Create → paste the URL, authentication: none.
32
+ - **Claude Code:** `claude mcp add --transport http aiden https://mcp.aiden.network/mcp`
33
+
34
+ ### Local (stdio via npx)
35
+
36
+ No install step needed; clients run it with `npx`.
37
+
38
+ ### Claude Code
39
+
40
+ ```bash
41
+ claude mcp add aiden -- npx -y aiden-mcp
42
+ ```
43
+
44
+ ### Claude Desktop / Cursor / other clients
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "aiden": {
50
+ "command": "npx",
51
+ "args": ["-y", "aiden-mcp"]
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Configuration
58
+
59
+ | Variable | Default | |
60
+ |---|---|---|
61
+ | `AIDEN_API_URL` | `https://api.aiden.network` | AIDEN indexer API. Use `http://127.0.0.1:4000` for a local indexer |
62
+ | `AIDEN_TIMEOUT_MS` | `10000` | Per-request timeout, 1000 to 60000 |
63
+
64
+ ## Development
65
+
66
+ ```bash
67
+ npm install
68
+ npm run dev # run from source (tsx)
69
+ npm test # vitest
70
+ npm run test:coverage # 80% threshold enforced
71
+ npm run inspect # MCP Inspector against dist/
72
+ npm run worker:dev # remote endpoint locally (wrangler)
73
+ npm run worker:deploy # typecheck, test, deploy the Worker
74
+ ```
75
+
76
+ The remote endpoint is `worker/index.ts`: a Cloudflare Worker that serves the same tools over Streamable HTTP
77
+ (`src/http.ts`). It reaches the AIDEN API through a service binding and forwards the caller's IP, so API rate
78
+ limits apply per user.
79
+
80
+ ## License
81
+
82
+ MIT
package/dist/client.js ADDED
@@ -0,0 +1,57 @@
1
+ import { z } from "zod";
2
+ // Every AIDEN 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 AidenApiError extends Error {
10
+ status;
11
+ constructor(message, status) {
12
+ super(message);
13
+ this.status = status;
14
+ this.name = "AidenApiError";
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": "aiden-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 AidenApiError(`AIDEN API unreachable (${reason})`);
33
+ }
34
+ let body;
35
+ try {
36
+ body = await res.json();
37
+ }
38
+ catch {
39
+ throw new AidenApiError(`AIDEN 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 AidenApiError(`AIDEN API returned an unexpected payload (HTTP ${res.status})`, res.status);
44
+ if (!res.ok || !parsed.data.success) {
45
+ throw new AidenApiError(parsed.data.error ?? `AIDEN 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.aiden.network";
2
+ export const DEFAULT_TIMEOUT_MS = 10_000;
3
+ const MIN_TIMEOUT_MS = 1_000;
4
+ const MAX_TIMEOUT_MS = 60_000;
5
+ // Reads AIDEN_API_URL / AIDEN_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.AIDEN_API_URL?.trim() || DEFAULT_API_URL;
9
+ let url;
10
+ try {
11
+ url = new URL(raw);
12
+ }
13
+ catch {
14
+ throw new Error(`AIDEN_API_URL is not a valid URL: ${raw}`);
15
+ }
16
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
17
+ throw new Error(`AIDEN_API_URL must be http(s), got ${url.protocol}`);
18
+ }
19
+ const timeoutRaw = env.AIDEN_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(`AIDEN_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/http.js ADDED
@@ -0,0 +1,49 @@
1
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
2
+ import { createServer } from "./server.js";
3
+ // Remote (Streamable HTTP) transport for runtimes with web-standard Request/Response, e.g. Cloudflare Workers.
4
+ // Stateless: every POST gets a fresh server and transport, so no session state lives between requests.
5
+ export const MCP_PATH = "/mcp";
6
+ export const MAX_BODY_BYTES = 64 * 1024;
7
+ const CORS_HEADERS = {
8
+ "access-control-allow-origin": "*",
9
+ "access-control-allow-methods": "POST, OPTIONS",
10
+ "access-control-allow-headers": "content-type, accept, authorization, mcp-protocol-version, mcp-session-id, last-event-id",
11
+ "access-control-expose-headers": "mcp-session-id, mcp-protocol-version",
12
+ "access-control-max-age": "600",
13
+ };
14
+ function withCors(res) {
15
+ const headers = new Headers(res.headers);
16
+ for (const [k, v] of Object.entries(CORS_HEADERS))
17
+ headers.set(k, v);
18
+ return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
19
+ }
20
+ function rpcError(status, message, extra = {}) {
21
+ const body = JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message }, id: null });
22
+ return withCors(new Response(body, { status, headers: { "content-type": "application/json", ...extra } }));
23
+ }
24
+ export async function handleMcpHttp(request, client) {
25
+ if (request.method === "OPTIONS")
26
+ return withCors(new Response(null, { status: 204 }));
27
+ if (request.method !== "POST") {
28
+ return rpcError(405, "Method not allowed. This endpoint is stateless: send JSON-RPC over POST.", { allow: "POST, OPTIONS" });
29
+ }
30
+ const server = createServer(client);
31
+ const transport = new WebStandardStreamableHTTPServerTransport({
32
+ sessionIdGenerator: undefined,
33
+ enableJsonResponse: true,
34
+ maxRequestBodySize: MAX_BODY_BYTES,
35
+ });
36
+ try {
37
+ await server.connect(transport);
38
+ return withCors(await transport.handleRequest(request));
39
+ }
40
+ catch (err) {
41
+ console.error("[aiden-mcp] http transport error", err);
42
+ return rpcError(500, "Internal error");
43
+ }
44
+ finally {
45
+ // Response bodies are fully materialised in JSON mode, so closing here is safe.
46
+ await transport.close().catch(() => { });
47
+ await server.close().catch(() => { });
48
+ }
49
+ }
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(`[aiden-mcp] ready, API ${config.apiUrl}`);
12
+ }
13
+ main().catch((err) => {
14
+ console.error("[aiden-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 = "aiden";
4
+ export const SERVER_VERSION = "0.2.0";
5
+ export function createServer(client) {
6
+ const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION }, {
7
+ instructions: "AIDEN indexes AI agent identities and reputation from the ERC-8004 registries on Robinhood Chain. " +
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,72 @@
1
+ import { z } from "zod";
2
+ import { AidenApiError } 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 AidenApiError ? err.message : "Unexpected error while calling the AIDEN API";
13
+ if (!(err instanceof AidenApiError))
14
+ console.error("[aiden-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 AIDEN directory of AI agents registered on Robinhood Chain (ERC-8004). Supports full-text search over names and " +
29
+ "descriptions, filtering by owner address, and sorting by trust score, feedback count or settled USDG.",
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().default(true)
34
+ .describe("Only agents with a readable profile (name, description). Default true; set false to include unnamed agents"),
35
+ sort: z.enum(["newest", "score", "feedback", "settled", "relevance"]).optional()
36
+ .describe("Default: relevance when a query is given, otherwise newest"),
37
+ page,
38
+ limit: limit.describe("Results per page, 1-100 (default 24)"),
39
+ },
40
+ annotations: { title: "Search agents", ...READ_ONLY },
41
+ }, ({ query, owner, readable_only, sort, page, limit }) => run(() => client.listAgents({ q: query, owner, readable: readable_only, sort, page, limit })));
42
+ server.registerTool("get_agent", {
43
+ title: "Get agent",
44
+ description: "Full AIDEN record for one agent: profile, owner, agent wallet, reputation summary, validations, trust score " +
45
+ "and the USDG settlement evidence behind it.",
46
+ inputSchema: { agent_id: agentId },
47
+ annotations: { title: "Get agent", ...READ_ONLY },
48
+ }, ({ agent_id }) => run(() => client.getAgent(agent_id)));
49
+ server.registerTool("get_agent_feedback", {
50
+ title: "Get agent feedback",
51
+ description: "Feedback left for an agent in the ERC-8004 Reputation Registry, newest first. Each entry is classified " +
52
+ "(receipt, rating, metric, self, revoked) with a normalised 0-100 rating where applicable.",
53
+ inputSchema: { agent_id: agentId, page, limit: limit.describe("Results per page, 1-100 (default 50)") },
54
+ annotations: { title: "Get agent feedback", ...READ_ONLY },
55
+ }, ({ agent_id, page, limit }) => run(() => client.listFeedback(agent_id, page, limit)));
56
+ server.registerTool("get_agent_validations", {
57
+ title: "Get agent validations",
58
+ description: "The latest 100 validation requests and responses for an agent from the ERC-8004 Validation Registry.",
59
+ inputSchema: { agent_id: agentId },
60
+ annotations: { title: "Get agent validations", ...READ_ONLY },
61
+ }, ({ agent_id }) => run(() => client.listValidations(agent_id)));
62
+ server.registerTool("get_registry_stats", {
63
+ title: "Get registry stats",
64
+ description: "Totals across the AIDEN index: agents, owners, feedback, validations and settled USDG.",
65
+ annotations: { title: "Get registry stats", ...READ_ONLY },
66
+ }, () => run(() => client.stats()));
67
+ server.registerTool("get_indexer_health", {
68
+ title: "Get indexer health",
69
+ description: "How far the AIDEN indexer is behind the Robinhood Chain head. Check this when results look empty or stale.",
70
+ annotations: { title: "Get indexer health", ...READ_ONLY },
71
+ }, () => run(() => client.health()));
72
+ }
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "aiden-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "MCP server for AIDEN: look up AI agent identity and reputation (ERC-8004) on Robinhood Chain",
5
+ "type": "module",
6
+ "bin": {
7
+ "aiden-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 && tsc -p worker/tsconfig.json",
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
+ "worker:dev": "wrangler dev",
24
+ "worker:deploy": "npm run typecheck && npm test && wrangler deploy"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "robinhood-chain",
29
+ "erc-8004",
30
+ "ai-agents",
31
+ "reputation"
32
+ ],
33
+ "author": "AIDEN",
34
+ "license": "MIT",
35
+ "homepage": "https://aiden.network/mcp",
36
+ "mcpName": "network.aiden/aiden-mcp",
37
+ "publishConfig": {
38
+ "access": "public",
39
+ "provenance": false
40
+ },
41
+ "dependencies": {
42
+ "@modelcontextprotocol/sdk": "^1.30.1",
43
+ "zod": "^4.6.5"
44
+ },
45
+ "devDependencies": {
46
+ "@cloudflare/workers-types": "^5.20260927.1",
47
+ "@types/node": "^26.6.3",
48
+ "@vitest/coverage-v8": "^5.0.2",
49
+ "tsx": "^4.23.15",
50
+ "typescript": "^5.9.3",
51
+ "vitest": "^5.0.2",
52
+ "wrangler": "^4.142.0"
53
+ }
54
+ }