PEP MCP Server
# PEP MCP Server
MCP server that exposes on-demand Python PEP lookup tools backed by the live
PEP index.
## Features
- `list_peps`: list only active PEPs
- `search_peps`: search active PEP titles
- `get_pep`: fetch a PEP document by number, optionally returning focused
excerpts for a query
## Data Sources
- PEP index JSON: `https://peps.python.org/api/peps.json`
- PEP content:
- `https://raw.githubusercontent.com/python/peps/main/peps/pep-XXXX.rst`
- Fallback: `https://github.com/python/peps/blob/main/peps/pep-XXXX.rst?plain=1`
## Setup
```bash
python -m venv .venv
.venv/bin/pip install -e ".[dev]"
```
## Run
```bash
.venv/bin/pep-mcp-server
```
or:
```bash
.venv/bin/python -m pep_mcp_server
```
## Docker
Build and tag:
```bash
docker build -t pep-mcp-server:latest .
```
MCP uses stdio, so the container must keep stdin open (`-i`). Example:
```bash
docker run --rm -i pep-mcp-server:latest
```
### Cursor MCP (Docker)
**You do not put PEP or GitHub URLs in `mcp.json`.** Cursor only needs the command that runs the server; listing and fetching PEPs happens inside the process when tools run.
Use `-i` (required for stdio). Optional `-e` lines silence the startup banner and pin transport:
```json
{
"mcpServers": {
"pep": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-e",
"FASTMCP_TRANSPORT=stdio",
"-e",
"FASTMCP_SHOW_SERVER_BANNER=false",
"pep-mcp-server:latest"
]
}
}
}
```
If the image is not on this machine yet, build it once from the project directory (see above).
If the MCP log shows `Found 0 tools` but `listOfferingsForUI` / `Not connected` warnings, that is often a Cursor UI race or a separate UI listing path; try reloading the window or invoking a tool from chat. The server still exposes three tools over stdio (verified with the MCP Python client).
## Tool Contracts
### `list_peps() -> list[dict]`
Returns active PEPs with:
- `number`
- `title`
- `type`
- `topic`
- `created`
- `url`
(`status` is omitted; every row is active.)
### `search_peps(query: str) -> list[dict]`
Case-insensitive substring search on active PEP titles.
### `get_pep(pep, query=None, max_full_content_chars=None) -> dict`
- Accepts `8`, `0008`, or `pep-0008`.
- Returns metadata and:
- `content` when `query` is not provided (capped by default for token efficiency)
- `excerpt` when `query` is provided and matches
- `content` fallback when `query` has no matches (also capped by default)
- Optional `max_full_content_chars`: omit or `None` for the default cap; use `0` for the full document (can be very large).
## Tests
```bash
.venv/bin/pytest -q
```
TDQS
Scored across 3 tools
The three tools have clearly distinct purposes: 'get' retrieves a specific PEP by number, 'list' enumerates all active PEPs, and 'search' finds PEPs by title query. No functional overlap exists between them.
All tools follow a consistent snake_case verb_noun pattern (get_pep, list_peps, search_peps). The pluralization appropriately matches the return cardinality (singular for single retrieval, plural for collection operations).
Three tools is the ideal minimum for a read-only document server, covering the essential access patterns: enumeration, search, and specific retrieval. The scope is tightly focused without bloat.
While the basic read operations are present, notable gaps exist: list_peps and search_peps are restricted to 'active' PEPs only with no way to access other statuses, search_peps only searches titles (not content or authors), and there are no filtering options for metadata like author or Python version.