Safe-by-default MCP-style tool server generator from OpenAPI specs.
Project description
MCPGen
Generate secure, policy-aware MCP servers from OpenAPI specs.
MCPGen is a production-oriented MVP Python framework that turns OpenAPI specifications into safe-by-default tool servers. It can generate a FastAPI demo server or an MCP-style stdio server, while keeping write operations blocked unless future policy work explicitly enables them.
Problem
AI applications often need access to many APIs, databases, and internal systems. Without a framework, teams tend to rebuild the same integrations repeatedly, expose too many tools to the model, and skip safety controls such as risk classification, audit logs, and write-operation guardrails.
MCP servers make tools available to AI systems, but fast prototypes can accidentally expose dangerous operations like DELETE, POST, PATCH, or PUT without review.
Solution
MCPGen reads an OpenAPI YAML or JSON file and generates:
- structured tool descriptors
- safe exposed tool lists
- withheld tool reports
- input schemas
- a policy-aware FastAPI or MCP server
- dry-run previews
- safe
GETexecution - JSONL audit logs
- file-based runtime metrics
- semantic tool routing with keyword fallback
The default behavior is intentionally conservative: only low-risk GET tools are exposed.
Features
- OpenAPI YAML/JSON parsing
- Tool generation from endpoints
- Risk classification:
GET= lowPOST,PUT,PATCH= mediumDELETE= high
- Safe-by-default filtering
- Input schema generation from path/query parameters and JSON request bodies
- Semantic tool routing with keyword fallback
- FastAPI mode
- MCP stdio mode with
tools/listandtools/call - Dry-run request previews
- Safe real execution for low-risk
GETtools only - Central policy engine
- JSONL audit logging
- Runtime metrics for routing, policy decisions, dry-runs, execution outcomes, and latency
- Upstream auth passthrough and API key injection without hardcoded secrets
- Lightweight in-memory rate limiting
- Runtime input validation for required fields, basic types, and enums
doctordiagnostics for specs and config readiness- Mock execution and failure injection for offline development
- CLI commands:
generate,inspect - Config via
mcpgen.yaml
Architecture Flow
OpenAPI spec
-> parser
-> tool generator
-> risk classifier
-> safety filter
-> tools.json / tools.all.json / tools.embeddings.json / safety_report.json
-> generated FastAPI or MCP server
-> policy engine
-> semantic/keyword router
-> dry-run or safe GET execution
-> audit log
-> metrics summary
Quick Start
Install locally:
pip install -e .[dev]
Install from PyPI after publishing:
pip install openapi-mcpgen
Inspect a spec:
mcpgen inspect --from examples/jsonplaceholder.openapi.yaml
Run diagnostics:
mcpgen doctor --from examples/jsonplaceholder.openapi.yaml
Generate a FastAPI server:
mcpgen generate --from examples/jsonplaceholder.openapi.yaml --mode fastapi --output generated_jsonplaceholder
Run it:
cd generated_jsonplaceholder
export API_BASE_URL=https://jsonplaceholder.typicode.com
uvicorn server:app --reload --port 8001
PowerShell:
cd generated_jsonplaceholder
$env:API_BASE_URL = "https://jsonplaceholder.typicode.com"
uvicorn server:app --reload --port 8001
Open:
http://127.0.0.1:8001/
http://127.0.0.1:8001/docs
http://127.0.0.1:8001/tools
http://127.0.0.1:8001/safety
http://127.0.0.1:8001/metrics
Example OpenAPI Input
Demo spec:
examples/jsonplaceholder.openapi.yaml
It includes:
GET /usersGET /users/{id}GET /postsGET /posts/{id}POST /postsDELETE /posts/{id}
Excerpt:
paths:
/users/{id}:
get:
operationId: getUserById
summary: Get user by ID
parameters:
- name: id
in: path
required: true
schema:
type: integer
Generated Tool Example
Generated safe tool:
{
"name": "get_user_by_id",
"description": "Get user by ID",
"method": "GET",
"path": "/users/{id}",
"risk_level": "low",
"enabled": true,
"operation_id": "getUserById",
"input_schema": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "User ID",
"x-mcpgen-location": "path"
}
},
"required": ["id"]
}
}
Withheld tools such as create_post and delete_post remain in tools.all.json and are explained in safety_report.json.
FastAPI Demo Commands
From the project root:
mcpgen generate --from examples/jsonplaceholder.openapi.yaml --mode fastapi --output generated_jsonplaceholder
cd generated_jsonplaceholder
export API_BASE_URL=https://jsonplaceholder.typicode.com
uvicorn server:app --reload --port 8001
PowerShell:
mcpgen generate --from examples/jsonplaceholder.openapi.yaml --mode fastapi --output generated_jsonplaceholder
cd generated_jsonplaceholder
$env:API_BASE_URL = "https://jsonplaceholder.typicode.com"
uvicorn server:app --reload --port 8001
List exposed safe tools:
curl http://127.0.0.1:8001/tools
Route tools by query:
curl -X POST http://127.0.0.1:8001/tools \
-H "Content-Type: application/json" \
-d "{\"query\":\"get user by id\"}"
PowerShell equivalent:
Invoke-RestMethod -Method Post http://127.0.0.1:8001/tools `
-ContentType "application/json" `
-Body '{"query":"get user by id"}'
Dry-run a safe tool:
curl -X POST http://127.0.0.1:8001/tools/get_user_by_id/dry-run \
-H "Content-Type: application/json" \
-d "{\"inputs\":{\"id\":1}}"
Execute a safe GET tool:
curl -X POST http://127.0.0.1:8001/execute \
-H "Content-Type: application/json" \
-d "{\"tool_name\":\"get_user_by_id\",\"params\":{\"id\":1}}"
PowerShell equivalent:
Invoke-RestMethod -Method Post http://127.0.0.1:8001/execute `
-ContentType "application/json" `
-Body '{"tool_name":"get_user_by_id","params":{"id":1}}'
Show blocked POST behavior:
curl -X POST http://127.0.0.1:8001/tools/create_post/dry-run \
-H "Content-Type: application/json" \
-d "{\"inputs\":{\"title\":\"Hello\",\"body\":\"Demo\",\"userId\":1}}"
Show blocked DELETE behavior:
curl -X POST http://127.0.0.1:8001/tools/delete_post/dry-run \
-H "Content-Type: application/json" \
-d "{\"inputs\":{\"id\":1}}"
Show audit log:
cat logs/audit.log
PowerShell equivalent:
Get-Content logs\audit.log
Show metrics:
curl http://127.0.0.1:8001/metrics
PowerShell equivalent:
Invoke-RestMethod http://127.0.0.1:8001/metrics
MCP Mode
Generate an MCP-style stdio server:
mcpgen generate --from examples/jsonplaceholder.openapi.yaml --mode mcp --output generated_jsonplaceholder_mcp
Run:
cd generated_jsonplaceholder_mcp
export API_BASE_URL=https://jsonplaceholder.typicode.com
python server.py
PowerShell:
cd generated_jsonplaceholder_mcp
$env:API_BASE_URL = "https://jsonplaceholder.typicode.com"
python server.py
Example tools/list JSON-RPC input:
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
Example tools/call dry-run input:
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_user_by_id","arguments":{"id":1}}}
MCP mode uses the same tools.json, policy engine, and audit logging as FastAPI mode. In the current MVP, tools/call is dry-run by default. With execution_mode: safe-execute, it can execute only low-risk GET tools.
Semantic Tool Routing
MCPGen writes tools.embeddings.json during generation and uses it when:
routing_mode: semantic
If embeddings are unavailable or semantic ranking fails, MCPGen automatically falls back to keyword routing. You can force keyword routing with:
routing_mode: keyword
Tool text combines the tool name, description, and optional tags. Example:
create_invoice create invoice for customer billing payments
By default, MCPGen uses a deterministic local embedding fallback so demos and tests work without model downloads. To use the optional sentence-transformers backend, install the extra and set the backend:
pip install "openapi-mcpgen[semantic]"
export MCPGEN_EMBEDDING_BACKEND=sentence-transformers
PowerShell:
pip install "openapi-mcpgen[semantic]"
$env:MCPGEN_EMBEDDING_BACKEND = "sentence-transformers"
Compare routing modes by changing routing_mode in mcpgen.yaml and regenerating the server. Semantic mode ranks by vector similarity; keyword mode ranks by normalized token overlap and includes matched terms.
Safety Model
MCPGen is safe by default:
- Only low-risk
GETtools are exposed intools.json. - Medium-risk write tools are withheld unless future config explicitly enables them.
- High-risk
DELETEtools are always blocked. - Real execution is restricted to low-risk
GETtools. - Write execution is not implemented.
- Auth is not implemented.
Policy decisions return:
{
"allowed": false,
"status": "blocked",
"reason": "Medium-risk tool is not listed in enabled_tools.",
"risk_level": "medium",
"tool_name": "create_post"
}
Request Validation
v0.6.0 validates tool inputs before dry-run previews or safe GET execution. Validation uses the generated input_schema from OpenAPI parameters and JSON request bodies.
MCPGen currently checks:
- required fields
- basic JSON Schema types:
string,integer,number,boolean,array,object - enum values
Example validation error:
{
"valid": false,
"status": "validation_error",
"tool_name": "get_user_by_id",
"errors": [
{
"field": "id",
"reason": "required field is missing"
}
]
}
Validation runs in:
- FastAPI dry-run:
POST /tools/{tool_name}/dry-run - FastAPI safe execution:
POST /execute - MCP
tools/call
This validation is intentionally MVP-level. It catches common input mistakes before network calls, but it is not a full JSON Schema validator yet.
Mock Runtime
v0.7.0 adds mock execution so developers can test generated servers without a live API, database, credentials, or internet access.
Config:
mock:
enabled: true
mode: schema
seed: 123
list_size: 3
When mock mode is enabled, safe GET execution returns deterministic mock data instead of calling the upstream API. Policy, validation, rate limiting, audit logging, and metrics still apply.
Example mock response:
{
"tool": "get_user_by_id",
"status": "success",
"status_code": 200,
"data": {
"id": 1,
"name": "Users 1",
"mock": true
},
"mocked": true
}
List-style tools return arrays using mock.list_size.
Failure Injection
Failure injection lets developers simulate common upstream failures and observe how their server, agent, or LLM workflow responds.
Config:
failure_injection:
enabled: true
scenarios:
get_user_by_id: not_found
list_posts: timeout
Supported MVP scenarios:
timeoutnot_foundserver_errormalformed_json
Example simulated response:
{
"tool": "get_user_by_id",
"status": "error",
"status_code": 404,
"data": {
"error": "Simulated not found."
},
"simulated": true
}
Failure injection takes precedence over mock mode when both are enabled for the same tool.
Audit Logging
Audit logs are JSONL records written to:
logs/audit.log
Config:
audit_enabled: true
audit_log_path: logs/audit.log
routing_mode: semantic
Each event includes:
- timestamp
- tool name
- method
- path
- risk level
- mode
- status
- allowed
- reason
- source
- action
Actions include:
policy_evaluationdry_runexecution_startedexecution_successexecution_errorexecution_blocked
Audit is the event trail. It answers what happened, when, and why for each attempt.
Observability Metrics
v0.3.0 adds lightweight aggregate metrics for generated servers. Metrics are stored as JSON at:
logs/metrics.json
Config:
metrics_enabled: true
metrics_path: logs/metrics.json
Metrics track:
- total tool routes
- total policy evaluations
- dry-runs
- execution starts, successes, errors, and blocked attempts
- confirmation-required decisions
- per-tool route, policy, dry-run, execution, success, error, and blocked counts
- average execution latency in milliseconds per tool
- last updated timestamp
FastAPI mode exposes:
GET /metrics
POST /metrics/reset
Example response:
{
"total_tool_routes": 1,
"total_policy_evaluations": 2,
"total_executions": 1,
"total_execution_success": 1,
"total_execution_errors": 0,
"total_execution_blocked": 1,
"total_dry_runs": 1,
"total_confirmation_required": 0,
"per_tool": {
"get_user_by_id": {
"routed": 1,
"policy_allowed": 2,
"policy_blocked": 0,
"dry_runs": 1,
"executions": 1,
"successes": 1,
"errors": 0,
"blocked": 0,
"average_execution_latency_ms": 42.5
}
},
"last_updated": "2026-05-05T12:00:00+00:00"
}
Metrics are MVP-level and file-based. They are useful for local demos and development visibility, but they are not a replacement for production telemetry systems.
Rate Limiting
v0.5.0 adds lightweight in-memory rate limiting for generated servers.
Config:
rate_limit:
enabled: true
per_tool: 10
global: 100
window_seconds: 60
Defaults:
rate_limit:
enabled: false
per_tool: 10
global: 100
window_seconds: 60
mock:
enabled: false
mode: schema
seed: 123
list_size: 3
failure_injection:
enabled: false
scenarios: {}
FastAPI mode applies the global limit to operational requests:
POST /toolsPOST /tools/{tool_name}/dry-runPOST /execute
Per-tool limits apply to:
POST /tools/{tool_name}/dry-runPOST /execute- MCP
tools/call
Health and root endpoints are not rate limited.
When a request exceeds the limit, FastAPI returns 429 with a Retry-After header:
{
"status": "rate_limited",
"scope": "per_tool",
"retry_after": 30,
"reason": "rate limit exceeded"
}
Rate-limited events are recorded in both audit logs and aggregate metrics:
total_rate_limitedper_tool.<tool>.rate_limited
Limitations: rate limiting is in-memory only, resets when the generated server restarts, and is not distributed across processes or machines. Redis and distributed rate limiting are intentionally out of scope for this MVP.
Auth Passthrough
v0.4.0 adds safe upstream authentication support for generated servers. Secrets are never written into generated files, audit logs, metrics, or responses.
Default config:
auth:
mode: none
api_key_env: API_KEY
api_key_header: X-API-Key
mode: none
No auth headers are sent upstream.
auth:
mode: none
mode: bearer_passthrough
FastAPI mode can forward an incoming Authorization header to the upstream API only when it starts with Bearer .
auth:
mode: bearer_passthrough
Example request:
curl -X POST http://127.0.0.1:8001/execute \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "{\"tool_name\":\"get_user_by_id\",\"params\":{\"id\":1}}"
PowerShell:
$env:TOKEN = "your-token"
Invoke-RestMethod -Method Post http://127.0.0.1:8001/execute `
-ContentType "application/json" `
-Headers @{ Authorization = "Bearer $env:TOKEN" } `
-Body '{"tool_name":"get_user_by_id","params":{"id":1}}'
MCP stdio mode does not have HTTP headers. For bearer_passthrough, provide explicit auth metadata in tools/call arguments:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_user_by_id",
"arguments": {
"id": 1,
"auth": {
"authorization": "Bearer your-token"
}
}
}
}
The auth metadata is stripped before tool parameter handling and is not logged.
mode: api_key
API key mode reads a key from an environment variable and injects it into the configured upstream header.
auth:
mode: api_key
api_key_env: JSONPLACEHOLDER_API_KEY
api_key_header: X-API-Key
Bash:
export JSONPLACEHOLDER_API_KEY=your-api-key
PowerShell:
$env:JSONPLACEHOLDER_API_KEY = "your-api-key"
If the environment variable is missing, /execute returns a clear error and does not call the upstream API.
OAuth2 is not implemented yet. It is listed in the roadmap.
Configuration
Default config:
max_tools: 5
allowed_methods:
- GET
output_dir: generated_mcp_server
api_base_url: https://api.example.com
enabled_tools: []
execution_mode: dry-run
audit_enabled: true
audit_log_path: logs/audit.log
routing_mode: semantic
metrics_enabled: true
metrics_path: logs/metrics.json
auth:
mode: none
api_key_env: API_KEY
api_key_header: X-API-Key
rate_limit:
enabled: false
per_tool: 10
global: 100
window_seconds: 60
For the JSONPlaceholder demo, set:
api_base_url: https://jsonplaceholder.typicode.com
Doctor Diagnostics
mcpgen doctor runs read-only checks against an OpenAPI spec and optional config file:
mcpgen doctor --from examples/jsonplaceholder.openapi.yaml --config mcpgen.yaml
It checks:
- config loading and validation
- OpenAPI parseability
- execution mode
- routing mode
- API base URL readiness
- auth mode
- rate limit settings
- metrics and audit settings
- generated tool counts
- exposed vs withheld tools
- potential tool overload against
max_tools
Example output:
MCPGen doctor: warn
[PASS] config: Config loaded successfully.
[PASS] openapi: Parsed 6 endpoint(s).
[WARN] api_base_url: api_base_url is using the default placeholder.
[PASS] safety: 4 low-risk tool(s) will be exposed.
doctor exits with code 1 if a failing check is found, which makes it useful in CI.
Current Limitations
- This is a production-oriented MVP, not a production-ready framework.
- Auth support is limited to bearer passthrough and API key header injection.
- Request validation is MVP-level and not full JSON Schema validation.
- Mock responses are simple deterministic fixtures, not full response-schema generation.
- Failure injection is configured per tool and supports only common MVP scenarios.
- No OAuth2 flow yet.
- No write execution.
- No confirmation workflow UI.
- No vector database or embedding cache optimization.
- Rate limiting is in-memory only and not distributed.
- No database-backed audit sink.
- No production telemetry backend.
- MCP mode uses a minimal stdio scaffold if the official Python MCP SDK is unavailable.
Roadmap
- Official MCP SDK integration
- Auth and secret management
- OAuth2 support
- Confirmation workflow for enabled medium-risk tools
- Rate limiting
- Request/response validation
- Full JSON Schema validation
- Response-schema-aware mock generation
- Failure scenario probabilities and per-request overrides
- Better OpenAPI schema support
- Pluggable audit sinks
- Better semantic routing models and embedding cache optimization
- Deployment templates
Publishing
MCPGen publishes through GitHub Actions using PyPI Trusted Publishing. No PyPI API token should be committed to the repository.
Workflow:
.github/workflows/publish.yml
Recommended flow:
- Configure Trusted Publishing on TestPyPI for this repository and the
Publish Python Packageworkflow. - Run the workflow manually with
repository = testpypi. - Install and verify from TestPyPI.
- Configure Trusted Publishing on PyPI.
- Publish a GitHub Release or run the workflow manually with
repository = pypi.
Install from TestPyPI:
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple openapi-mcpgen
Install from PyPI:
pip install openapi-mcpgen
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openapi_mcpgen-0.7.0.tar.gz.
File metadata
- Download URL: openapi_mcpgen-0.7.0.tar.gz
- Upload date:
- Size: 44.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd03e7f9b0116f7d2fdbfd7f190eb527bde37037d02d126b3b04aa08650707ad
|
|
| MD5 |
48a3311a35dc0aedf15249512f62d3a7
|
|
| BLAKE2b-256 |
45a5809f8d4067d16251335a74165150a7265bd7edd4de1ec1cde3bc0215934d
|
Provenance
The following attestation bundles were made for openapi_mcpgen-0.7.0.tar.gz:
Publisher:
publish.yml on breezykalama/mcpgen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openapi_mcpgen-0.7.0.tar.gz -
Subject digest:
fd03e7f9b0116f7d2fdbfd7f190eb527bde37037d02d126b3b04aa08650707ad - Sigstore transparency entry: 1451544275
- Sigstore integration time:
-
Permalink:
breezykalama/mcpgen@1eae1df362bc201c36afe8a73c0e56b567cf2ea7 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/breezykalama
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1eae1df362bc201c36afe8a73c0e56b567cf2ea7 -
Trigger Event:
release
-
Statement type:
File details
Details for the file openapi_mcpgen-0.7.0-py3-none-any.whl.
File metadata
- Download URL: openapi_mcpgen-0.7.0-py3-none-any.whl
- Upload date:
- Size: 36.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd799ee57cb478e4bb5f0bcd844c7335a135dd16336ef160c173e7ec15f09a80
|
|
| MD5 |
1dfc1c2143889b5ed3a56e86c507240c
|
|
| BLAKE2b-256 |
561754550af093e26bcb50c2788db599e9c8fb87a9f55c4c89bc723e2eb94cc5
|
Provenance
The following attestation bundles were made for openapi_mcpgen-0.7.0-py3-none-any.whl:
Publisher:
publish.yml on breezykalama/mcpgen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openapi_mcpgen-0.7.0-py3-none-any.whl -
Subject digest:
bd799ee57cb478e4bb5f0bcd844c7335a135dd16336ef160c173e7ec15f09a80 - Sigstore transparency entry: 1451544498
- Sigstore integration time:
-
Permalink:
breezykalama/mcpgen@1eae1df362bc201c36afe8a73c0e56b567cf2ea7 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/breezykalama
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1eae1df362bc201c36afe8a73c0e56b567cf2ea7 -
Trigger Event:
release
-
Statement type: