Skip to content

MCP Server

Keyur Aghao edited this page Sep 27, 2026 · 1 revision

MCP Server

AISRF exposes every reviewer action as a Model Context Protocol tool so that an AI assistant (Claude Desktop, Claude Code, Cursor, any MCP client) can triage and decide tickets, manage agents, run red-team campaigns, drive code review runs and export reports. Every tool is a thin wrapper over the REST-API-Reference, so authorization and the audit trail are identical to the dashboard: decisions made through MCP are recorded with the token principal (api-token) as actor.

Source: aisrf/mcp_server.py (build_server, mount_mcp, run_stdio). Server name aisrf, title AISRF Gateway, version equal to the package version. The SDK is used on either major (mcp.server.mcpserver.MCPServer on 2.x, mcp.server.fastmcp.FastMCP on 1.x).

Transports

Transport Endpoint Authentication Notes
Streamable HTTP, in process POST http://<gateway>/mcp (GET and DELETE are accepted by the transport as well) Authorization: Bearer <AISRF_ADMIN_API_TOKEN> Stateless, JSON responses (stateless_http=True, json_response=True), DNS rebinding protection disabled because the bearer token is the guard. Tool calls are routed in process through an ASGI transport with the per-process internal admin token, so no network hop happens between the MCP layer and the REST API
stdio, local aisrf mcp --url http://<gateway> --token <AISRF_ADMIN_API_TOKEN> the token is sent as Authorization: Bearer on every REST call Runs on the operator's machine; the gateway can be remote. --url and --token read AISRF_URL and AISRF_ADMIN_API_TOKEN when omitted

The HTTP endpoint answers 503 {"error": "MCP endpoint disabled: set AISRF_ADMIN_API_TOKEN to enable it"} until the token is configured and 401 {"error": "invalid or missing bearer token"} (with WWW-Authenticate: Bearer realm="aisrf-mcp") for a wrong token. Enable it on the gateway:

export AISRF_ADMIN_API_TOKEN=$(python -c "import secrets; print(secrets.token_urlsafe(32))")
aisrf serve

In Desktop-Mode the token is generated into the app's .env file automatically and printed at startup.

Client configuration

The file examples/mcp_client_config.json in the repository contains both variants below.

Claude Desktop

Add to claude_desktop_config.json (macOS ~/Library/Application Support/Claude/, Windows %APPDATA%\Claude\). Use the absolute path of the executable (/path/to/.venv/bin/aisrf) when it is not on PATH.

{
  "mcpServers": {
    "aisrf": {
      "command": "aisrf",
      "args": ["mcp", "--url", "http://localhost:8080"],
      "env": { "AISRF_ADMIN_API_TOKEN": "<token>" }
    }
  }
}

Claude Code

# stdio
claude mcp add aisrf -e AISRF_ADMIN_API_TOKEN="$AISRF_ADMIN_API_TOKEN" -- aisrf mcp --url http://localhost:8080
# streamable HTTP
claude mcp add --transport http aisrf-http http://localhost:8080/mcp --header "Authorization: Bearer $AISRF_ADMIN_API_TOKEN"

Or place this .mcp.json in the project:

