self-hosted mcp server · v0.4.8

Raven

66 tools. One server. Two transports.

Point any MCP client at one process you host yourself. raven-mcp speaks stdio for local clients and JSON-RPC over HTTP for remote ones, with workspace confinement and keyed auth on both.

MCP 2025-03-26 Python 3.11+ MIT License no telemetry

raven-mcp — stdio handshake
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"any-mcp-client","version":"*"}}}
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-03-26","capabilities":{"tools":{}},"serverInfo":{"name":"raven-mcp","version":"0.4.8"}}}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"read"},{"name":"write"},{"name":"edit"},{"name":"verify"},{"name":"glob"},{"name":"grep"},{"name":"bash"},"… 59 more"]}}
66 tools registered · workspace confined

Why Raven

Built for control

Your code, your keys, your infrastructure. No Raven-run service in the middle, no telemetry by default, no vendor lock-in.

Self-hosted

One Python process on your own server. Nothing of yours runs on anyone else's hardware: the calls it makes go to the LLM provider, the messengers and the tools you configure, and every one of those destinations is listed in the egress map.

66 tools

Files, shell, git, web search, code analysis, testing, memory, routines, monitors — all over MCP.

Two transports

stdio for local clients that spawn the server as a subprocess, JSON-RPC over HTTP for remote access.

Workspace confinement

Every file operation is bounded to your project. Symlinks resolved, escape attempts blocked.

Keyed auth

HTTP transport requires a secret token. Fail-closed: unconfigured means rejected, not accepted.

MIT licensed

Use it, modify it, distribute it. No copyleft, no strings attached.

Registry

Tool registry

Every tool the server exposes, with the parameters it accepts. Select one to build its request frame in the console.

