mcpify-openapi 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Furkan GΓΆktan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,208 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcpify-openapi
3
+ Version: 1.0.0
4
+ Summary: Turn any OpenAPI REST API into an MCP server so AI agents can call it β€” zero dependencies
5
+ License: MIT
6
+ Keywords: mcp,model-context-protocol,openapi,swagger,ai-agents,claude,rest,api,cli
7
+ Classifier: Environment :: Console
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Software Development :: Libraries
12
+ Classifier: Topic :: Utilities
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Provides-Extra: yaml
17
+ Requires-Dist: pyyaml>=6.0.3; extra == "yaml"
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=9.1.1; extra == "dev"
20
+ Requires-Dist: pytest-cov>=7.1.0; extra == "dev"
21
+ Requires-Dist: ruff>=0.16.4; extra == "dev"
22
+ Requires-Dist: mypy>=2.3.1; extra == "dev"
23
+ Requires-Dist: types-PyYAML; extra == "dev"
24
+ Requires-Dist: pyyaml>=6.0.3; extra == "dev"
25
+ Dynamic: license-file
26
+
27
+ # πŸ”Œ mcpify
28
+
29
+ ![Python](https://img.shields.io/badge/python-3.9%2B-blue)
30
+ ![License](https://img.shields.io/badge/license-MIT-green)
31
+
32
+ 🌐 English | [Türkçe](README.tr.md)
33
+
34
+ <p align="center">
35
+ <img src="docs/demo.gif" alt="mcpify in action β€” listing and serving OpenAPI endpoints as MCP tools" width="720">
36
+ </p>
37
+ ![Tests](https://img.shields.io/badge/tests-53%20passed-brightgreen)
38
+ [![CI](https://github.com/furkan708/mcpify/actions/workflows/ci.yml/badge.svg)](https://github.com/furkan708/mcpify/actions/workflows/ci.yml) ![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-261230.svg) ![Types: mypy](https://img.shields.io/badge/types-mypy-blue)
39
+ ![Dependencies](https://img.shields.io/badge/dependencies-zero-success)
40
+
41
+ **Turn any OpenAPI REST API into an [MCP](https://modelcontextprotocol.io) server** β€” so Claude Code, Cursor, and every other MCP client can call your API directly. One command. Zero dependencies.
42
+
43
+ ![mcpify terminal demo](docs/screenshot.png)
44
+
45
+ Your company has a REST API. Your AI agent needs to call it. Until now that
46
+ meant hand-writing a custom MCP server for every API. With mcpify:
47
+
48
+ ```bash
49
+ mcpify serve https://your-company.com/openapi.json
50
+ ```
51
+
52
+ That's it β€” every endpoint just became a tool your AI agent can discover, understand, and call.
53
+
54
+ πŸ“– **Deep docs:** [Usage guide](docs/USAGE.md) β€” auth patterns, scoping, Docker, troubleshooting Β· [Architecture](docs/ARCHITECTURE.md) Β· [Contributing](CONTRIBUTING.md) Β· [Changelog](CHANGELOG.md) Β· [Security](SECURITY.md)
55
+
56
+ ## ✨ Why you'll like it
57
+
58
+ - ⚑ **60 seconds to working** β€” point it at any OpenAPI 3.x spec (file or URL)
59
+ - πŸ” **Credentials never touch the spec or the model** β€” pulled from your
60
+ environment at call time (`--auth-env`), sent as `Authorization: Bearer`,
61
+ a custom header, or a query parameter
62
+ - 🧰 **Every operation becomes a first-class MCP tool** β€” input schemas are
63
+ generated from `parameters` + `requestBody`, internal `$ref`s are resolved
64
+ - 🎚️ **Scope it down** β€” `--read-only` (GET only), `--tag payments`,
65
+ `--include /v1/orders`, `--exclude /admin`, plus a policy layer for real-world
66
+ APIs: `--deny REGEX` hides mutating GETs, `--allow REGEX` re-includes
67
+ read-style POST endpoints. Deny always wins.
68
+ - 🩺 **`mcpify doctor`** β€” tells you if your spec is agent-friendly before you ship
69
+ - πŸͺΆ **Zero dependencies** β€” one pure-Python file tree; YAML specs need an
70
+ optional `pip install 'mcpify[yaml]'`
71
+ - πŸ§ͺ **53 tests** including a full end-to-end suite against a real local HTTP API
72
+
73
+ ## πŸš€ Quick start
74
+
75
+ ```bash
76
+ git clone https://github.com/furkan708/mcpify.git
77
+ cd mcpify && pip install .
78
+
79
+ # 1. preview the tools that will be generated
80
+ mcpify list examples/petstore.json
81
+
82
+ # 2. validate the spec is agent-friendly
83
+ mcpify doctor examples/petstore.json
84
+
85
+ # 3. serve it over MCP
86
+ mcpify serve examples/petstore.json --base-url https://petstore.example.com/v1
87
+ ```
88
+
89
+ ### With authentication
90
+
91
+ ```bash
92
+ # Bearer token read from the environment (never hardcoded)
93
+ export PETSTORE_KEY="sk-..."
94
+ mcpify serve petstore.json \
95
+ --base-url https://petstore.example.com/v1 \
96
+ --auth-env PETSTORE_KEY \
97
+ --auth-style bearer \
98
+ --read-only
99
+ ```
100
+
101
+ | Flag | Meaning |
102
+ | ---- | ------- |
103
+ | `--auth-env VAR` | environment variable holding the credential |
104
+ | `--auth-style bearer\|header\|query` | how it is sent |
105
+ | `--auth-name NAME` | header / query name for non-bearer styles (e.g. `X-API-Key`) |
106
+
107
+ ## πŸ€– Plug it into your agent
108
+
109
+ **Claude Code:**
110
+
111
+ ```bash
112
+ claude mcp add my-api -- mcpify serve openapi.json --read-only
113
+ ```
114
+
115
+ **Claude Desktop / Cursor / any MCP client** (`claude_desktop_config.json`):
116
+
117
+ ```json
118
+ {
119
+ "mcpServers": {
120
+ "petstore": {
121
+ "command": "mcpify",
122
+ "args": ["serve", "~/specs/petstore.json", "--auth-env", "PETSTORE_KEY"]
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ Now ask your agent: *"list the pets, then create one named Milo"* β€” it discovers `list_pets` and `create_pet`, fills the arguments, and performs real HTTP calls.
129
+
130
+ ## πŸ” How operations become tools
131
+
132
+ | OpenAPI | mcpify |
133
+ | ------- | ------ |
134
+ | `operationId` | tool name (sanitized; falls back to `method_path`) |
135
+ | `summary` / `description` | tool description the agent reads |
136
+ | `parameters` (path/query/header) | individual typed arguments with enums |
137
+ | `requestBody` (JSON) | a `body` object argument |
138
+ | `$ref` pointers | resolved inline (components β†’ real schemas) |
139
+ | `servers[0].url` | default base URL (override: `--base-url`) |
140
+
141
+ The agent only ever sees the tool list and your API's JSON responses β€”
142
+ mcpify adds no middleware, caches nothing, and sends credentials nowhere
143
+ except your API.
144
+
145
+ ## 🩺 Doctor
146
+
147
+ ```bash
148
+ $ mcpify doctor my-api.json
149
+ openapi: 3.0.3
150
+ title: Acme API
151
+ paths: 23
152
+ tools: 41 operations
153
+ servers: https://api.acme.com
154
+ warning: 12/41 operations have no operationId (names fall back to method_path)
155
+ warning: 30/41 operations have no summary (agents see no description)
156
+ ```
157
+
158
+ ## πŸ“– CLI reference
159
+
160
+ ```
161
+ mcpify list <spec> [--tag T] [--include P] [--exclude P] [--read-only] [--json]
162
+ mcpify serve <spec> [--base-url URL] [--name N] [--auth-env VAR]
163
+ [--auth-style bearer|header|query] [--auth-name NAME]
164
+ [--timeout S] [--read-only] [--tag T] [--include P] [--exclude P]
165
+ mcpify doctor <spec>
166
+ ```
167
+
168
+ ### Notes & limitations
169
+
170
+ - JSON specs work out of the box; YAML specs need `pip install 'mcpify[yaml]'`
171
+ - Only local `$ref` pointers are resolved (bundle external docs first β€” most tools do anyway)
172
+ - Request bodies are exposed as a single `body` object argument β€” predictable over clever
173
+ - Spec versions: OpenAPI 3.x and Swagger 2.x roots are accepted; 3.x is the happy path
174
+
175
+ ## πŸ§ͺ Tests
176
+
177
+ ```bash
178
+ pip install pytest pyyaml
179
+ pytest -v
180
+ ```
181
+
182
+ The e2e suite boots a real local HTTP API and drives the full MCP protocol
183
+ over stdio β€” initialize β†’ tools/list β†’ tools/call β€” and asserts on the HTTP
184
+ requests that hit the wire.
185
+
186
+ ## πŸ—‚οΈ Project Structure
187
+
188
+ ```
189
+ mcpify/
190
+ β”œβ”€β”€ mcpify/
191
+ β”‚ β”œβ”€β”€ spec.py # OpenAPI loading, $ref resolution, operation walking
192
+ β”‚ β”œβ”€β”€ tools.py # operation -> MCP tool, argument -> HTTP request
193
+ β”‚ β”œβ”€β”€ http_client.py # execution (urllib, HTTP errors become tool results)
194
+ β”‚ β”œβ”€β”€ api_server.py # MCP stdio server (JSON-RPC 2.0)
195
+ β”‚ └── cli.py # list / serve / doctor
196
+ β”œβ”€β”€ examples/petstore.json
197
+ └── tests/
198
+ ```
199
+
200
+ ## πŸ—ΊοΈ Roadmap
201
+
202
+ - [ ] `--output-server FILE` β€” generate a standalone, shareable server script
203
+ - [ ] Per-operation rate limiting
204
+ - [ ] OAuth2 client-credentials flow
205
+
206
+ ## πŸ“„ License
207
+
208
+ MIT β€” see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,182 @@
1
+ # πŸ”Œ mcpify
2
+
3
+ ![Python](https://img.shields.io/badge/python-3.9%2B-blue)
4
+ ![License](https://img.shields.io/badge/license-MIT-green)
5
+
6
+ 🌐 English | [Türkçe](README.tr.md)
7
+
8
+ <p align="center">
9
+ <img src="docs/demo.gif" alt="mcpify in action β€” listing and serving OpenAPI endpoints as MCP tools" width="720">
10
+ </p>
11
+ ![Tests](https://img.shields.io/badge/tests-53%20passed-brightgreen)
12
+ [![CI](https://github.com/furkan708/mcpify/actions/workflows/ci.yml/badge.svg)](https://github.com/furkan708/mcpify/actions/workflows/ci.yml) ![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-261230.svg) ![Types: mypy](https://img.shields.io/badge/types-mypy-blue)
13
+ ![Dependencies](https://img.shields.io/badge/dependencies-zero-success)
14
+
15
+ **Turn any OpenAPI REST API into an [MCP](https://modelcontextprotocol.io) server** β€” so Claude Code, Cursor, and every other MCP client can call your API directly. One command. Zero dependencies.
16
+
17
+ ![mcpify terminal demo](docs/screenshot.png)
18
+
19
+ Your company has a REST API. Your AI agent needs to call it. Until now that
20
+ meant hand-writing a custom MCP server for every API. With mcpify:
21
+
22
+ ```bash
23
+ mcpify serve https://your-company.com/openapi.json
24
+ ```
25
+
26
+ That's it β€” every endpoint just became a tool your AI agent can discover, understand, and call.
27
+
28
+ πŸ“– **Deep docs:** [Usage guide](docs/USAGE.md) β€” auth patterns, scoping, Docker, troubleshooting Β· [Architecture](docs/ARCHITECTURE.md) Β· [Contributing](CONTRIBUTING.md) Β· [Changelog](CHANGELOG.md) Β· [Security](SECURITY.md)
29
+
30
+ ## ✨ Why you'll like it
31
+
32
+ - ⚑ **60 seconds to working** β€” point it at any OpenAPI 3.x spec (file or URL)
33
+ - πŸ” **Credentials never touch the spec or the model** β€” pulled from your
34
+ environment at call time (`--auth-env`), sent as `Authorization: Bearer`,
35
+ a custom header, or a query parameter
36
+ - 🧰 **Every operation becomes a first-class MCP tool** β€” input schemas are
37
+ generated from `parameters` + `requestBody`, internal `$ref`s are resolved
38
+ - 🎚️ **Scope it down** β€” `--read-only` (GET only), `--tag payments`,
39
+ `--include /v1/orders`, `--exclude /admin`, plus a policy layer for real-world
40
+ APIs: `--deny REGEX` hides mutating GETs, `--allow REGEX` re-includes
41
+ read-style POST endpoints. Deny always wins.
42
+ - 🩺 **`mcpify doctor`** β€” tells you if your spec is agent-friendly before you ship
43
+ - πŸͺΆ **Zero dependencies** β€” one pure-Python file tree; YAML specs need an
44
+ optional `pip install 'mcpify[yaml]'`
45
+ - πŸ§ͺ **53 tests** including a full end-to-end suite against a real local HTTP API
46
+
47
+ ## πŸš€ Quick start
48
+
49
+ ```bash
50
+ git clone https://github.com/furkan708/mcpify.git
51
+ cd mcpify && pip install .
52
+
53
+ # 1. preview the tools that will be generated
54
+ mcpify list examples/petstore.json
55
+
56
+ # 2. validate the spec is agent-friendly
57
+ mcpify doctor examples/petstore.json
58
+
59
+ # 3. serve it over MCP
60
+ mcpify serve examples/petstore.json --base-url https://petstore.example.com/v1
61
+ ```
62
+
63
+ ### With authentication
64
+
65
+ ```bash
66
+ # Bearer token read from the environment (never hardcoded)
67
+ export PETSTORE_KEY="sk-..."
68
+ mcpify serve petstore.json \
69
+ --base-url https://petstore.example.com/v1 \
70
+ --auth-env PETSTORE_KEY \
71
+ --auth-style bearer \
72
+ --read-only
73
+ ```
74
+
75
+ | Flag | Meaning |
76
+ | ---- | ------- |
77
+ | `--auth-env VAR` | environment variable holding the credential |
78
+ | `--auth-style bearer\|header\|query` | how it is sent |
79
+ | `--auth-name NAME` | header / query name for non-bearer styles (e.g. `X-API-Key`) |
80
+
81
+ ## πŸ€– Plug it into your agent
82
+
83
+ **Claude Code:**
84
+
85
+ ```bash
86
+ claude mcp add my-api -- mcpify serve openapi.json --read-only
87
+ ```
88
+
89
+ **Claude Desktop / Cursor / any MCP client** (`claude_desktop_config.json`):
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": {
94
+ "petstore": {
95
+ "command": "mcpify",
96
+ "args": ["serve", "~/specs/petstore.json", "--auth-env", "PETSTORE_KEY"]
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ Now ask your agent: *"list the pets, then create one named Milo"* β€” it discovers `list_pets` and `create_pet`, fills the arguments, and performs real HTTP calls.
103
+
104
+ ## πŸ” How operations become tools
105
+
106
+ | OpenAPI | mcpify |
107
+ | ------- | ------ |
108
+ | `operationId` | tool name (sanitized; falls back to `method_path`) |
109
+ | `summary` / `description` | tool description the agent reads |
110
+ | `parameters` (path/query/header) | individual typed arguments with enums |
111
+ | `requestBody` (JSON) | a `body` object argument |
112
+ | `$ref` pointers | resolved inline (components β†’ real schemas) |
113
+ | `servers[0].url` | default base URL (override: `--base-url`) |
114
+
115
+ The agent only ever sees the tool list and your API's JSON responses β€”
116
+ mcpify adds no middleware, caches nothing, and sends credentials nowhere
117
+ except your API.
118
+
119
+ ## 🩺 Doctor
120
+
121
+ ```bash
122
+ $ mcpify doctor my-api.json
123
+ openapi: 3.0.3
124
+ title: Acme API
125
+ paths: 23
126
+ tools: 41 operations
127
+ servers: https://api.acme.com
128
+ warning: 12/41 operations have no operationId (names fall back to method_path)
129
+ warning: 30/41 operations have no summary (agents see no description)
130
+ ```
131
+
132
+ ## πŸ“– CLI reference
133
+
134
+ ```
135
+ mcpify list <spec> [--tag T] [--include P] [--exclude P] [--read-only] [--json]
136
+ mcpify serve <spec> [--base-url URL] [--name N] [--auth-env VAR]
137
+ [--auth-style bearer|header|query] [--auth-name NAME]
138
+ [--timeout S] [--read-only] [--tag T] [--include P] [--exclude P]
139
+ mcpify doctor <spec>
140
+ ```
141
+
142
+ ### Notes & limitations
143
+
144
+ - JSON specs work out of the box; YAML specs need `pip install 'mcpify[yaml]'`
145
+ - Only local `$ref` pointers are resolved (bundle external docs first β€” most tools do anyway)
146
+ - Request bodies are exposed as a single `body` object argument β€” predictable over clever
147
+ - Spec versions: OpenAPI 3.x and Swagger 2.x roots are accepted; 3.x is the happy path
148
+
149
+ ## πŸ§ͺ Tests
150
+
151
+ ```bash
152
+ pip install pytest pyyaml
153
+ pytest -v
154
+ ```
155
+
156
+ The e2e suite boots a real local HTTP API and drives the full MCP protocol
157
+ over stdio β€” initialize β†’ tools/list β†’ tools/call β€” and asserts on the HTTP
158
+ requests that hit the wire.
159
+
160
+ ## πŸ—‚οΈ Project Structure
161
+
162
+ ```
163
+ mcpify/
164
+ β”œβ”€β”€ mcpify/
165
+ β”‚ β”œβ”€β”€ spec.py # OpenAPI loading, $ref resolution, operation walking
166
+ β”‚ β”œβ”€β”€ tools.py # operation -> MCP tool, argument -> HTTP request
167
+ β”‚ β”œβ”€β”€ http_client.py # execution (urllib, HTTP errors become tool results)
168
+ β”‚ β”œβ”€β”€ api_server.py # MCP stdio server (JSON-RPC 2.0)
169
+ β”‚ └── cli.py # list / serve / doctor
170
+ β”œβ”€β”€ examples/petstore.json
171
+ └── tests/
172
+ ```
173
+
174
+ ## πŸ—ΊοΈ Roadmap
175
+
176
+ - [ ] `--output-server FILE` β€” generate a standalone, shareable server script
177
+ - [ ] Per-operation rate limiting
178
+ - [ ] OAuth2 client-credentials flow
179
+
180
+ ## πŸ“„ License
181
+
182
+ MIT β€” see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,17 @@
1
+ """mcpify β€” turn any OpenAPI REST API into an MCP server for AI agents."""
2
+
3
+ from .spec import SpecError, load_spec, resolve_ref, resolve_schema
4
+ from .tools import AuthConfig, RequestError, build_request, spec_to_tools
5
+
6
+ __version__ = "1.0.0"
7
+ __all__ = [
8
+ "SpecError",
9
+ "load_spec",
10
+ "resolve_ref",
11
+ "resolve_schema",
12
+ "AuthConfig",
13
+ "RequestError",
14
+ "build_request",
15
+ "spec_to_tools",
16
+ "__version__",
17
+ ]
@@ -0,0 +1,139 @@
1
+ """MCP (Model Context Protocol) stdio server that exposes an OpenAPI spec.
2
+
3
+ Specks newline-delimited JSON-RPC 2.0 over stdio, as used by MCP stdio
4
+ transports. Every OpenAPI operation becomes an MCP tool that performs a
5
+ real HTTP call against the configured base URL.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import sys
12
+ from typing import Any
13
+
14
+ from .http_client import execute, format_result
15
+ from .tools import AuthConfig, RequestError, build_request, spec_to_tools
16
+
17
+ PROTOCOL_VERSION = "2025-06-18"
18
+ SERVER_NAME = "mcpify"
19
+ SERVER_VERSION = "1.0.0"
20
+
21
+
22
+ class ApiServer:
23
+ """MCP handler backed by one OpenAPI specification."""
24
+
25
+ def __init__(
26
+ self,
27
+ spec: dict,
28
+ base_url: str,
29
+ server_name: str = "mcpify",
30
+ auth: AuthConfig | None = None,
31
+ timeout: float = 30.0,
32
+ ) -> None:
33
+ self.spec = spec
34
+ self.base_url = base_url
35
+ self.server_name = server_name
36
+ self.auth = auth
37
+ self.timeout = timeout
38
+ self.tools = spec_to_tools(spec)
39
+ self.by_name = {tool["name"]: tool for tool in self.tools}
40
+
41
+ # -- public API used by the CLI --------------------------------------
42
+ @property
43
+ def tool_count(self) -> int:
44
+ return len(self.tools)
45
+
46
+ def public_tools(self) -> list[dict]:
47
+ return [
48
+ {k: v for k, v in tool.items() if not k.startswith("_")}
49
+ for tool in self.tools
50
+ ]
51
+
52
+ def call_tool(self, name: str, arguments: dict) -> tuple[str, bool]:
53
+ tool = self.by_name.get(name)
54
+ if tool is None:
55
+ raise KeyError(name)
56
+ request = build_request(self.base_url, tool["_meta"], arguments, self.auth)
57
+ if self.auth is not None:
58
+ request["url"] = self.auth.apply_query(request["url"])
59
+ result = execute(request, timeout=self.timeout)
60
+ return format_result(result)
61
+
62
+ # -- MCP plumbing -----------------------------------------------------
63
+ def _result(self, request_id: int | str | None, payload: dict) -> dict:
64
+ return {"jsonrpc": "2.0", "id": request_id, "result": payload}
65
+
66
+ def _error(self, request_id: int | str | None, code: int, message: str) -> dict:
67
+ return {"jsonrpc": "2.0", "id": request_id, "error": {"code": code, "message": message}}
68
+
69
+ def _text(self, text: str, is_error: bool = False) -> dict:
70
+ payload: dict = {"content": [{"type": "text", "text": text}]}
71
+ if is_error:
72
+ payload["isError"] = True
73
+ return payload
74
+
75
+ def handle_message(self, message: dict) -> dict | None:
76
+ method = message.get("method", "")
77
+ request_id = message.get("id")
78
+ params = message.get("params") or {}
79
+
80
+ if method == "initialize":
81
+ requested = params.get("protocolVersion", PROTOCOL_VERSION)
82
+ return self._result(
83
+ request_id,
84
+ {
85
+ "protocolVersion": requested,
86
+ "capabilities": {"tools": {}},
87
+ "serverInfo": {"name": self.server_name, "version": SERVER_VERSION},
88
+ },
89
+ )
90
+ if method.startswith("notifications/"):
91
+ return None
92
+ if method == "ping":
93
+ return self._result(request_id, {})
94
+ if method == "tools/list":
95
+ return self._result(request_id, {"tools": self.public_tools()})
96
+ if method == "tools/call":
97
+ name = params.get("name", "")
98
+ arguments = params.get("arguments") or {}
99
+ try:
100
+ text, is_error = self.call_tool(name, arguments)
101
+ except KeyError:
102
+ return self._error(request_id, -32601, f"unknown tool: {name}")
103
+ except RequestError as err:
104
+ return self._result(request_id, self._text(str(err), is_error=True))
105
+ return self._result(request_id, self._text(text, is_error=is_error))
106
+ return self._error(request_id, -32601, f"method not found: {method}")
107
+
108
+ def serve(self, stdin: Any = None, stdout: Any = None) -> None:
109
+ input_stream = stdin if stdin is not None else sys.stdin
110
+ output_stream = stdout if stdout is not None else sys.stdout
111
+ for line in input_stream:
112
+ line = line.strip()
113
+ if not line:
114
+ continue
115
+ decoded: Any
116
+ response: dict | None
117
+ try:
118
+ decoded = json.loads(line)
119
+ except json.JSONDecodeError:
120
+ response = self._error(None, -32700, "parse error")
121
+ else:
122
+ response = self.handle_message(decoded)
123
+ if response is not None:
124
+ output_stream.write(json.dumps(response, ensure_ascii=False) + "\n")
125
+ output_stream.flush()
126
+
127
+
128
+ def serve(
129
+ spec_path: str,
130
+ base_url: str,
131
+ name: str = "mcpify",
132
+ auth: AuthConfig | None = None,
133
+ timeout: float = 30.0,
134
+ ) -> None:
135
+ """Load the spec and block on the stdio loop (the `mcpify serve` entry)."""
136
+ from .spec import load_spec
137
+
138
+ spec = load_spec(spec_path)
139
+ ApiServer(spec, base_url, server_name=name, auth=auth, timeout=timeout).serve()