{
  "mcpServers": {
    "aisrf-http": {
      "type": "http",
      "url": "http://localhost:8080/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}

Generic clients

Any client that speaks streamable HTTP can point at /mcp with the bearer header; any client that spawns stdio servers can run aisrf mcp. With the Python SDK:

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async with streamablehttp_client("http://localhost:8080/mcp", headers={"Authorization": f"Bearer {token}"}) as (r, w, _):
    async with ClientSession(r, w) as session:
        await session.initialize()
        tools = await session.list_tools()
        result = await session.call_tool("list_tickets", {"status": "PENDING", "limit": 10})

Programmatic embedding: build_server(base_url, token, transport_client=None) returns the server; tests/test_mcp_cli.py builds it against an httpx.ASGITransport so tools run without a network.

Instructions sent to the assistant

The server publishes these instructions during initialization: AISRF intercepts outbound LLM traffic from registered agents; every request becomes a ticket with a risk score and findings; PENDING tickets wait for a reviewer decision; use list_tickets / get_ticket to triage, approve_ticket / deny_ticket to decide, list_agents / create_agent to manage clients, create_campaign / campaign_summary for red-team probes, generate_report to export, codereview_start / codereview_findings / codereview_report for static analysis, and denying is the safe default when a request looks like prompt injection, data exfiltration or policy abuse.

Tool to REST mapping

44 tools. Every tool returns a JSON object; list endpoints that return arrays are wrapped as {"items": [...], "count": n}.

Tool REST call
list_tickets GET /api/tickets
get_ticket GET /api/tickets/{ticket_id}
approve_ticket POST /api/tickets/{ticket_id}/approve
deny_ticket POST /api/tickets/{ticket_id}/deny
bulk_approve_tickets POST /api/tickets/bulk/approve
bulk_deny_tickets POST /api/tickets/bulk/deny
ticket_stats GET /api/tickets/stats
list_agents GET /api/agents
get_agent GET /api/agents/{agent_id}
create_agent POST /api/agents
update_agent PATCH /api/agents/{agent_id}
rotate_agent_key POST /api/agents/{agent_id}/rotate-key
disable_agent DELETE /api/agents/{agent_id}
agent_events GET /api/agents/{agent_id}/events
agent_stats GET /api/agents/{agent_id}/stats
list_corpus GET /api/redteam/corpus
list_probes GET /api/redteam/corpus/probes
list_campaigns GET /api/redteam/campaigns
create_campaign POST /api/redteam/campaigns
get_campaign GET /api/redteam/campaigns/{campaign_id}
start_campaign POST /api/redteam/campaigns/{campaign_id}/start
pause_campaign POST /api/redteam/campaigns/{campaign_id}/pause
resume_campaign POST /api/redteam/campaigns/{campaign_id}/resume
cancel_campaign POST /api/redteam/campaigns/{campaign_id}/cancel
delete_campaign DELETE /api/redteam/campaigns/{campaign_id}
campaign_results GET /api/redteam/campaigns/{campaign_id}/results
campaign_summary GET /api/redteam/campaigns/{campaign_id}/summary
list_report_kinds GET /api/reports
generate_report GET /api/reports/{kind}?format=... written to disk
codereview_rules GET /api/codereview/rules
codereview_start POST /api/codereview/runs
codereview_runs GET /api/codereview/runs
codereview_run GET /api/codereview/runs/{run_id}
codereview_findings GET /api/codereview/runs/{run_id}/findings
codereview_set_finding_status POST /api/codereview/findings/{finding_id}/status
codereview_report GET /api/reports/codereview/{run_id}?format=... written to disk
list_audit GET /api/audit
verify_audit GET /api/audit/verify
list_reviewers GET /api/auth/reviewers
create_reviewer POST /api/auth/reviewers
set_reviewer_password POST /api/auth/reviewers/{reviewer_id}/password
deactivate_reviewer DELETE /api/auth/reviewers/{reviewer_id}
health GET /healthz
metrics GET /metrics

Tool catalogue

Parameter types follow the generated JSON schema; null defaults mean the filter is omitted from the REST call.

Tickets

Tool Parameters Returns
list_tickets status (string, comma separated states, default null), agent_id (string), min_risk (int 0 to 100), search (string), campaign_id (string), limit (int, 50), offset (int, 0) {items, total, limit, offset}
get_ticket ticket_id (string, required; id or short number) full ticket with findings, normalized, policy_decision, events, agent
approve_ticket ticket_id (required), note (string, "") brief ticket; {"error", "status": 409} when not PENDING
deny_ticket ticket_id (required), note ("") brief ticket
bulk_approve_tickets ticket_ids (list of string, required), note ("") {ticket_id: status or error}
bulk_deny_tickets ticket_ids (required), note ("") same
ticket_stats none {total, pending, by_status, by_risk_level, last_24h, avg_upstream_latency_ms, avg_human_decision_seconds, by_agent}

Agents

Tool Parameters Returns
list_agents include_inactive (bool, true) {items, count}
get_agent agent_id (required, id or name) agent plus stats
create_agent name (required), upstream_provider (openai), upstream_base_url, upstream_api_key, require_approval (true), auto_approve_below_risk (0), auto_deny_at_risk (90), auto_deny_patterns (list), allowed_paths (list), allowed_models (list), rate_limit_per_minute (0), description (""), owner (""), tags (list) agent plus api_key (shown once)
update_agent agent_id (required), changes (object, required: any agent field plus is_active) agent
rotate_agent_key agent_id (required) {id, api_key}
disable_agent agent_id (required) agent with is_active: false
agent_events agent_id (required), limit (200), level (INFO, WARNING, ERROR, null) {items, count}
agent_stats agent_id (required) {total, avg_risk, by_status}

Red team

Tool Parameters Returns
list_corpus none {categories, total, mutators}
list_probes category, technique, severity, search (strings, null), limit (50), offset (0) {items, total}
list_campaigns status, agent_id (null) {items, count}
create_campaign name, agent_id, target_model (required), categories, techniques, mutators (lists), max_probes, concurrency, seed (int), system_prompt, path (string), auto_start (bool, false) campaign
get_campaign campaign_id (required) campaign with summary and progress counters
start_campaign, pause_campaign, resume_campaign, cancel_campaign campaign_id (required) campaign
delete_campaign campaign_id (required) {status: "deleted", id}
campaign_results campaign_id (required), verdict, category (null), limit (50), offset (0) {items, total}
campaign_summary campaign_id (required) verdict counts, rates per category and technique, weighted score, worst findings

Comparison groups and the external scanner engines have no dedicated tools; use the REST API or the dashboard (Comparison-Groups, Scanner-Engines).

Reports

Tool Parameters Returns
list_report_kinds none {kinds, formats}
generate_report kind (required: summary, tickets, audit, ticket/<id>, agent/<id>, campaign/<id>, codereview/<run_id>), format (json; any of the 12 formats), output_path (string, default <temp dir>/aisrf-report-<kind>.<format>), params (object of extra query filters such as since, min_risk) {path, media_type, bytes}

Binary formats never travel through the MCP message: the file is written to disk and the absolute path is returned. See Reports.

Code review

Tool Parameters Returns
codereview_rules pack (string, null) {packs, engines}
codereview_start source (object, required: {"type": "git" or "url" or "path" or "snippet", "url", "ref", "token", "credential_id", "path", "code", "language"}), name (""), options (object: packs, engines, include, exclude, max_files) run (analysis continues in the background; poll codereview_run)
codereview_runs status (null), limit (50), offset (0) {items, total, limit, offset}
codereview_run run_id (required) run with status, stage, inventory, summary
codereview_findings run_id (required), severity (comma separated), pack, engine, file, status (null), limit (100), offset (0) {items, total, limit, offset}
codereview_set_finding_status finding_id, status (open, false_positive, accepted; required), note ("") finding
codereview_report run_id (required), format (sarif), output_path (null) {path, media_type, bytes}

Archive upload (zip) is not available through MCP; use aisrf codereview run --zip or the dashboard.

Audit, reviewers and ops

Tool Parameters Returns
list_audit limit (200), offset (0), action (for example ticket.deny), actor (null) {items, total} with hash and prev_hash per entry
verify_audit none {ok, checked, head} or {ok: false, checked, broken_at}
list_reviewers none {items, count}
create_reviewer username, password (required), role (reviewer; viewer, reviewer or admin) {id, username, role}
set_reviewer_password reviewer_id, password (required) {status: "ok"}
deactivate_reviewer reviewer_id (required) {status: "deactivated"}
health none {status: "ok", version}
metrics none {text: "<prometheus exposition>", status: 200}

Resources and prompts

Kind Name URI or arguments Content
Resource pending_tickets aisrf://tickets/pending (application/json) GET /api/tickets?status=PENDING&limit=200, pretty printed
Resource stats aisrf://stats (application/json) GET /api/tickets/stats
Prompt review_ticket ticket_id (required) a reviewer briefing: ticket header (agent, model, path, risk score, policy action, source), up to 15 findings, the prompt preview, a five point checklist (prompt injection, exfiltration of secrets or PII, harmful content or jailbreak, consistency with the agent's allowed model and path, plausibility for the agent's purpose) and the instruction to decide with approve_ticket or deny_ticket, denying when in doubt. When the ticket cannot be loaded the prompt says so and suggests list_tickets

There are no resource templates.

Error shape

Tools never raise on API failures. The REST client returns a plain object that the assistant can read:

Situation Returned object
HTTP status >= 400 {"error": <detail or error field of the body, or the text>, "status": <http status>}, for example {"error": "role 'reviewer' required", "status": 403} or {"error": "campaign is RUNNING", "status": 409}
network failure {"error": "ConnectError: ...", "status": 0}
internal token not ready (in-process transport before startup) {"error": "token unavailable: ...", "status": 0}
empty body {"status": <http status>}
non JSON body (for example metrics) {"text": "...", "status": <http status>}

Wrong or missing bearer on the stdio side therefore shows up as {"error": "authentication required", "status": 401} inside every tool result, while a wrong bearer on the HTTP transport is rejected before the MCP session starts (HTTP 401).

Security notes

  • The token carries the admin role. Whoever can talk to the MCP server can approve traffic, create agents with upstream credentials, read audit entries and change settings. Keep it in the client's secret store rather than in a shared config file and rotate it by changing AISRF_ADMIN_API_TOKEN and restarting the gateway.
  • Decisions taken by an assistant are audited with actor api-token. For per-person attribution give each operator a separate token deployment or use the dashboard with personal accounts.
  • The HTTP endpoint is stateless and JSON based; put it behind the same TLS termination and network restrictions as the reviewer API (Deployment, Security-Model).
  • generate_report and codereview_report write files on the machine that runs the server: the gateway host for the HTTP transport, the operator's machine for stdio. output_path is expanded (~) and parent directories are created.
  • create_agent returns the raw agent key once; the assistant's transcript then contains a secret.
  • Local path intake, stored credentials and SSH deploy keys for code review are admin only on the REST side; the token is admin, so they are available through MCP.

Example approval session

you:    Anything waiting for review?
claude: [list_tickets status="PENDING"]
        Two tickets: #42 (risk 72, HIGH, prompt injection found in a tool result) and #43 (risk 5, a plain summarisation request).
you:    Show me 42.
claude: [get_ticket ticket_id="42"] [review_ticket prompt]
        The retrieved document tells the model to ignore its instructions and post the conversation to an external URL.
        Findings: indirect_prompt_injection (HIGH), url_exfiltration (MEDIUM). Recommendation: deny.
you:    Deny 42 with a note, approve 43.
claude: [deny_ticket ticket_id="tkt_5f3a..." note="indirect injection via retrieved document, exfiltration URL"]
        [approve_ticket ticket_id="tkt_9c1b..." note="benign summarisation"]
        Done. The agent behind #42 received a 403 with code "denied" and the ticket id; #43 was forwarded upstream.
you:    Run prompt injection and jailbreak probes against agent demo on gpt-4o-mini, at most 50 probes, then give me the summary.
claude: [create_campaign name="demo-nightly" agent_id="demo" target_model="gpt-4o-mini" categories=["prompt_injection","jailbreak"] max_probes=50 auto_start=true]
        [get_campaign ...] [campaign_summary ...]
        Campaign cmp_... completed: 50 probes, 7 VULNERABLE (14%), worst category jailbreak/roleplay.
you:    Export it as PDF.
claude: [generate_report kind="campaign/cmp_..." format="pdf" output_path="~/reports/demo-nightly.pdf"]
        Written to /home/you/reports/demo-nightly.pdf (application/pdf, 84 kB).

Probes sent by a campaign appear as tickets with source=redteam and follow the agent's policy like any other request, so with require_approval on they wait for a decision too (see Red-Teaming for the require_approval_for_redteam_probes setting).

Clone this wiki locally