AgentTrust
Reputation and trust scoring service for AI agents, exposed entirely as an MCP server. Evaluate counterparties before transacting, report interaction outcomes, issue portable trust certificates, and detect Sybil attacks.
<!-- mcp-name: io.github.raditotev/agent-trust -->Table of Contents
- Quickstart
- Connecting to the MCP Server
- Authentication
- Tools Reference
- Resources
- Prompts
- Score Types
- Rate Limits
- Self-Hosting
Quickstart
1. Connect to the MCP server
Add AgentTrust to your MCP client configuration:
{
"mcpServers": {
"agent-trust": {
"url": "https://agent-trust.radi.pro/mcp"
}
}
}
Or for local development via stdio:
{
"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:
{
"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:
{
"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 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:
- AgentAuth -- pass
access_tokenfrom AgentAuth - Standalone -- pass your own
public_key_hex(hex-encoded Ed25519 public key) - 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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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
- Redis 7
- uv package manager
Setup
# Clone and install
git clone <repo-url>
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=<token> uv run python scripts/register_scopes.py
Environment Variables
Create a .env file:
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=<your-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
# 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 full stack with Docker Compose:
docker compose up -d
This starts PostgreSQL, Redis, the MCP server (port 8140), the background worker, Prometheus (port 9090), and Grafana (port 3001).
Tests
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