Skip to main content
Glama
Varun4b1

readonly-mcp-server

by Varun4b1

readonly-mcp-server

An MCP (Model Context Protocol) server with:

  • Transport layer configuration — stdio for local process spawning, or HTTP+SSE for a networked/shared deployment. Selection follows "if stdio else http+sse": any MCP_TRANSPORT value other than the literal stdio falls back to HTTP+SSE.

  • Connection configuration (authentication) — bearer API keys on HTTP+SSE (Authorization: Bearer <key>, required on the SSE connection and every message POST), and a pre-shared key on stdio so the same auth model applies to local connections too.

  • Authorization limits (read / search only) — the server exposes exactly two tool categories, read and search. There are no write/update/delete tools at all, and each API key is independently granted a subset of {read, search}, enforced on every tool call.

Project layout

mcp-server-py/
├── config/
│   ├── api_keys.json     # key -> client_id + scopes (read/search)
│   └── documents.json    # sample read-only data backing the tools
├── src/
│   ├── config.py          # env-driven transport/auth/data settings
│   ├── auth.py            # API key store, TokenVerifier, scope checks
│   ├── data_store.py       # read-only document backend
│   ├── tools.py            # list_documents / read_document / search_documents
│   └── server.py           # wires transport + auth + tools, entry point
├── requirements.txt
├── .env.example
└── test_stdio_client.py / test_http_client.py   # example clients

Related MCP server: PDF MCP Server

Install

pip install -r requirements.txt
cp .env.example .env   # then edit as needed

Configuration (environment variables)

Variable

Purpose

Default

MCP_TRANSPORT

stdio or anything else → HTTP+SSE

stdio

MCP_HTTP_HOST / MCP_HTTP_PORT

HTTP+SSE bind address

0.0.0.0 / 8000

MCP_SSE_PATH / MCP_MESSAGE_PATH

HTTP+SSE endpoint paths

/sse / /messages/

MCP_API_KEYS_FILE

Path to key→scope config

config/api_keys.json

MCP_STDIO_API_KEY

Pre-shared key required for stdio clients

(unset — required)

MCP_REQUIRE_AUTH

Disable HTTP auth (local dev only)

true

MCP_ISSUER_URL / MCP_RESOURCE_SERVER_URL

Metadata URLs for the HTTP auth middleware

http://localhost:8000

MCP_DOCUMENTS_FILE

Path to the document data file

config/documents.json

Edit config/api_keys.json to add/remove keys. Each key lists scopes, which may only contain read and/or search — anything else is rejected at startup.

Run — stdio transport

MCP_TRANSPORT=stdio MCP_STDIO_API_KEY=demo-read-search-key-change-me \
  python3 src/server.py

Point an MCP client (Claude Desktop, Claude Code, etc.) at this command, passing MCP_STDIO_API_KEY in the client's configured environment for the server process, e.g. in claude_desktop_config.json:

{
  "mcpServers": {
    "readonly-docs": {
      "command": "python3",
      "args": ["/absolute/path/to/mcp-server-py/src/server.py"],
      "env": {
        "MCP_TRANSPORT": "stdio",
        "MCP_STDIO_API_KEY": "demo-read-search-key-change-me"
      }
    }
  }
}

Run — HTTP+SSE transport

MCP_TRANSPORT=http MCP_HTTP_PORT=8000 python3 src/server.py

Clients connect to GET http://host:8000/sse and POST /messages/, both requiring Authorization: Bearer <api-key>. See test_http_client.py for a minimal client example using the official mcp Python SDK.

Tools exposed

Tool

Required scope

Description

list_documents

read

List all document ids + titles

read_document

read

Fetch full content of one document by id

search_documents

search

Keyword search returning matches + snippets

A key missing the required scope gets a clear isError=true tool result (Access denied: this operation requires the 'X' scope...) rather than a crash or silent failure.

Design notes / how the auth is wired

  • HTTP+SSE: uses the MCP Python SDK's built-in TokenVerifier + AuthSettings support. ApiKeyTokenVerifier (in auth.py) adapts our flat API-key file to that protocol; the SDK then wraps both the /sse and /messages endpoints with AuthenticationMiddleware + RequireAuthMiddleware automatically, and binds each SSE session to the credential that opened it (a POST with a different/no credential for an existing session is rejected).

  • stdio: authenticated once at process startup via MCP_STDIO_API_KEY; scopes are held for the lifetime of the process. All logging goes to stderr — the stdio transport reserves stdout for JSON-RPC protocol frames.

  • Scope enforcement: auth.require_scope() is called at the top of every tool function and reads the caller's granted scopes via get_access_token() (HTTP) or the stdio session's validated key (stdio), raising if the scope isn't present.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A read-only MCP server for PDF analysis that enables text extraction, image extraction, metadata retrieval, and text search via natural language.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    This MCP server provides read-only access to a single allowed local directory through a secure HTTP gateway, enabling remote MCP clients to read files without syncing them to cloud storage.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    A security-first, read-only MCP server that lets clients browse and read text, PDF, and XLSX files from an explicit allowlist of local folders, with strict path and secret protections.
    MIT