ptm-mcp
MCP (Model Context Protocol) stdio server for the Prompt Test Manager API.
Lets agents built on top of MCP-capable clients (Claude Desktop, Claude Code, Codex, etc.) call PTM as first-class tools: list prompts, run evaluations, update prompt content, submit optimizations. All traffic is tagged with X-PTM-Client: ptm-mcp/<version> + a per-process X-PTM-MCP-Session UUID so the PTM backend can rate-limit, budget, and audit agent traffic separately from humans and service accounts.
Prereqs
- Python >= 3.12.
- A reachable PTM backend (>= 1.9.0) and a personal access token or service-account token with the scopes your flow needs.
Install
pip install ptm-mcp
Or zero-install via uvx:
uvx ptm-mcp
ptm-mcp pulls in ptm-client and the mcp SDK automatically.
Configure your MCP client
Claude Desktop
Config file:
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
Merge into mcpServers (create the key if it doesn't exist):
{
"mcpServers": {
"ptm": {
"command": "uvx",
"args": ["ptm-mcp"],
"env": {
"PTM_API_BASE_URL": "https://ptm.example.com",
"PTM_API_TOKEN": "ptm_u_PASTE_HERE",
"PTM_MCP_READ_ONLY": "true"
}
}
}
}
Fully quit + reopen Claude Desktop after editing. stderr lands in ~/Library/Logs/Claude/mcp-server-ptm.log (macOS) or %APPDATA%\Claude\logs\mcp-server-ptm.log (Windows).
Claude Code
# macOS / Linux
claude mcp add --transport stdio --scope user ptm \
--env PTM_API_BASE_URL=https://ptm.example.com \
--env PTM_API_TOKEN=ptm_u_PASTE_HERE \
--env PTM_MCP_READ_ONLY=true \
-- uvx ptm-mcp
# Windows (PowerShell / cmd) - needs cmd /c wrapper
claude mcp add --transport stdio --scope user ptm `
--env PTM_API_BASE_URL=https://ptm.example.com `
--env PTM_API_TOKEN=ptm_u_PASTE_HERE `
--env PTM_MCP_READ_ONLY=true `
-- cmd /c uvx ptm-mcp
Codex
~/.codex/config.toml (macOS / Linux) or %USERPROFILE%\.codex\config.toml (Windows):
[mcp_servers.ptm]
command = "uvx"
args = ["ptm-mcp"]
env = { PTM_API_BASE_URL = "https://ptm.example.com", PTM_API_TOKEN = "ptm_u_PASTE_HERE", PTM_MCP_READ_ONLY = "true" }
startup_timeout_sec = 10
tool_timeout_sec = 60
Or CLI-first: codex mcp add ptm -- uvx ptm-mcp (with --env KEY=VAL per var).
Environment variables
Consumed at startup. Missing required values fail fast with a descriptive error.
| Variable | Required | Default | Notes |
|---|---|---|---|
PTM_API_BASE_URL |
yes | - | e.g. https://ptm.example.com |
PTM_API_TOKEN |
yes | - | PTM bearer. Service-account tokens preferred for long-running agent sessions. |
PTM_MCP_READ_ONLY |
no | false |
Set to true to lock to read-only (recommended for service accounts that should never write). |
PTM_MCP_TIMEOUT_SECONDS |
no | 30 |
Per-request timeout (1..600). |
PTM_MCP_LOG_LEVEL |
no | INFO |
DEBUG / INFO / WARNING / ERROR / CRITICAL. |
PTM_SSL_VERIFY |
no | true |
Set to false to disable TLS certificate verification (allows self-signed / invalid certs against a homelab PTM). Local dev only - never disable in production. |
CF_ACCESS_CLIENT_ID |
no | - | Cloudflare Access service-token Client ID. Paired with CF_ACCESS_CLIENT_SECRET. |
CF_ACCESS_CLIENT_SECRET |
no | - | Cloudflare Access service-token Client Secret. Paired with CF_ACCESS_CLIENT_ID. |
CF_ACCESS_JWT |
no | - | Cloudflare Access user JWT (alternative to the service-token pair). |
PTM_CF_AUTO_DISCOVER |
no | true |
Falsy value opts out of auto-discovery via cloudflared. |
Startup scrubs every env var outside a narrow allow-list (cloud creds, GitHub tokens, etc. get dropped).
Cloudflare Access
If your PTM deployment sits behind Cloudflare Access:
- Default path (recommended): install
cloudflared(brew install cloudflaredor equivalent) and runcloudflared access login https://your-ptm-hostonce. ptm-mcp auto-detects CF challenges and injects the cached JWT on request. - Service token (CI / headless): ask an admin to mint a service token for the PTM app in Cloudflare Zero Trust -> Access -> Service Auth. Set
CF_ACCESS_CLIENT_ID+CF_ACCESS_CLIENT_SECRETin the MCP env block. Explicit config disables auto-discovery. - Direct access (no CF Access): skip this section; no CF env vars needed.
On a Cloudflare Access block, ptm-mcp surfaces a CloudflareAccessError with the exact next step rather than a raw JSON decode crash.
Tool inventory
75 tools total (33 read + 42 write) + 5 resource URI patterns. Write tools are gated by PTM_MCP_READ_ONLY=false; the backend enforces per-prompt ownership, group membership, admin role, and the manage_tool_registry / manage_skill_catalog permissions on top of the PAT's scopes. ptm-mcp relays backend errors verbatim with actionable hints; it does not re-implement permission checks.
Read (33)
list_providers, list_prompts, get_prompt, get_prompt_tests, get_prompt_contents, load_prompt, list_groups, list_prompt_versions, get_prompt_version, compare_prompt_versions, list_runs, get_run, get_run_report, get_optimization_status, get_optimization_history, get_optimization_detail, list_tools, get_tool_dependents, export_tool_registry, get_prompt_git_sync_status, get_skill_git_sync_status, list_git_integrations, list_gpg_keys, get_gpg_public_key, list_triage, list_skills, search_skills, get_skill, get_skill_contents, list_skill_versions, compare_skill_versions, run_skill_inline, run_prompt_inline.
get_skill_contents - Fetch skill bundle in memory (no disk write). Returns parsed prompt.md + manifest + files. No runs row, no LLM call. Increments inline-usage counter.
get_prompt_contents - Same for a library prompt: prompt_text, version, tests, deepeval_metrics, tool_definitions. Active version by default; pass version=N for a specific version.
run_skill_inline / run_prompt_inline - Client inline run: returns the bundle/prompt contents for the caller to execute locally on the current session's model. No server LLM call, no DeepEval, no run row.
Write (42, gated by PTM_MCP_READ_ONLY)
Eval / optimization (4): run_manual_eval (QA eval with custom prompt + test cases), run_prompt_eval (QA eval against stored test suite), submit_optimization, cancel_optimization.
submit_optimization accepts variance-aware fields: stability_samples, validation_samples, flakiness_threshold, min_consistent_improvement, variance_aware_mutator, variance_signal, enforce_target_score, chained_baseline_mode. Omit any to inherit admin defaults.
Inline eval (4): run_skill_eval_ptm / run_prompt_eval_ptm (PTM runs the LLM + DeepEval server-side, persists a scored run row), run_skill_eval_client_and_ptm / run_prompt_eval_client_and_ptm (returns the bundle for local execution AND enqueues a parallel scored PTM eval; response includes run_key for polling).
Library mutation (9): create_prompt, update_prompt, activate_prompt_version, share_prompt, unshare_prompt, add_prompt_to_group, remove_prompt_from_group, transfer_prompt_ownership, update_prompt_sampling.
Skill Library (6): install_skill, load_skill (installs the bundle into the native-agent skills dir, no LLM call, no run row), publish_skill, update_skill, deprecate_skill, activate_skill_version (version rollback/forward).
Tool Registry (4): create_tool, update_tool, deprecate_tool, delete_mock_profile.
Git sync (5): configure_prompt_git_sync, configure_skill_git_sync, set_git_integration, delete_git_integration, create_git_sync_pr.
GPG keys (5): generate_gpg_key, import_gpg_key, export_gpg_secret_key, set_primary_gpg_key, delete_gpg_key.
Triage (4): promote_run_to_golden, resolve_triage_item, reopen_triage_item, bulk_resolve_triage.
Runs (1): submit_run_feedback.
Permissions model
| Action | Required |
|---|---|
| Read tools | Valid PAT (results filtered by visibility scope) |
update_prompt, activate_prompt_version |
Prompt owner OR prompt-overwrite role |
share_prompt, group tools |
Admin OR group-manager role |
transfer_prompt_ownership |
Admin ONLY |
| Eval + run tools | run_evaluations scope |
get_skill_contents, get_prompt_contents |
install_skill scope |
Backend error codes surface as actionable tool errors:
- 403 -> "Permission denied. Check prompt ownership or required role."
- 404 -> "Not found. Verify prompt_id / skill_id and PAT visibility scope."
- 409 -> "Conflict. Repository-backed prompt (edit source files) OR concurrent writer. Re-fetch and retry."
- 422 -> "Validation failed - : ." when the backend names the offending field (e.g.
team: String should have at least 1 character), otherwise a generic causes list (empty required field, prompt_text length max 500k, test / metric shapes, field types).
Resources (5 URI patterns)
ptm://prompts/{prompt_id}- active version'sprompt_text(text/plain)ptm://prompts/{prompt_id}/v{N}- that version'sprompt_text(text/plain)ptm://runs/{run_key}/report.md- markdown report (text/markdown)ptm://runs/{run_key}/report.html- HTML report (text/html)ptm://optimizations/{optimization_id}/report.md- markdown summary (text/markdown)
Dynamic segments are allow-list validated (^[a-zA-Z0-9_.-]+$ plus explicit ./.. rejection).
Security defaults
PTM_MCP_READ_ONLY=trueblocks every write tool at call time.X-PTM-Client+X-PTM-MCP-Sessionon every outbound request so the backend can classify and audit agent traffic.- Env scrub at startup drops anything outside the allow-list.
- Startup preflight (
/healthz+/auth/me+/meta) with exponential backoff on transient failures and dedicated exit codes per failed layer.
Exit codes
| Code | Meaning |
|---|---|
0 |
clean shutdown |
1 |
unhandled exception |
2 |
/healthz unreachable after 31s of backoff |
3 |
/auth/me rejected the token |
4 |
backend version < 1.9.0 or unparseable |
130 |
interrupted (SIGINT) |
Status
0.19.0. 75 tools: full PTM surface (prompts, evals, optimization, triage, runs, skills, tool registry, git sync, GPG keys, inline execution + contents fetch) + 5 resource URI patterns, read-only gate, Cloudflare Access auto-discovery. See CHANGELOG.md for release notes.
Release files for ptm-mcp 0.21.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ptm_mcp-0.21.0.tar.gz | 65.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ptm_mcp-0.21.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 140.1 kB
Release files / ptm_mcp-0.21.0.tar.gz
| Download URL | ptm_mcp-0.21.0.tar.gz |
|---|---|
| Size | 65.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
38c56e0e2a591a604df31f041eca9541743aa8da98e80d1d42472e443213178a
|
|
BLAKE2b-256 checksum How to use checksums |
9f20fbc737bc14d323a1005ce6647dc22bf47a8fbe379076283e5838022cdd6b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.15
|
Release files / ptm_mcp-0.21.0-py3-none-any.whl
| Download URL | ptm_mcp-0.21.0-py3-none-any.whl |
|---|---|
| Size | 74.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
59038d90bbc40d133529aab4730c68ab18b7c901112b98e767271c5ef28c8bf2
|
|
BLAKE2b-256 checksum How to use checksums |
c2be1d0bc3ea01c40e020a8d41f14eefb5c9a783aa619926adc28abdb5fc406c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.15
|