mcp-openapix 0.1.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.
Files changed (39) hide show
  1. mcp_openapix-0.1.0/.gitattributes +4 -0
  2. mcp_openapix-0.1.0/.github/workflows/ci.yml +84 -0
  3. mcp_openapix-0.1.0/.gitignore +41 -0
  4. mcp_openapix-0.1.0/.pre-commit-config.yaml +19 -0
  5. mcp_openapix-0.1.0/.python-version +1 -0
  6. mcp_openapix-0.1.0/AGENTS.md +138 -0
  7. mcp_openapix-0.1.0/CLAUDE.md +1 -0
  8. mcp_openapix-0.1.0/LICENSE +21 -0
  9. mcp_openapix-0.1.0/PKG-INFO +284 -0
  10. mcp_openapix-0.1.0/README.md +246 -0
  11. mcp_openapix-0.1.0/config.example.json +86 -0
  12. mcp_openapix-0.1.0/docs/token-protocol.md +208 -0
  13. mcp_openapix-0.1.0/pyproject.toml +95 -0
  14. mcp_openapix-0.1.0/server.json +23 -0
  15. mcp_openapix-0.1.0/src/mcp_openapix/__init__.py +0 -0
  16. mcp_openapix-0.1.0/src/mcp_openapix/api_client.py +228 -0
  17. mcp_openapix-0.1.0/src/mcp_openapix/auth.py +568 -0
  18. mcp_openapix-0.1.0/src/mcp_openapix/cache.py +149 -0
  19. mcp_openapix-0.1.0/src/mcp_openapix/config.py +496 -0
  20. mcp_openapix-0.1.0/src/mcp_openapix/refresh.py +305 -0
  21. mcp_openapix-0.1.0/src/mcp_openapix/responses.py +58 -0
  22. mcp_openapix-0.1.0/src/mcp_openapix/server.py +540 -0
  23. mcp_openapix-0.1.0/src/mcp_openapix/spec_loader.py +249 -0
  24. mcp_openapix-0.1.0/src/mcp_openapix/tools/__init__.py +1 -0
  25. mcp_openapix-0.1.0/src/mcp_openapix/tools/call_endpoint.py +103 -0
  26. mcp_openapix-0.1.0/src/mcp_openapix/tools/describe_endpoint.py +43 -0
  27. mcp_openapix-0.1.0/src/mcp_openapix/tools/list_endpoints.py +67 -0
  28. mcp_openapix-0.1.0/src/mcp_openapix/tools/list_platforms.py +42 -0
  29. mcp_openapix-0.1.0/tests/__init__.py +0 -0
  30. mcp_openapix-0.1.0/tests/conftest.py +188 -0
  31. mcp_openapix-0.1.0/tests/test_api_client.py +291 -0
  32. mcp_openapix-0.1.0/tests/test_auth.py +306 -0
  33. mcp_openapix-0.1.0/tests/test_config.py +253 -0
  34. mcp_openapix-0.1.0/tests/test_refresh.py +239 -0
  35. mcp_openapix-0.1.0/tests/test_resources_e2e.py +96 -0
  36. mcp_openapix-0.1.0/tests/test_responses.py +57 -0
  37. mcp_openapix-0.1.0/tests/test_spec_loader.py +107 -0
  38. mcp_openapix-0.1.0/tests/test_tools.py +167 -0
  39. mcp_openapix-0.1.0/uv.lock +1097 -0
