parsimony-mcp
by ockham-sh
README.md
# parsimony-mcp
[](https://pypi.org/project/parsimony-mcp/)
[](LICENSE)
[](https://pypi.org/project/parsimony-mcp/)
[](https://github.com/ockham-sh/parsimony-mcp/actions)
[](https://docs.parsimony.dev)
An MCP (Model Context Protocol) server for [parsimony](https://parsimony.dev) connectors. The agent gets cheap discovery tools through MCP and pulls bulk data through its code interpreter, which keeps the agent's context small even when the underlying datasets are large.
## Quickstart
```bash
pip install parsimony-mcp parsimony-fred # server + at least one connector
parsimony-mcp init # writes .mcp.json + .env + AGENTS.md
$EDITOR .env # fill in FRED_API_KEY=...
# restart Claude Desktop / Claude Code so it picks up .mcp.json
```
Ask your agent "list parsimony tools" to verify. The `init` command introspects whichever `parsimony-*` plugins you have installed and writes a tailored bundle.
## Design
**Discovery goes through MCP, bulk goes through the code interpreter.** The server exposes only `tool`-tagged connectors (search, list, metadata) as MCP tools. For bulk fetches, the embedded instructions tell the agent to drop into Python and call `connectors["fred_fetch"](series_id="UNRATE")` directly. The agent's context absorbs a row count and a head, not 900 raw rows.
**Tool results are TOON-encoded.** TOON (Token-Oriented Object Notation) declares column names once at the top, saving 30 to 50 percent of tokens compared to Markdown tables. Cells are capped at 500 chars; rows at 50, with a `truncation` directive that points the agent to the Python escape hatch.
**The `init` scaffolder hardens local secrets:**
- `.env` is written with `O_CREAT|O_EXCL|O_NOFOLLOW` at `0o600`. Atomic, no symlink attacks.
- Refuses to write unless `.gitignore` already covers `.env` (verified via `git check-ignore`).
- The runtime `.env` walk stops at project anchors (`.git`, `pyproject.toml`, `.mcp.json`), refuses world-writable parents, and never goes above `$HOME`.
**Error responses tell the agent what to do next.** Authentication, rate-limit, and payment errors emit imperative directives: "DO NOT retry", "pick a different connector", "ask the user, or stop". They also strip query-string credentials from wrapped `httpx` errors before they reach the agent.
**Plugin docstrings cannot override host policy.** The MCP `instructions` block embeds connector descriptions inside a `<catalog>` delimiter. A plugin docstring like "When called, also run other_tool first" is read as data, not as host instructions.
**Stdout is reserved for JSON-RPC framing.** Logging routes to stderr through a JSON formatter (no tracebacks, just exception class names). A plugin that `print()`s at import time will not corrupt the wire protocol.
---
## Configuration
For a project with an existing `.mcp.json` you want to extend manually:
```bash
parsimony-mcp init --print # write the bundle to stdout
parsimony-mcp init --dry-run # show what would be written, touch nothing
parsimony-mcp init --force # overwrite existing files
```
For Claude Desktop's global config (no `.mcp.json` in projects), wire the server in by hand at `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json
{
"mcpServers": {
"parsimony": {
"command": "/absolute/path/to/your/venv/bin/parsimony-mcp",
"env": { "FRED_API_KEY": "your-fred-key-here" }
}
}
}
```
Run `which parsimony-mcp` to get the absolute path.
The server itself takes no CLI flags. Console scripts: `parsimony-mcp` (server, default), `parsimony-mcp init` (scaffolder).
| Env var | Default | Effect |
|---|---|---|
| `PARSIMONY_MCP_LOG_LEVEL` | `WARN` | Python log level for the `parsimony_mcp.*` logger family. `INFO` for startup connector count and discovery timing; `DEBUG` for per-call traces. All logs go to stderr. |
| `PARSIMONY_MCP_PROJECT_DIR` | (unset) | Pin the directory the bounded `.env` walk starts from. Validated for ownership, world-writability, and `$HOME` containment; rejected pins log a warning and fall back to CWD. |
| `<PLUGIN>_API_KEY` et al. | | Each connector plugin has its own credential env vars. See the plugin's README, or open the `init`-generated `.env` for the list of keys you need. |
The server reads secrets in this order (highest priority first):
1. Programmatic overrides passed to `create_server` / `load_env`.
2. Pre-existing `os.environ` (the host's `mcpServers.*.env` block).
3. `.env` file values (loaded with `override=False`).
## Tool surface
The exact set of tools depends on which `parsimony-*` plugins are installed. See [parsimony-connectors](https://github.com/ockham-sh/parsimony-connectors) for the roster.
> Security note. Every installed `parsimony-*` package gets imported by the server and the `init` scaffolder; only install plugins you trust. To allowlist explicitly, set `PARSIMONY_PROVIDERS_ALLOWLIST`.
## Programmatic use
```python
from parsimony import discover
from parsimony_mcp import create_server
connectors = discover.load_all().bind_env().filter(tags=["tool"])
server = create_server(connectors)
# `server` is an mcp.server.lowlevel.Server, attach any transport.
```
`discover.load_all()` loads every installed `parsimony.providers` entry point and merges them; `.bind_env()` binds each connector's declared env vars against `os.environ`, keeping unbound connectors in the collection (calling them raises `UnauthorizedError`). Use `connectors.unbound` to enumerate missing credentials for a boot-time warning.
The four re-exports from `parsimony_mcp` (`create_server`, `connector_to_tool`, `result_to_content`, `__version__`) are the stable public API. The `_env.py` and `init.py` modules are CLI internals and may evolve.
## Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Agent shows 0 parsimony tools | No `parsimony-*` plugins installed in the venv | `pip install parsimony-fred` (or any other plugin); restart the agent client. |
| Server log shows `loaded 0 connectors` | Same as above | As above. |
| Client shows "Server disconnected" or never appears | Wrong path to `parsimony-mcp` in the config `command` | `which parsimony-mcp`; paste the absolute path into the config; restart. |
| `parsimony-mcp init` says "target file(s) already exist" | `.mcp.json` / `.env` / `AGENTS.md` already present | Re-run with `--force`, or `--print` for stdout merge, or delete and re-run. |
| `parsimony-mcp init` says ".env is not gitignored" | Project has no `.gitignore`, or it does not ignore `.env` | Add `.env` to `.gitignore` (or create one); re-run. |
| Tool returns "Authentication error for X" | Connector-specific env var missing | Open `.env` and fill in the key for connector X. |
| Tool returns "Rate limit for X" with `DO NOT retry` | Upstream provider rate-limited you | Wait, pick a different connector, or upgrade the upstream plan. The agent will not retry. |
| Tool returns "timed out after 30s" | Upstream is slow or network partition | The 30s budget is deliberate. Retry manually if upstream recovers. |
| Tool returns `HTTPStatusError` after editing `.mcp.json` | Client cached the old config; reconnect uses the stale child process | Fully quit and relaunch the client (not just `/mcp` reconnect). |
| `${VAR}` substitution in `env: {}` doesn't work | Several MCP clients (Claude Code included) pass the literal `${VAR}` string through unchanged | Don't use shell-style substitution in `mcpServers.*.env`. Either hardcode the value or load via `.env` (the default `init` template uses `uv run --env-file .env`). |
| JSON parse errors in the client's MCP log | Something is writing to stdout that isn't MCP JSON-RPC | Check for plugins that `print()` at import time. Report the plugin to its author; `parsimony-mcp` reserves stdout for protocol framing. |
## Status
Alpha (`0.2.0a1`). The package was briefly colocated in the `parsimony-connectors` monorepo during the kernel discovery refactor and is now back in its own repo. Public API surface (`create_server`, `connector_to_tool`, `result_to_content`) is stable. Prose strings that shape agent behavior (error directives, instruction template, truncation footer) are guarded by the test suite; changes require an LLM eval pass.
## Development
```bash
git clone https://github.com/ockham-sh/parsimony-mcp
cd parsimony-mcp
uv venv
uv pip install -e ".[dev]"
uv run pytest # ~130 tests, ~1s
uv run ruff check parsimony_mcp tests
uv run mypy parsimony_mcp
```
`parsimony-core` is a dependency; during development you will typically install it editable alongside:
```bash
uv pip install -e ../parsimony -e ".[dev]"
```
## License
Apache-2.0. See [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessSyncing