A reusable MCP server that turns any OpenAPI or Swagger spec into:
- discovery tools for schema inspection
- execution tools for calling the underlying API
- HTTP and STDIO transports for OpenAI, Claude, Osirus, and other MCP clients
This repo is intentionally generic. CMS is one target, but the same server can front Zendesk, WordPress, internal APIs, and other OpenAPI-backed systems.
Point the server at an OpenAPI spec and it will expose:
- schema tools like
list-endpoints,get-endpoint, andsearch-schema - generated execution tools like
search-pages,publish-page, orcreate-ticket - a generic
execute-requesttool for custom calls
npm install
export OPENAPI_URL="https://petstore.swagger.io/v2/swagger.json"
export API_BASE_URL="https://petstore.swagger.io/v2"
npm startThis repo's Docker and CI flows expect both package.json and package-lock.json to be present in the build context.
- Commit
package-lock.jsonwhenever dependencies change. - Do not ignore the lockfile in Git.
- If Docker reports
COPY package.json package-lock.json ./: "/package-lock.json": not found, the usual cause is that the lockfile exists only in a local working tree and was not committed into the submodule revision being built.
Server endpoints:
GET /infoGET /healthPOST /mcpGET /sseGET /mcp/api/v1/u/:token/ssePOST /tools/listPOST /tools/callPOST /call-tool
Example: CMS
export OPENAPI_URL="https://your-cms.example.com/public/api/system/swagger.json"
export API_BASE_URL="https://your-cms.example.com/public/api"
export OPENAPI_AUTH_TOKEN="cms-api-bearer-token"
npm startExample: Zendesk
export OPENAPI_URL="https://your-proxy.example.com/zendesk/openapi.json"
export API_BASE_URL="https://your-subdomain.zendesk.com/api/v2"
export OPENAPI_AUTH_TOKEN="zendesk-api-token-or-proxy-token"
npm startExample: WordPress
export OPENAPI_URL="https://your-wordpress.example.com/wp-json/openapi.json"
export API_BASE_URL="https://your-wordpress.example.com/wp-json"
export OPENAPI_AUTH_TOKEN="wp-api-token"
npm startIf you want Osirus, OpenAI tools, Claude, or another client to authenticate to this MCP server, set one or more MCP auth tokens:
export MCP_AUTH_TOKEN="shared-mcp-token"Or:
export MCP_AUTH_TOKENS="token-a,token-b,token-c"Then clients call the MCP server with:
Authorization: Bearer shared-mcp-tokenClaude-style SSE URLs also work:
https://your-mcp.example.com/mcp/api/v1/u/shared-mcp-token/sse
curl -X POST http://localhost:8000/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer shared-mcp-token" \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"tools/list",
"params":{}
}'curl -X POST http://localhost:8000/tools/call \
-H "Content-Type: application/json" \
-H "Authorization: Bearer shared-mcp-token" \
-d '{
"name":"list-endpoints",
"arguments":{}
}'{
"mcpServers": {
"openapi": {
"command": "node",
"args": ["/path/to/openapi-mcp-server/src/client.js"],
"env": {
"OPENAPI_URL": "https://your-api.example.com/openapi.json",
"API_BASE_URL": "https://your-api.example.com",
"OPENAPI_AUTH_TOKEN": "upstream-api-token"
}
}
}
}There are two separate token concerns:
This protects access to the MCP server itself.
Use:
MCP_AUTH_TOKENMCP_AUTH_TOKENS- optional
REQUIRE_AUTH=true|false
If any MCP auth token is configured, auth is required by default.
This is the bearer token the generated tools use when they call the underlying API.
Use:
OPENAPI_AUTH_TOKEN- or
API_BEARER_TOKEN
Schema loading from remote URLs also uses the upstream bearer token.
| Variable | Purpose |
|---|---|
OPENAPI_URL |
URL or local path to the OpenAPI/Swagger spec |
API_BASE_URL |
Base URL for execution tools |
OPENAPI_AUTH_TOKEN |
Bearer token for the upstream API |
API_BEARER_TOKEN |
Alternate name for upstream bearer token |
MCP_AUTH_TOKEN |
Single bearer token for protecting the MCP server |
MCP_AUTH_TOKENS |
Comma-separated list of valid MCP bearer tokens |
REQUIRE_AUTH |
Force on/off MCP auth requirement |
MCP_ALLOWED_TOOLS |
Comma-separated allowlist of exposed tool names |
PORT |
HTTP port |
HOST |
Bind host |
PUBLIC_BASE_URL |
Public URL advertised in info output |
SERVER_NAME |
Optional display name |
SERVER_VERSION |
Optional display version |
SERVER_DESCRIPTION |
Optional display description |
CORS_ORIGIN |
Optional CORS origin override |
list-endpointsget-endpointget-request-bodyget-response-schemaget-path-parameterslist-componentsget-componentlist-security-schemessearch-schema
Generated from paths and methods in the OpenAPI spec, for example:
search-pagescreate-pageread-pageupdate-pagepublish-page
And always:
execute-request
- The MCP server auth token and upstream API token are intentionally separate.
- Reserved outbound headers like
Authorization,Host, andContent-Lengthare stripped before proxying and rebuilt safely. - Tool results are normalized to MCP
contentresponses. - Tool allowlisting is supported through
MCP_ALLOWED_TOOLS. - Do not expose admin-grade upstream tokens unless the API behind them is already scoped appropriately.
npm test
npm run test:mcp- Verify
package-lock.jsonis tracked before tagging a release. - Check that example tokens in docs and tests are placeholders, not live credentials.
- Build the image from the repo root or from the submodule root with the lockfile included in the Docker context.
Not to run this repo by itself.
You only need env vars to run the server directly.
But yes, if you want this to become a real Solodev product feature, we should add a CMS provider so admins can manage:
- MCP server URL
- MCP auth token
- upstream OpenAPI URL
- upstream API bearer token
- allowed tools
- policy prompts / guardrails
That provider should be generic, not CMS-only, so the same CMS UI can register:
- Solodev MCP
- Zendesk MCP
- WordPress MCP
- other OpenAPI MCP connections
src/
server.js
client.js
config/
middleware/
routes/
services/
tools/
utils/
test/
samples/
MIT