@@ -0,0 +1,4 @@
1
+ * text=auto eol=lf
2
+ *.{cmd,[cC][mM][dD]} text eol=crlf
3
+ *.{bat,[bB][aA][tT]} text eol=crlf
4
+
@@ -0,0 +1,84 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["*"]
7
+ pull_request:
8
+ branches: [main]
9
+ workflow_dispatch:
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ test:
20
+ runs-on: ubuntu-latest
21
+
22
+ steps:
23
+ - name: Checkout
24
+ uses: actions/checkout@v7
25
+
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0
28
+ with:
29
+ enable-cache: true
30
+
31
+ - name: Install dependencies
32
+ run: uv sync --extra dev
33
+
34
+ - name: Lint and format
35
+ run: |
36
+ uv run ruff check src/ tests/
37
+ uv run ruff format --check src/ tests/
38
+
39
+ - name: Type check
40
+ run: uv run pyright
41
+
42
+ - name: Test
43
+ run: uv run pytest tests/ -v --tb=short
44
+
45
+ publish:
46
+ name: Release
47
+ needs: test
48
+ if: startsWith(github.ref, 'refs/tags/')
49
+ runs-on: ubuntu-latest
50
+ environment:
51
+ name: pypi
52
+ url: https://pypi.org/project/mcp-openapix/${{ github.ref_name }}/
53
+ permissions:
54
+ contents: read
55
+ id-token: write
56
+ steps:
57
+ - name: Checkout
58
+ uses: actions/checkout@v7
59
+
60
+ - name: Install uv
61
+ uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0
62
+ with:
63
+ enable-cache: true
64
+
65
+ - name: Build Python package distributions
66
+ run: uv build
67
+
68
+ - name: Publish Python package to PyPI
69
+ uses: pypa/gh-action-pypi-publish@release/v1
70
+
71
+ - name: Install MCP Registry publisher
72
+ run: |
73
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
74
+
75
+ - name: Update MCP Registry manifest version
76
+ run: |
77
+ jq --arg version "${GITHUB_REF_NAME}" '.version = $version | .packages[].version = $version' server.json > server.tmp
78
+ mv server.tmp server.json
79
+
80
+ - name: Authenticate MCP Registry publisher
81
+ run: ./mcp-publisher login github-oidc
82
+
83
+ - name: Publish server metadata to MCP Registry
84
+ run: ./mcp-publisher publish
@@ -0,0 +1,41 @@
1
+ # Project / local
2
+ .env
3
+ .env.local
4
+ .env.*.local
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+ *.so
11
+
12
+ # Virtual environment (uv)
13
+ .venv/
14
+ env/
15
+ venv/
16
+
17
+ # Build / packaging
18
+ build/
19
+ dist/
20
+ *.egg-info/
21
+ *.egg
22
+
23
+ # Test / coverage (pytest, pytest-cov)
24
+ .pytest_cache/
25
+ .coverage
26
+ .coverage.*
27
+ htmlcov/
28
+ cover/
29
+
30
+ # Ruff
31
+ .ruff_cache/
32
+
33
+ # mypy
34
+ .mypy_cache/
35
+
36
+ # PyPI (credentials)
37
+ .pypirc
38
+
39
+ # AI agent
40
+ *-progress.md
41
+ .claude/settings.local.json
@@ -0,0 +1,19 @@
1
+ # See https://pre-commit.com for more
2
+ # Run: pre-commit install
3
+
4
+ repos:
5
+ - repo: https://github.com/astral-sh/ruff-pre-commit
6
+ rev: v0.8.4
7
+ hooks:
8
+ - id: ruff
9
+ args: [--fix]
10
+ - id: ruff-format
11
+
12
+ - repo: local
13
+ hooks:
14
+ - id: pyright
15
+ name: pyright
16
+ entry: uv run pyright
17
+ language: system
18
+ types: [python]
19
+ pass_filenames: false
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,138 @@
1
+ # Agent Instructions
2
+
3
+ Rules AI agents must follow when working in this repository.
4
+
5
+ ---
6
+
7
+ ## Commit messages
8
+
9
+ Use **Conventional Commits**.
10
+
11
+ ### Header
12
+
13
+ * Format: `<type>(optional scope): summary`
14
+ * Use lowercase types (`feat`, `fix`, `ci`, `chore`, `docs`)
15
+ * Use scopes when relevant
16
+ * Write summaries in lowercase, imperative mood
17
+
18
+ ### Body
19
+
20
+ * Leave a blank line after the header
21
+ * Explain **why**, not what
22
+ * Use imperative, present tense
23
+ * Wrap lines at ~72 characters
24
+
25
+ The body is optional for trivial changes.
26
+
27
+ ---
28
+
29
+ ## Commits
30
+
31
+ When generating commits via a shell:
32
+
33
+ * Do **not** pass generated messages directly to `git commit -m`
34
+ * Write the commit message to a file or standard input
35
+ * Use `git commit -F <file>` or `git commit -F -`
36
+ * Disable shell expansion when writing commit messages
37
+
38
+ This avoids issues with backticks, quotes, and other shell-expanded characters in
39
+ generated commit messages.
40
+
41
+ ---
42
+
43
+ ## Attribution
44
+
45
+ Every AI-assisted commit, tag, PR, comment, reply, or message an agent writes for
46
+ someone must carry an `Assisted-by` trailer:
47
+
48
+ ```
49
+ Assisted-by: AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2]
50
+ ```
51
+
52
+ | Field | Description |
53
+ |-------------------|-----------------------------------------------------------|
54
+ | `AGENT_NAME` | AI tool or framework (e.g. `Claude`, `Cursor`, `Copilot`) |
55
+ | `MODEL_VERSION` | Specific model (e.g. `claude-opus-4-6`) |
56
+ | `[TOOL1] [TOOL2]` | Optional specialized analysis tools; omit everyday tools |
57
+
58
+ * Place it at the **end**, after a blank line: a git trailer in commits, the last line
59
+ of the body everywhere else.
60
+ * Skip it only for text the user dictates verbatim.
61
+ * Use only `Assisted-by` — no `Co-Authored-By`, no `Made with …`, no hand-written `Sent
62
+ using …`, no other footers.
63
+
64
+ Example:
65
+
66
+ ```
67
+ Assisted-by: Claude:claude-opus-4-6 coccinelle sparse
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Code style
73
+
74
+ Follow existing project conventions.
75
+
76
+ * Match formatting, naming, and file structure already in use
77
+ * Wrap prose and Markdown matching `line-length` in `pyproject.toml`; leave code blocks,
78
+ tables, and URLs unwrapped
79
+ * Do not reformat unrelated code
80
+ * Prefer small, focused changes
81
+ * Avoid introducing new patterns without clear benefit
82
+
83
+ ### Language-specific rules
84
+
85
+ * Respect `.editorconfig` when present
86
+ * Do not disable lint rules without justification
87
+ * Prefer explicit, readable code over clever abstractions
88
+ * Ensure all changes pass `ruff check .`, `ruff format --check .`, `pyright`, and
89
+ `pytest`
90
+
91
+ ---
92
+
93
+ ## `uv` Workflow Rules
94
+
95
+ * Use `uv` exclusively for dependency management instead of `pip`
96
+ * Always prefix tool and script invocations with `uv run` so they execute inside the
97
+ managed environment
98
+ * Do not manually create, activate, or delete `.venv` directories
99
+
100
+ ---
101
+
102
+ ## Vendor neutrality
103
+
104
+ This server fronts **any** OpenAPI service. Nothing in `src/`, `tests/`,
105
+ `config.example.json` or the docs may name a specific vendor, brand, product, internal
106
+ hostname, or employer.
107
+
108
+ * Example configs use `example.com` and placeholder names (`acme`, `items`)
109
+ * A behavior that exists because one upstream happens to work a certain way is
110
+ documented by the **property** it relies on, never by who it was observed in
111
+ * Vendor-specific login belongs in a token helper, in its own repository — see
112
+ `docs/token-protocol.md`
113
+ * The `[OpenAPI]` tool-description prefix required below names the protocol this server
114
+ speaks, not a vendor, and is not an exception to this rule
115
+
116
+ ---
117
+
118
+ ## MCP Metadata
119
+
120
+ Normative, high-density metadata: enough for correct tool and parameter selection,
121
+ minimal to reduce token cost.
122
+
123
+ * **The tool/parameter description MUST start with [OpenAPI], followed by a Verb-Object
124
+ fragment**, e.g. `[OpenAPI] Execute an API operation`, `[OpenAPI] List OpenAPI
125
+ operations`, `[OpenAPI] Get the full OpenAPI schema`.
126
+ * **Use tag-based lineage (Src: <Entity>) for parameters that refer to entities the
127
+ server can enumerate** (e.g. platform → Src: platforms, region → Src: regions, service
128
+ → Src: services, operation id → Src: operations).
129
+ * **Every tool MUST declare `ToolAnnotations`** — hints, not guarantees, that a host
130
+ turns into a consent prompt.
131
+ * `read_only_hint=True` claims the tool changes nothing anywhere: writing a local file
132
+ is a change.
133
+ * `destructive_hint` and `idempotent_hint` matter only when `read_only_hint=False` — set
134
+ both there, omit both otherwise. Both are positive claims: `destructive_hint=False`
135
+ promises additive-only writes, `idempotent_hint=True` promises a repeat call with the
136
+ same arguments has no further effect.
137
+ * `open_world_hint` tracks the domain of interaction, not the I/O — `False` only when
138
+ that domain is fixed at build time (bundled data, local config).
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jon X
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,284 @@
1
+ Metadata-Version: 2.5
2
+ Name: mcp-openapix
3
+ Version: 0.1.0
4
+ Summary: A Model Context Protocol (MCP) server that fronts any OpenAPI service: discover operations from its spec and call them, with bearer tokens supplied by pluggable helper commands.
5
+ Project-URL: Homepage, https://github.com/alyiox/mcp-openapix
6
+ Project-URL: Repository, https://github.com/alyiox/mcp-openapix.git
7
+ Project-URL: Issues, https://github.com/alyiox/mcp-openapix/issues
8
+ Author-email: Joh X <alyiox@hotmail.com>
9
+ Maintainer-email: Joh X <alyiox@hotmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,api,llm,mcp,model-context-protocol,openapi,rest,swagger
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: >=3.13
24
+ Requires-Dist: filelock>=3.30
25
+ Requires-Dist: httpx>=0.27
26
+ Requires-Dist: mcp[cli]>=2.0.0
27
+ Requires-Dist: pydantic>=2.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: anyio>=4.0; extra == 'dev'
30
+ Requires-Dist: pre-commit>=4.0; extra == 'dev'
31
+ Requires-Dist: pyright>=1.1; extra == 'dev'
32
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
33
+ Requires-Dist: pytest-cov>=6.0; extra == 'dev'
34
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
35
+ Requires-Dist: respx>=0.21; extra == 'dev'
36
+ Requires-Dist: ruff>=0.15; extra == 'dev'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # mcp-openapix
40
+
41
+ [![CI](https://github.com/alyiox/mcp-openapix/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/alyiox/mcp-openapix/actions/workflows/ci.yml)
42
+ [![PyPI](https://img.shields.io/pypi/v/mcp-openapix.svg)](https://pypi.org/project/mcp-openapix/)
43
+ [![Python
44
+ 3.13+](https://img.shields.io/badge/python-3.13%2B-blue.svg)](https://www.python.org/downloads/)
45
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
46
+
47
+ <!-- mcp-name: io.github.alyiox/mcp-openapix -->
48
+
49
+ [MCP](https://modelcontextprotocol.io) server that fronts **any** OpenAPI service behind
50
+ four generic tools.
51
+
52
+ An agent finds operations in each deployment's OpenAPI document and calls them; the
53
+ server resolves the URL, obtains a bearer token, and builds the request. Discovery is
54
+ `list_platforms`, `list_endpoints` and `describe_endpoint`; execution is the generic
55
+ proxy `call_endpoint`.
56
+
57
+ ```
58
+ example / us / items / prod
59
+ │ │ │ └── env ......... which deployment URL a call reaches
60
+ │ │ └───────── service ..... one backend, one OpenAPI spec
61
+ │ └─────────────── region ...... a geographic deployment
62
+ └───────────────────── platform .... the product or API family
63
+ ```
64
+
65
+ ## Requirements
66
+
67
+ - Python 3.13+ and [`uv`](https://docs.astral.sh/uv/)
68
+ - A `config.json` describing the deployments you hold credentials for
69
+
70
+ ## Quick start
71
+
72
+ Set up your config (see [Configuration](#configuration)), then run the server:
73
+
74
+ ```bash
75
+ # Run directly with uvx (no clone needed)
76
+ npx -y @modelcontextprotocol/inspector@latest uvx mcp-openapix
77
+ ```
78
+
79
+ ```bash
80
+ # Or run from source
81
+ npx -y @modelcontextprotocol/inspector@latest uv run mcp-openapix
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ `config.json` MUST live at `~/.config/mcp-openapix/config.json`
87
+ (`%USERPROFILE%\.config\…` on Windows). `config.example.json` is a full template.
88
+
89
+ ```json
90
+ {
91
+ "headers": { "accept": "application/json" },
92
+ "defaults": { "platform": "example", "region": "us", "service": "items", "env": "prod" },
93
+ "platforms": {
94
+ "example": {
95
+ "regions": {
96
+ "us": {
97
+ "services": {
98
+ "token_helper": "us",
99
+ "items": {
100
+ "desc": "Catalogue and inventory API",
101
+ "spec_path": "/swagger/v1/swagger.json",
102
+ "canonical_env": "prod",
103
+ "envs": {
104
+ "prod": { "url": "https://api.example.com/items" },
105
+ "dev": { "url": "https://api-dev.example.com/items" }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ },
113
+ "token_helpers": {
114
+ "us": {
115
+ "command": "token-helper",
116
+ "args": ["issue"]
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ### `platforms`
123
+
124
+ A hierarchy of `platform → region → services → service → env`. Each service declares:
125
+
126
+ | Field | Notes |
127
+ |---|---|
128
+ | `spec_path` | Required. The OpenAPI JSON endpoint relative to the service URL |
129
+ | `canonical_env` | Required when more than one env is configured — the env whose URL the spec is fetched from |
130
+ | `envs` | Required. One entry per deployment environment, each carrying a full base `url` |
131
+ | `desc` | Optional. A short description surfaced by `list_platforms` |
132
+ | `token_helper` | Optional. The token helper this level binds to |
133
+
134
+ The `services` object may also contain a `token_helper` default applying to all services
135
+ in that region. A service or environment can override it.
136
+
137
+ ### `token_helpers`
138
+
139
+ Named token helpers, in the same shape as an MCP server entry:
140
+
141
+ | Field | Required | Default | Notes |
142
+ |---|---|---|---|
143
+ | `command` | yes | — | Resolved on `PATH`; never run through a shell |
144
+ | `args` | no | `[]` | Passed verbatim |
145
+ | `timeout` | no | `60` | Seconds before the helper's process group is killed; at most `300` |
146
+
147
+ The config names a command and nothing else, so `config.json` holds **no secrets**.
148
+ The complete helper invocation and output contract is documented in
149
+ [`docs/token-protocol.md`](docs/token-protocol.md).
150
+
151
+ Which helper a call uses is resolved most-specific-first:
152
+
153
+ ```
154
+ env.token_helper → service.token_helper → services.token_helper
155
+ → region.token_helper → platform.token_helper → defaults.token_helper
156
+ ```
157
+
158
+ If no level declares a helper, the deployment is unauthenticated. Omit
159
+ `token_helper` for public deployments.
160
+
161
+ ### `headers`
162
+
163
+ Constant headers added to every API call — for APIs that require a tenant, product or
164
+ locale header:
165
+
166
+ ```json
167
+ "headers": { "accept": "application/json", "x-product": "example" }
168
+ ```
169
+
170
+ ### `defaults`
171
+
172
+ Makes every tool argument optional: a call falls back to `defaults.platform`, `.region`,
173
+ `.service`, `.env`, `.username` and `.token_helper` when they are omitted.
174
+
175
+ ### Top-level options
176
+
177
+ | Field | Default | Notes |
178
+ |---|---|---|
179
+ | `truncate_threshold` | `1024` | Response bytes returned inline before truncating to a preview |
180
+ | `response_cache_ttl` | `3600` | Seconds a truncated body stays readable at its resource URI |
181
+ | `spec_refresh` | `{"auto": true, "interval": 7}` | Background spec refresh; `interval` is days and MAY be fractional |
182
+
183
+ ## Tools
184
+
185
+ | Tool | Purpose |
186
+ |---|---|
187
+ | `list_platforms` | Every platform with its regions, services, and envs |
188
+ | `list_endpoints` | A service's operations, filtered by `query`, `tag` or `method` |
189
+ | `describe_endpoint` | One operation plus the transitive closure of the schemas it references |
190
+ | `call_endpoint` | Execute an operation, or a raw `method` + `path` absent from the spec |
191
+
192
+ ### Operation ids
193
+
194
+ Many OpenAPI documents omit `operationId`, so the server synthesizes one as `"<METHOD>
195
+ <path>"`:
196
+
197
+ ```
198
+ POST /api/items
199
+ └─┬─┘ └───┬───┘
200
+ method path as the spec declares it
201
+ ```
202
+
203
+ Where a spec does declare an `operationId`, that value wins.
204
+
205
+ ## Specs
206
+
207
+ Specs are **not** bundled. Each deployment's document is fetched on demand — an
208
+ unauthenticated `GET` — and cached under
209
+ `~/.cache/mcp-openapix/{platform}/{region}/{service}.json`.
210
+
211
+ A document MUST declare at least one operation before it is installed, so a deployment
212
+ answering `200` with an error body cannot replace a working snapshot with one that
213
+ serves nothing.
214
+
215
+ Cached specs refresh in the background: once at startup, then every
216
+ `spec_refresh.interval` days. Set `auto` to `false` to stop it; the manual lever still
217
+ works:
218
+
219
+ ```bash
220
+ uvx mcp-openapix --refresh
221
+ ```
222
+
223
+ ## MCP resources
224
+
225
+ | Resource URI | Description |
226
+ |---|---|
227
+ | `openapi://responses/{request_id}` | Full body of a truncated `call_endpoint` response |
228
+ | `openapi://curl/{request_id}` | Equivalent curl command for a `call_endpoint` request |
229
+
230
+ Both expire `response_cache_ttl` seconds after the call. The curl command may embed
231
+ a short-lived token.
232
+
233
+ ## Tokens at rest
234
+
235
+ Tokens are cached in memory and, when expiry metadata is available, under
236
+ `~/.cache/mcp-openapix/tokens/` (mode `0600`) keyed by the token-helper declaration
237
+ and username. This lets client sessions share a login without spawning a helper each.
238
+ A `401` retires the cached token so the next call obtains a fresh one. To clear them all:
239
+
240
+ ```bash
241
+ uvx mcp-openapix --logout
242
+ ```
243
+
244
+ ## MCP host examples
245
+
246
+ <details> <summary><b>Cursor / Claude Code</b></summary>
247
+
248
+ ```json
249
+ {
250
+ "mcpServers": {
251
+ "openapi": { "command": "uvx", "args": ["mcp-openapix"] }
252
+ }
253
+ }
254
+ ```
255
+
256
+ </details>
257
+
258
+ <details> <summary><b>Codex</b></summary>
259
+
260
+ ```toml
261
+ [mcp_servers.openapi]
262
+ command = "uvx"
263
+ args = ["mcp-openapix"]
264
+ ```
265
+
266
+ </details>
267
+
268
+ ## Development
269
+
270
+ ```bash
271
+ uv sync --extra dev
272
+ uv run ruff check .
273
+ uv run ruff format --check .
274
+ uv run pyright
275
+ uv run pytest
276
+ ```
277
+
278
+ All four MUST pass; see `AGENTS.md`. Tests use
279
+ [`respx`](https://github.com/lundberg/respx) to mock HTTP and real subprocesses for
280
+ token helpers, so no live API access is required.
281
+
282
+ ## License
283
+
284
+ [MIT](LICENSE).