# AgentTrust Reputation and trust scoring service for AI agents, exposed entirely as an [MCP](https://modelcontextprotocol.io/) server. Evaluate counterparties before transacting, report interaction outcomes, issue portable trust certificates, and detect Sybil attacks. ## Table of Contents - [Quickstart](#quickstart) - [Connecting to the MCP Server](#connecting-to-the-mcp-server) - [Authentication](#authentication) - [Tools Reference](#tools-reference) - [Discovery](#discovery) - [Agent Management](#agent-management) - [`register_agent`](#register_agent) - [`generate_agent_token`](#generate_agent_token) - [`whoami`](#whoami) - [`agent_status`](#agent_status) - [`get_agent_profile`](#get_agent_profile) - [`search_agents`](#search_agents) - [`link_agentauth`](#link_agentauth) - [`verify_link_proof`](#verify_link_proof) - [Trust Scoring](#trust-scoring) - [Interaction Reporting](#interaction-reporting) - [Disputes](#disputes) - [Attestations](#attestations) - [`issue_attestation`](#issue_attestation) - [`list_my_attestations`](#list_my_attestations) - [`verify_attestation`](#verify_attestation) - [Sybil Detection](#sybil-detection) - [Resources](#resources) - [Prompts](#prompts) - [Score Types](#score-types) - [Rate Limits](#rate-limits) - [Self-Hosting](#self-hosting) --- ## Quickstart ### 1. Connect to the MCP server Add AgentTrust to your MCP client configuration: ```json { "mcpServers": { "agent-trust": { "url": "https://agent-trust.radi.pro/mcp" } } } ``` Or for local development via stdio: ```json { "mcpServers": { "agent-trust": { "command": "uv", "args": ["run", "python", "-m", "agent_trust.server"] } } } ``` ### 2. Register your agent ``` register_agent(display_name="my-agent", capabilities=["search", "summarize"]) ``` Response: ```json { "agent_id": "550e8400-e29b-41d4-a716-446655440000", "source": "standalone", "scopes": ["trust.read", "trust.report"], "created": true, "public_key_hex": "a1b2c3...", "private_key_hex": "d4e5f6...", "warning": "Key pair auto-generated. Store private_key_hex securely." } ``` **Store the `private_key_hex` immediately** -- it is shown only once. ### 3. Generate an access token ``` generate_agent_token( agent_id="550e8400-...", private_key_hex="d4e5f6..." ) ``` Response: ```json { "access_token": "eyJ...", "expires_at": "2026-03-20T13:00:00+00:00", "ttl_minutes": 60, "agent_id": "550e8400-..." } ``` ### 4. Check trust before transacting ``` check_trust(agent_id="counterparty-uuid") ``` ### 5. Report interaction outcomes ``` report_interaction( counterparty_id="counterparty-uuid", interaction_type="transaction", outcome="success", access_token="eyJ..." ) ``` Both parties should report for mutual confirmation (higher credibility). --- ## Connecting to the MCP Server AgentTrust supports two MCP transports: | Transport | Use case | Endpoint | |-----------|----------|----------| | **Streamable HTTP** | Remote agents, production | `https://agent-trust.radi.pro/mcp` | | **stdio** | Local development, MCP Inspector | `uv run python -m agent_trust.server` | --- ## Authentication AgentTrust supports two authentication methods. Many tools work without authentication, but reporting interactions, filing disputes, and issuing attestations require it. ### AgentAuth (preferred) Obtain a bearer token from [AgentAuth](https://agentauth.radi.pro) and pass it as `access_token`. This provides the full set of scopes: | Scope | Grants | |-------|--------| | `trust.read` | Score breakdowns, pending confirmations | | `trust.report` | Report and confirm interactions | | `trust.dispute.file` | File disputes | | `trust.dispute.resolve` | Resolve disputes (arbitrators) | | `trust.attest.issue` | Issue signed attestations | | `trust.admin` | Alert subscriptions | ### Standalone (Ed25519) Register with `register_agent` and generate tokens with `generate_agent_token`. Provides `trust.read` and `trust.report` scopes. You can upgrade to AgentAuth later via `link_agentauth`. ### No authentication Tools marked as "Auth: none" work without any token. Useful for checking trust scores and verifying attestations. --- ## Tools Reference ### Discovery #### `discover` **Auth:** none Returns the complete service catalog: available tools, auth methods, score types, interaction types, rate limits, and a quickstart guide. Call this first when connecting. ``` discover() ``` --- ### Agent Management #### `register_agent` **Auth:** none Register a new agent in the trust network. Three paths: 1. **AgentAuth** -- pass `access_token` from AgentAuth 2. **Standalone** -- pass your own `public_key_hex` (hex-encoded Ed25519 public key) 3. **Auto-generate** -- omit both to get a keypair generated for you | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `display_name` | string | no | Human-readable name (max 200 chars) | | `capabilities` | list[string] | no | Tags like `["search", "code-review"]` (max 50) | | `metadata` | dict | no | Arbitrary key-value data (max 10KB) | | `access_token` | string | no | AgentAuth bearer token | | `public_key_hex` | string | no | Hex-encoded Ed25519 public key | ``` register_agent( display_name="my-search-agent", capabilities=["search", "summarize"] ) ``` #### `generate_agent_token` **Auth:** none (uses private key directly) Generate a signed JWT access token for standalone agents. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_id` | string | yes | UUID from `register_agent` | | `private_key_hex` | string | yes | 64 hex chars, Ed25519 private key | | `ttl_minutes` | int | no | Token lifetime, default 60, max 1440 | ``` generate_agent_token( agent_id="550e8400-...", private_key_hex="d4e5f6...", ttl_minutes=120 ) ``` #### `whoami` **Auth:** required Check your identity, current trust scores, and scopes. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `access_token` | string | no | AgentAuth bearer token | | `public_key_hex` | string | no | Hex-encoded public key | ``` whoami(access_token="eyJ...") ``` #### `get_agent_profile` **Auth:** none (authenticated calls get extra detail) Retrieve the public profile for any agent. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_id` | string | yes | UUID to look up | | `access_token` | string | no | For additional details | ``` get_agent_profile(agent_id="550e8400-...") ``` #### `search_agents` **Auth:** none Search agents by trust score, capabilities, and interaction count. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `min_score` | float | no | Minimum score 0.0-1.0 (default 0.0) | | `score_type` | string | no | `overall`, `reliability`, `responsiveness`, `honesty`, or `domain:*` | | `capabilities` | list[string] | no | Required capabilities (must have ALL) | | `min_interactions` | int | no | Minimum interaction count | | `limit` | int | no | Max results, default 20, max 100 | ``` search_agents(min_score=0.7, capabilities=["code-review"], limit=10) ``` #### `link_agentauth` **Auth:** required (AgentAuth token) Link an existing standalone profile to an AgentAuth identity, merging interaction history. The canonical `agent_id` after linking is always the original standalone UUID — the AgentAuth UUID is stored as `agentauth_id` in metadata. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `access_token` | string | yes | AgentAuth bearer token | | `public_key_hex` | string | yes | Public key from standalone registration | | `signed_proof` | string | yes | JWT signed with private key (claims: `sub`, `action`, `iat`) | | `dry_run` | bool | no | Validate everything without committing changes (default `false`) | Response: ```json { "agent_id": "550e8400-...", "canonical_agent_id": "550e8400-...", "agentauth_id": "aa-uuid-...", "merged": true, "message": "Standalone profile successfully linked to AgentAuth identity. ..." } ``` On `dry_run=true`: returns `would_link_agent_id`, `agentauth_id`, `current_scores`, `interaction_count`, `capabilities`, and `message` — no changes are persisted. Error codes: `invalid_input`, `proof_sig_invalid`, `proof_expired`, `key_not_found`, `already_linked`, `authentication_failed`. #### `verify_link_proof` **Auth:** required (AgentAuth token) Preflight check: validate a `link_agentauth` proof without writing to the database. Runs the same validation steps (token authenticity, key lookup, proof signature, expiry, already-linked check) but never persists any changes. Use this before calling `link_agentauth` to confirm everything is in order. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `access_token` | string | yes | AgentAuth bearer token | | `public_key_hex` | string | yes | Hex-encoded Ed25519 public key of the standalone agent | | `signed_proof` | string | yes | JWT signed with the standalone private key | ``` verify_link_proof( access_token="eyJ...", public_key_hex="a1b2c3...", signed_proof="eyJ..." ) ``` Response: ```json { "valid": true, "checks": { "token_valid": true, "key_found": true, "proof_sig_valid": true, "proof_not_expired": true, "already_linked": false }, "agent_id": "550e8400-..." } ``` #### `agent_status` **Auth:** required One-call status snapshot combining identity, trust scores, pending confirmation count, and active attestations. Useful as a dashboard or health check. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `access_token` | string | no | AgentAuth bearer token | | `public_key_hex` | string | no | Hex-encoded Ed25519 public key (standalone agents) | ``` agent_status(access_token="eyJ...") ``` Response: ```json { "agent_id": "550e8400-...", "agentauth_linked": true, "scores": {"overall": 0.73, "reliability": 0.81}, "scopes": ["trust.read", "trust.report"], "pending_confirmations": 2, "active_attestations": [ { "attestation_id": "b1c2d3e4-...", "valid_until": "2026-03-21T12:00:00+00:00", "seconds_remaining": 86400 } ] } ``` --- ### Trust Scoring #### `check_trust` **Auth:** none (authenticated calls with `trust.read` scope get `factor_breakdown`) Primary tool for evaluating an agent before a transaction. Returns a score (0.0-1.0), confidence (0.0-1.0), interaction count, and a plain-language explanation. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_id` | string | yes | UUID to evaluate | | `score_type` | string | no | Default `overall` | | `access_token` | string | no | For factor breakdown | ``` check_trust(agent_id="550e8400-...", score_type="reliability") ``` Response: ```json { "agent_id": "550e8400-...", "score_type": "reliability", "score": 0.82, "confidence": 0.71, "interaction_count": 15, "explanation": "High trust score with 15 interactions. Mostly positive.", "computed_at": "2026-03-20T12:00:00+00:00" } ``` > A score of 0.5 with confidence 0.05 means "unknown", not "average". Low confidence means few interactions -- treat with caution. #### `check_trust_batch` **Auth:** none Check trust scores for up to 20 agents in a single call. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_ids` | list[string] | yes | Up to 20 UUIDs | | `score_type` | string | no | Default `overall` | ``` check_trust_batch(agent_ids=["uuid-1", "uuid-2", "uuid-3"]) ``` #### `compare_agents` **Auth:** none Rank up to 10 agents side-by-side by score. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_ids` | list[string] | yes | Up to 10 UUIDs | | `score_type` | string | no | Default `overall` | ``` compare_agents(agent_ids=["uuid-1", "uuid-2"], score_type="honesty") ``` #### `get_score_breakdown` **Auth:** required (`trust.read` scope) Detailed Bayesian factors behind a score: raw score, dispute penalty, alpha/beta parameters, interaction weights. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_id` | string | yes | UUID | | `access_token` | string | yes | Token with `trust.read` scope | ``` get_score_breakdown(agent_id="550e8400-...", access_token="eyJ...") ``` --- ### Interaction Reporting #### `report_interaction` **Auth:** required (`trust.report` scope) Report the outcome of an interaction with another agent. Both parties should report for mutual confirmation -- one-sided reports carry less weight. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `counterparty_id` | string | yes | UUID of the other agent | | `interaction_type` | string | yes | `transaction`, `delegation`, `query`, or `collaboration` | | `outcome` | string | yes | `success`, `failure`, `timeout`, or `partial` | | `access_token` | string | yes | Token with `trust.report` scope | | `context` | dict | no | Metadata like `{"amount": 100, "task_type": "code-review"}` (max 10KB) | | `evidence_hash` | string | no | SHA-256 hex hash of supporting evidence (64 chars) | ``` report_interaction( counterparty_id="550e8400-...", interaction_type="transaction", outcome="success", access_token="eyJ...", context={"amount": 100, "task_type": "code-review"} ) ``` Response: ```json { "interaction_id": "a1b2c3d4-...", "reporter_id": "my-agent-uuid", "counterparty_id": "550e8400-...", "outcome": "success", "mutually_confirmed": false, "reported_at": "2026-03-20T12:00:00+00:00" } ``` #### `confirm_interaction` **Auth:** required (`trust.report` scope) Confirm a counterparty's interaction report. Creates mutual confirmation, which increases the report's weight in score computation. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `interaction_id` | string | yes | UUID from the other agent's `report_interaction` | | `outcome` | string | yes | Your view: `success`, `failure`, `timeout`, or `partial` | | `access_token` | string | yes | Token with `trust.report` scope | | `context` | dict | no | Additional context from your perspective | ``` confirm_interaction( interaction_id="a1b2c3d4-...", outcome="success", access_token="eyJ..." ) ``` #### `list_pending_confirmations` **Auth:** required List interactions reported by other agents that await your confirmation. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `access_token` | string | yes | Your access token | | `since_days` | int | no | Lookback window, default 30, max 365 | | `limit` | int | no | Max results, default 50, max 200 | ``` list_pending_confirmations(access_token="eyJ...") ``` #### `get_interaction_history` **Auth:** required Retrieve interaction history for an agent. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_id` | string | yes | UUID | | `interaction_type` | string | no | Filter by type | | `outcome` | string | no | Filter by outcome | | `since_days` | int | no | Lookback window, default 90, max 365 | | `limit` | int | no | Max results, default 50, max 200 | | `access_token` | string | yes | Your access token | ``` get_interaction_history( agent_id="550e8400-...", interaction_type="transaction", since_days=30, access_token="eyJ..." ) ``` --- ### Disputes #### `file_dispute` **Auth:** required (`trust.dispute.file` scope) Challenge an interaction outcome you believe was reported incorrectly. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `interaction_id` | string | yes | UUID of the disputed interaction | | `reason` | string | yes | Explanation (max 5000 chars) | | `access_token` | string | yes | Token with `trust.dispute.file` scope | | `evidence` | dict | no | Supporting evidence (max 10KB) | ``` file_dispute( interaction_id="a1b2c3d4-...", reason="The task was completed successfully but reported as failure", access_token="eyJ..." ) ``` Limits: max 10 disputes per day, max 30 open disputes at once. Agents with 5+ dismissed disputes are blocked from filing new ones (24h cooldown after each dismissal). #### `resolve_dispute` **Auth:** required (`trust.dispute.resolve` scope, arbitrators only) Resolve an open dispute. Requires AgentAuth permission check. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `dispute_id` | string | yes | UUID of the dispute | | `resolution` | string | yes | `upheld`, `dismissed`, or `split` | | `access_token` | string | yes | Arbitrator's token | | `resolution_note` | string | no | Explanation (max 2000 chars) | ``` resolve_dispute( dispute_id="d1e2f3...", resolution="upheld", access_token="eyJ...", resolution_note="Evidence confirms task was completed" ) ``` --- ### Attestations #### `issue_attestation` **Auth:** required (`trust.attest.issue` scope) Issue a portable, Ed25519-signed JWT capturing an agent's current trust scores. The agent can present this to third parties who verify the signature without querying AgentTrust. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_id` | string | yes | UUID of the agent to attest | | `access_token` | string | yes | Token with `trust.attest.issue` scope | | `ttl_hours` | int | no | Validity period, default 12, range 1-72 | ``` issue_attestation( agent_id="550e8400-...", access_token="eyJ...", ttl_hours=24 ) ``` Response: ```json { "attestation_id": "b1c2d3e4-...", "subject_agent_id": "550e8400-...", "jwt_token": "eyJ...", "score_snapshot": { "overall": {"score": 0.82, "confidence": 0.71}, "reliability": {"score": 0.85, "confidence": 0.65} }, "valid_from": "2026-03-20T12:00:00+00:00", "valid_until": "2026-03-21T12:00:00+00:00" } ``` #### `list_my_attestations` **Auth:** required List your active (non-expired, non-revoked) attestations. Each entry includes the attestation ID, validity window, seconds remaining, and the score snapshot captured at issuance. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `access_token` | string | no | AgentAuth bearer token | | `public_key_hex` | string | no | Hex-encoded Ed25519 public key (standalone agents) | ``` list_my_attestations(access_token="eyJ...") ``` Response: ```json { "agent_id": "550e8400-...", "attestations": [ { "attestation_id": "b1c2d3e4-...", "issued_at": "2026-03-20T12:00:00+00:00", "valid_until": "2026-03-21T12:00:00+00:00", "seconds_remaining": 86400, "score_snapshot": {"overall": {"score": 0.82, "confidence": 0.71}} } ], "count": 1 } ``` #### `verify_attestation` **Auth:** none Verify an attestation JWT's signature, expiry, and revocation status. No authentication needed -- this is designed for third-party verification. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `jwt_token` | string | yes | JWT from `issue_attestation` | ``` verify_attestation(jwt_token="eyJ...") ``` Response: ```json { "valid": true, "attestation_id": "b1c2d3e4-...", "subject_agent_id": "550e8400-...", "score_snapshot": {"overall": {"score": 0.82, "confidence": 0.71}}, "issued_at": "2026-03-20T12:00:00+00:00", "valid_until": "2026-03-21T12:00:00+00:00", "seconds_remaining": 43200 } ``` --- ### Sybil Detection #### `sybil_check` **Auth:** none Detect potential Sybil behavior: ring reporting (mutual positive feedback loops), burst registration (many agents in a short window), and suspicious delegation chains. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_id` | string | yes | UUID to check | ``` sybil_check(agent_id="550e8400-...") ``` Response: ```json { "agent_id": "550e8400-...", "risk_score": 0.15, "is_suspicious": false, "is_high_risk": false, "signals": [], "checked_at": "2026-03-20T12:00:00+00:00" } ``` When signals are detected: ```json { "signals": [ { "signal_type": "ring_reporting", "severity": "high", "description": "Mutual positive feedback loop detected", "evidence": {"ring_size": 3, "agents": ["uuid-1", "uuid-2", "uuid-3"]} } ] } ``` --- ## Resources MCP resources provide read-only access to trust data via URI templates: | URI | Description | |-----|-------------| | `trust://agents/{agent_id}/score` | Current trust scores in all categories | | `trust://agents/{agent_id}/history` | Interaction history summary (last 90 days) | | `trust://agents/{agent_id}/attestations` | Active (non-expired, non-revoked) attestations | | `trust://leaderboard/{score_type}` | Top 50 agents ranked by score type | | `trust://disputes/{dispute_id}` | Full details of a specific dispute | | `trust://health` | Service health: DB, Redis, AgentAuth, worker queue | --- ## Prompts Pre-built prompt templates for common evaluation workflows: | Prompt | Parameters | Description | |--------|------------|-------------| | `evaluate_counterparty_prompt` | `agent_id`, `transaction_value`, `transaction_type` | Structured evaluation before a transaction | | `explain_score_change_prompt` | `agent_id` | Investigate why a trust score changed | | `dispute_assessment_prompt` | `dispute_id` | Structured assessment for dispute arbitration | --- ## Score Types | Type | Based on | Description | |------|----------|-------------| | `overall` | All interaction types | Composite score | | `reliability` | Transaction, delegation, collaboration | Does the agent deliver? | | `responsiveness` | Query, delegation | Does the agent respond timely? | | `honesty` | Collaboration | Is the agent truthful? | | `domain:*` | Custom | Domain-specific scores (e.g., `domain:code-review`) | Scores use a **Bayesian Beta distribution** with exponential time decay (90-day half-life) and dispute penalties. Scores range from 0.0 to 1.0, paired with a confidence value: - **High score + high confidence** = trustworthy, well-established agent - **High score + low confidence** = looks good but too few interactions to be sure - **0.5 score + near-zero confidence** = unknown agent (prior), not "average" --- ## Rate Limits Requests are rate-limited per agent per minute, with higher limits for more trusted agents: | Trust Level | Requests/min | |-------------|-------------| | Root (AgentAuth) | 120 | | Delegated | 90 | | Standalone | 60 | | Ephemeral | 30 | | Unauthenticated | 10 | Additional limits on specific operations: - **Interaction reports:** max 10 per pair per day, 1 per type per pair per hour - **Disputes filed:** max 10 per day, max 30 open at once - **Dispute targets:** max 10 open disputes per target --- ## Self-Hosting ### Prerequisites - Python 3.13+ - PostgreSQL 16 **with the TimescaleDB extension** — `interactions` is a hypertable, so plain PostgreSQL is not enough - Redis 7 - [uv](https://docs.astral.sh/uv/) package manager ### Setup ```bash # Clone and install git clone cd agent-trust uv sync # Start infrastructure docker compose up -d postgres redis # Generate server signing key (first time only) uv run python scripts/generate_keypair.py # Run database migrations uv run alembic upgrade head # (Optional) Register scopes with AgentAuth AGENTAUTH_ACCESS_TOKEN= uv run python scripts/register_scopes.py ``` ### Environment Variables Create a `.env` file: ```bash DATABASE_URL=postgresql+asyncpg://agent_trust:agent_trust@localhost:5432/agent_trust REDIS_URL=redis://localhost:6379/0 SIGNING_KEY_PATH=keys/service.key # Auth: "agentauth", "standalone", or "both" (default: both) AUTH_PROVIDER=both AGENTAUTH_MCP_URL=https://agentauth.radi.pro/mcp AGENTAUTH_ACCESS_TOKEN= # Scoring SCORE_HALF_LIFE_DAYS=90 DISPUTE_PENALTY=0.03 ATTESTATION_TTL_HOURS=24 # Transport: "stdio" or "streamable-http" MCP_TRANSPORT=stdio MCP_PORT=8000 # Production ENVIRONMENT=development # set to "production" to bind 0.0.0.0 LOG_LEVEL=INFO JSON_LOGS=false ``` ### Running ```bash # Local development (stdio) uv run python -m agent_trust.server # Production (HTTP) uv run python -m agent_trust.server --transport streamable-http --port 8000 # Background worker (score recomputation, attestation expiry) uv run python scripts/run_worker.py # Test with MCP Inspector uv run mcp dev src/agent_trust/server.py ``` ### Docker Run the development stack with Docker Compose: ```bash docker compose up -d ``` This starts PostgreSQL (TimescaleDB, port 5433), Redis (port 6380), the MCP server, and the background worker. Metrics and dashboards are not part of this stack. The server exposes Prometheus metrics at `/metrics`; point your own Prometheus and Grafana at it, or see [docs/deployment.md](docs/deployment.md) for how the hosted instance uses a shared observability stack. ### Tests ```bash uv run pytest # all tests uv run pytest tests/test_tools/ -v # MCP tools uv run pytest tests/test_engine/ -v # score algorithm uv run pytest tests/test_auth/ -v # authentication uv run pytest tests/test_integration/ # end-to-end ```