readonly-mcp-server
readonly-mcp-server
An MCP (Model Context Protocol) server with:
Transport layer configuration —
stdiofor local process spawning, or HTTP+SSE for a networked/shared deployment. Selection follows "ifstdioelsehttp+sse": anyMCP_TRANSPORTvalue other than the literalstdiofalls 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,
readandsearch. 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 clientsInstall
pip install -r requirements.txt
cp .env.example .env # then edit as neededConfiguration (environment variables)
Variable | Purpose | Default |
|
|
|
| HTTP+SSE bind address |
|
| HTTP+SSE endpoint paths |
|
| Path to key→scope config |
|
| Pre-shared key required for stdio clients | (unset — required) |
| Disable HTTP auth (local dev only) |
|
| Metadata URLs for the HTTP auth middleware |
|
| Path to the document data file |
|
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.pyPoint 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.pyClients 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 all document ids + titles |
|
| Fetch full content of one document by id |
|
| 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+AuthSettingssupport.ApiKeyTokenVerifier(inauth.py) adapts our flat API-key file to that protocol; the SDK then wraps both the/sseand/messagesendpoints withAuthenticationMiddleware+RequireAuthMiddlewareautomatically, 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 viaget_access_token()(HTTP) or the stdio session's validated key (stdio), raising if the scope isn't present.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Varun4b1/mcp-server-py'
If you have feedback or need assistance with the MCP directory API, please join our Discord server