Loading registry…

    Protocol

    Send a request

    Build a real JSON-RPC 2.0 frame in your browser: start from an example, edit it, or select a tool in the registry and the frame comes pre-filled.

    raven-mcp — request builder 66 tools reachable

    Enter sends the frame, Shift+Enter adds a line. The eight exchanges behind these buttons were captured from a live raven-mcp process, including its -32602 and -32601 replies. Anything outside that recording is answered by a running server, not by this page.

    Run it

    One registry, three ways in

    The same tools are reachable as an MCP server, as a terminal coding agent with its own session store, and behind a gateway that holds the channel adapters.

    MCP server

    Stdio for a client that spawns the process, JSON-RPC over HTTP for one that reaches it across a network.

    stdio
    raven-mcp --workspace <dir>
    ravencode-mcp --workspace <dir>

    serverInfo.name · raven-mcp / ravencode

    Coding agent

    RavenCode runs the registry in an agent loop, keeps sessions on disk, exports them with credentials stripped and answers as an OpenAI-compatible endpoint.

    cli
    ravencode tui <dir>
    ravencode serve --port 8080
    ravencode session export <id>

    sessions under the data dir · basic auth on serve

    Gateway

    One process holds the registry, the DM policy and the messengers, so a chat message reaches the same tools a client frame does.

    cli
    raven start
    raven start --daemon
    ravenflow

    15 channel adapters · DM policy pairing|open|closed

    Gateway

    15 channel adapters, one admission policy

    One process holds every messenger on one event loop. Each adapter reads its own variables and stays silent when they are empty, so starting the server without credentials opens no connections and answers no chats.

    How each adapter receives a message
    Adapter Inbound path Variables it reads
    discord Gateway websocket, launched with the bot token DISCORD_BOT_TOKEN
    email IMAP poll of the inbox, SMTP for the reply EMAIL_IMAP_HOST EMAIL_IMAP_USER EMAIL_IMAP_PASS EMAIL_POLL_INTERVAL
    feishu POST /api/webhooks/feishu, SHA-256 over timestamp, nonce, key and body FEISHU_ENCRYPT_KEY
    github POST /api/webhooks/github, HMAC of the raw body in X-Hub-Signature-256 GITHUB_WEBHOOK_SECRET
    gitlab POST /api/webhooks/gitlab, shared token in X-Gitlab-Token GITLAB_WEBHOOK_SECRET
    googlechat POST /api/webhooks/googlechat WEB_SECRET_KEY
    irc Socket, TLS by port; channels joined after 001 IRC_SERVER IRC_NICK IRC_CHANNELS
    line POST /api/webhooks/line, base64 HMAC in X-Line-Signature LINE_CHANNEL_SECRET
    matrix GET /_matrix/client/v3/sync long-poll, 30 s MATRIX_HOMESERVER MATRIX_ACCESS_TOKEN
    signal POST /api/webhooks/signal from a signal-cli receiver WEB_SECRET_KEY
    slack POST /api/webhooks/slack/events, v0 HMAC of timestamp and body SLACK_SIGNING_SECRET
    teams POST /api/webhooks/teams WEB_SECRET_KEY
    telegram getUpdates long-polling through the Bot API TELEGRAM_BOT_TOKEN
    webchat WebSocket /ws, /ws/stream, /ws/canvas WEB_SECRET_KEY
    whatsapp POST /api/webhooks/whatsapp plus the GET handshake WHATSAPP_APP_SECRET WHATSAPP_VERIFY_TOKEN

    A webhook route that has no secret of its own accepts the shared HMAC of the raw body in X-Webhook-Signature instead. With neither key configured it answers 403 to everything: an unverified payload is refused, not parked in a low-priority queue. Outbound-only settings — TEAMS_WEBHOOK_URL, GOOGLECHAT_WEBHOOK_URL, FEISHU_WEBHOOK_URL — carry replies, they do not admit anyone.

    Who may talk to it

    An unknown address opens a pairing, not a session

    reply
    Welcome! Your pairing code is: `H7KQ2M`
    Please send `/pair H7KQ2M` to authorize.

    Six characters from A–Z0–9, minted per address while DM_POLICY=pairing

    The owner approves it on the machine, not in the chat

    cli
    raven pairing list
    raven pairing approve H7KQ2M
    raven doctor

    raven doctor lists the adapters that have credentials, so an inert one is visible

    Policy is a switch you throw on purpose

    DM_POLICY=open admits anyone who finds the bot; closed admits only addresses already in the store. CHANNEL_ALLOW_FROM takes a JSON map of channel to user ids and answers a refusal on the spot. Every approval is written to the audit log and counted in the metrics.

    The console the same process serves

    raven start mounts the built React console on the API it already runs — no separate web server, no CDN, no second port to firewall.

    shell
    cd web && npm install && npm run build
    raven start
    # http://127.0.0.1:18888 — WEB_HOST, WEB_PORT, RAVEN_WEB_DIST

    Bound to loopback by default; the gateway warns when you point WEB_HOST at 0.0.0.0. The bundle is looked up in RAVEN_WEB_DIST, then the wheel, then web/dist, and if none of them carry an index.html the API still starts and logs the build command instead.

    The 38 screens the console serves
    Group Routes under the SPA shell
    Agent agent agents chat chat/history collab contexts dream tasks
    Code code code-quality git github ide scaffold tests cicd
    Automation workflows routines monitors plugins chaos abtesting
    Knowledge knowledge rag finetune providers cost
    Insight analytics insights project-insights
    Content media voice email browser web-search
    Operations admin settings components

    /assets and a catch-all are mounted last, after every router, so /api/* keeps winning and an unknown path under /api/ answers 404 instead of the shell. The console speaks to those same routes: it is a client of the gateway, not a service beside it.

    Path

    How a request reaches a tool

    One protocol, three stages: a client frame, a confined server, a registered tool.

    Clients

    Local stdio subprocess
    Remote HTTP client
    Desktop chat app
    IDE extension
    Terminal coding agent
    Script or CI job
    JSON-RPC 2.0 · initialize → tools/list → tools/call

    raven-mcp

    Protocol handler
    Tool registry
    Workspace confinement
    Auth: X-Raven-Key or Bearer
    Tool execution, inside the allowed path

    Tools (66)

    File read / write / edit
    Shell + sandbox policy
    Web search / fetch
    Browser automation
    Git, tests, LSP
    Sub-agents, todos, memory
    Cron, artifacts, TTS

    Numbers from the repository

    Counted in the code on this branch, not estimated.

    Registered tools
    66
    Channel adapters
    15
    Transports
    stdio · HTTP
    Protocol version
    2025-03-26
    Runtime
    Python 3.11+
    License
    MIT

    Each tool carries its own JSON Schema, so tools/list returns the parameter names and types a client needs before it calls anything. The two stdio servers answer the same registry and differ only in the name they report at initialize: raven-mcp and ravencode.

    Where the server answers
    Surface Detail
    stdio, Raven server raven-mcp --workspace /path/to/project
    stdio, RavenCode server ravencode-mcp --workspace /path/to/project
    module entry points python -m raven.core.mcp / python -m ravencode.mcp
    JSON-RPC over HTTP POST /mcp/rpc
    plain tool list GET /mcp/tools
    direct tool call POST /mcp/tools/{name}
    events and liveness GET /mcp/events, GET /mcp/health
    auth X-Raven-Key or Authorization: Bearer
    path confinement --workspace, rejects outside paths and symlink escapes

    Refusals

    What the server refuses to do

    Self-hosting is only worth the name if the process says no by itself, with no policy service to consult and nothing to phone home to.

    • Step outside the workspace

      Every file tool resolves its target against the --workspace root with symlinks followed before the check. A path that lands outside is rejected, not clamped to the boundary.

    • Answer an unauthenticated call

      The HTTP transport requires X-Raven-Key or Authorization: Bearer. With no key configured it fails closed: the request is refused rather than waved through.

    • Trust an unknown sender

      The gateway's DM policy defaults to pairing, so a first message from an address it has not paired with gets a code instead of a session. open has to be chosen on purpose.

    • Let an identifier name a path

      Session ids are validated against ^[A-Za-z0-9_-]{1,128}$ at the HTTP, WebSocket, store and CLI boundaries, so one cannot address a directory outside the session root.

    • Hand a key to an export

      session export --sanitize masks entries whose names look like credentials and values that match provider key shapes, recursing through nested lists and objects.

    • Report a number it did not measure

      Nothing here is fetched from a third-party host: the fonts, the tool data and the recorded frames all ship from this directory, and the build fails if a request escapes it.

    Setup

    Install and configure

    Three steps from a clone to a client calling tools.

    Install the server

    shell
    git clone https://github.com/ssrjkk/raven
    cd raven
    pip install .

    Python 3.11+ · installs the raven-mcp and ravencode-mcp commands

    Add it to your client

    json
    {
      "mcpServers": {
        "raven": {
          "command": "raven-mcp",
          "args": ["--workspace", "/path/to/project"]
        }
      }
    }

    Same shape for every MCP client that starts a stdio server

    Call the tools

    Ask the assistant to read a file, run a command or search the web. It calls the MCP tool and Raven runs it inside the workspace you allowed. Over HTTP, the same tools answer POST /mcp/rpc with a bearer key.