io.github.toosoontospoon/crate-quote
# mcp-services
MCP ([Model Context Protocol](https://modelcontextprotocol.io)) tools for several business services,
one module per service. Each module is a thin client of that business's own HTTP API: no business
logic lives here. The first service is **crate**, Speed Crate quotes for custom wooden shipping crates.
<!-- mcp-name: io.github.toosoontospoon/crate-quote -->
- One server per service, so each can be listed on its own (MCP Registry, Muse Connector
Platform), or all services in one server.
- Transports: **stdio** (local clients such as Claude Desktop or Cursor) and **streamable HTTP**
(hosted endpoints).
- Official Python MCP SDK 2.x (`MCPServer`, which replaced FastMCP), `httpx` for the backend APIs.
- MIT licensed.
## Quick start
pip install toosoon-mcp-services # not on PyPI yet: pip install -e ".[test]" from a checkout
export SPEEDCRATE_API_URL=http://127.0.0.1:8080 # the crate API (CustomCrateBuilder speedcrate-api)
toosoon-mcp-services --list # services and their tools
mcp-crate-quote # crate service only, stdio
toosoon-mcp-services --service crate # same
toosoon-mcp-services # every enabled service in one server
toosoon-mcp-services --service crate --transport streamable-http --host 127.0.0.1 --port 8765
# -> http://127.0.0.1:8765/mcp (--stateless for load balancers)
Client config (stdio), e.g. Claude Desktop:
```json
{ "mcpServers": { "crate-quote": {
"command": "uvx", "args": ["toosoon-mcp-services", "--service", "crate"],
"env": { "SPEEDCRATE_API_URL": "https://YOUR-CRATE-API" } } } }
```
## Configuration
| Variable | Meaning |
|---|---|
| `MCP_SERVICES` | Comma list of services to run when `--service` isn't given (default: all enabled) |
| `<PREFIX>_API_URL` | Base URL of the service's API (required to call its tools) |
| `<PREFIX>_API_KEY` | Optional API key, sent as `Authorization: Bearer …`. Never logged |
| `<PREFIX>_TIMEOUT` | Request timeout in seconds (default 20) |
| `<PREFIX>_ENABLED` | `0` leaves the service out of "all services" |
Prefixes: crate = `SPEEDCRATE`.
## Services
### crate: Speed Crate quotes (`SPEEDCRATE_*`)
Calls the CustomCrateBuilder HTTP API (`speedcrate-api`). Sizes are inside inches. Quotes need the
customer's email. They return **only a price range** (plus code, sizes, layout and notices) and say the
full quote will be emailed to that address. The API queues the full-quote request; this server sends
no email.
| Tool | API call | Access | Sensitive data | Side effects |
|---|---|---|---|---|
| `quote_crate` | `POST /v1/quote` | **write** | customer email (PII) | queues a full-quote request; the shop emails the customer |
| `quote_crate_by_code` | `POST /v1/quote/by-code` | **write** | customer email (PII) | same |
| `parse_crate_code` | `POST /v1/codes/parse` | read-only | none | none |
| `encode_crate_code` | `POST /v1/codes/encode` | read-only | none | none |
| `crate_size_limits` | `GET /v1/limits` | read-only | none | none |
| `crate_preview_svg` | `GET /v1/designs/{code}/preview.svg` | read-only | none | none |
The tools carry matching MCP annotations: `readOnlyHint` is true for the read tools. The quote tools
are `readOnlyHint: false`, `destructiveHint: false`, `idempotentHint: false`, `openWorldHint: true`.
## MCP Registry entries (one per service)
Each service gets its own registry entry and `server.json` under `servers/<registry-name>/`. All
entries point at the **same PyPI package** (`toosoon-mcp-services`) with a `--service <name>` package
argument, plus that service's environment variables. The registry checks PyPI ownership by looking for
an `mcp-name` line with the registry name in the package README (this file). Every service's name
gets its own line at the top of this file.
| Service | Registry name | server.json |
|---|---|---|
| crate | `io.github.toosoontospoon/crate-quote` | `servers/crate-quote/server.json` |
Publishing (not done yet):
1. Release the package to PyPI.
2. `mcp-publisher login github` as toosoontospoon.
3. `mcp-publisher publish servers/crate-quote/server.json`.
`mcp-publisher validate servers/crate-quote/server.json` passes today.
<!-- TODO(remotes): when the crate MCP server is hosted (streamable HTTP, needed for Muse Connector
Platform), add to servers/crate-quote/server.json:
"remotes": [{ "type": "streamable-http", "url": "https://<HOST>/mcp" }]
No hosted URL exists yet. -->
**Hosted endpoint (TODO):** no public streamable-HTTP URL exists yet. When there is one, add a
`remotes` entry (`{"type": "streamable-http", "url": "https://<host>/mcp"}`) to the service's
server.json. A public deployment also needs TLS in front of it, an allowed Host/Origin list (the SDK's
DNS-rebinding protection), and probably `--stateless`.
## Development
uv venv && uv pip install -e ".[test]"
pytest # unit tests with a mocked API (respx)
CRATE_API_REPO=/path/to/CustomCrateBuilder CRATE_API_PYTHON=/path/to/its/python pytest
# + integration tests against a local uvicorn of the API
SPEEDCRATE_TEST_API_URL=http://127.0.0.1:8080 pytest # or against a running API
See [CONTRIBUTING.md](CONTRIBUTING.md) to add a service.
TDQS
Scored across 6 tools
quote_crate and quote_crate_by_code have overlapping purpose but are clearly distinguished by input type (dimensions vs configuration code), and the description explicitly cross-references them. parse_crate_code and encode_crate_code are inverse operations with distinct roles, and crate_size_limits and crate_preview_svg are unambiguous. Only the two quote tools risk mild confusion.
Most tools use a verb_noun pattern (quote_crate, parse_crate_code, encode_crate_code) with a shared 'crate' token, giving good predictability. crate_size_limits and crate_preview_svg deviate to noun-first phrasing, a minor inconsistency but still readable and clearly scoped.
Six tools is well-scoped for a crate-quoting domain: two quoting paths, code parse/encode, size limits, and a preview generator. Each tool earns its place with no redundant or filler entries.
The surface covers the core lifecycle: quote from dimensions or code, encode/decode codes, check size limits, and render a preview. There is no tool to retrieve or manage previously submitted quote requests, but quoting plus code handling covers the main workflows an agent would need.