speculate-mcp 0.2.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,38 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
.venv/
|
|
6
|
+
.env
|
|
7
|
+
*.egg-info/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
|
|
11
|
+
# uv
|
|
12
|
+
.uv/
|
|
13
|
+
|
|
14
|
+
# Node
|
|
15
|
+
node_modules/
|
|
16
|
+
.next/
|
|
17
|
+
out/
|
|
18
|
+
|
|
19
|
+
# Env files (keep examples, ignore actuals)
|
|
20
|
+
.env.local
|
|
21
|
+
!.env.local.example
|
|
22
|
+
!.env.example
|
|
23
|
+
|
|
24
|
+
# OS
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.vscode/
|
|
30
|
+
.idea/
|
|
31
|
+
*.swp
|
|
32
|
+
|
|
33
|
+
# Claude Code internal config
|
|
34
|
+
CLAUDE.md
|
|
35
|
+
AGENTS.md
|
|
36
|
+
|
|
37
|
+
# Local notes (not for repo)
|
|
38
|
+
NOTES.md
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: speculate-mcp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: MCP server for any OpenAPI-described API — resolve, search, and execute
|
|
5
|
+
Project-URL: Homepage, https://speculateapi.dev
|
|
6
|
+
Project-URL: Repository, https://github.com/mgalobll/speculate-app
|
|
7
|
+
License: MIT
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: httpx>=0.27.0
|
|
10
|
+
Requires-Dist: mcp>=1.0.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# speculate-mcp
|
|
14
|
+
|
|
15
|
+
MCP server for any OpenAPI-described API. Gives AI agents the ability to resolve, explore, and execute calls against any REST API with an OpenAPI spec — without writing custom integration code.
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
Five tools, covering the full integration loop:
|
|
20
|
+
|
|
21
|
+
| Tool | What it does |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `resolve_spec` | Load any API by name ("Stripe"), doc URL, or direct spec URL |
|
|
24
|
+
| `describe_auth` | Return exact auth headers, OAuth2 flows, and credential instructions |
|
|
25
|
+
| `search_endpoints` | Find relevant endpoints with a natural language query |
|
|
26
|
+
| `get_endpoint_schema` | Return path params, body fields, types, enums, and required flags |
|
|
27
|
+
| `execute_call` | Execute a live API call and return the response |
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
The recommended way — no global install needed:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uvx speculate-mcp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or install globally:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install speculate-mcp
|
|
41
|
+
# or
|
|
42
|
+
uv tool install speculate-mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Configure your AI IDE
|
|
46
|
+
|
|
47
|
+
### Claude Code (`~/.claude.json` or project `.claude/settings.json`)
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"speculate": {
|
|
53
|
+
"command": "speculate-mcp",
|
|
54
|
+
"env": {
|
|
55
|
+
"SPECULATE_API_KEY": "your-key-here"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Cursor (`.cursor/mcp.json`)
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"speculate": {
|
|
68
|
+
"command": "speculate-mcp",
|
|
69
|
+
"env": {
|
|
70
|
+
"SPECULATE_API_KEY": "your-key-here"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Claude Desktop (`claude_desktop_config.json`)
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"speculate": {
|
|
83
|
+
"command": "speculate-mcp",
|
|
84
|
+
"env": {
|
|
85
|
+
"SPECULATE_API_KEY": "your-key-here"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Get your API key at [speculateapi.dev](https://speculateapi.dev). Free tier works without a key (rate-limited).
|
|
93
|
+
|
|
94
|
+
## Usage example
|
|
95
|
+
|
|
96
|
+
Once connected, your agent can do things like:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
Load the Stripe API, find the endpoint to create a payment intent,
|
|
100
|
+
and make a test call with amount=1000, currency=usd.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The agent will call `resolve_spec("stripe")`, then `search_endpoints(...)`,
|
|
104
|
+
then `execute_call(...)` — all automatically.
|
|
105
|
+
|
|
106
|
+
## Environment variables
|
|
107
|
+
|
|
108
|
+
| Variable | Default | Description |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `SPECULATE_API_URL` | `https://mcp.speculateapi.dev` | Backend URL (for self-hosted deployments) |
|
|
111
|
+
| `SPECULATE_API_KEY` | _(empty)_ | Your Speculate API key |
|
|
112
|
+
| `MCP_TRANSPORT` | `stdio` | Set to `http` to run as an HTTP server instead of stdio |
|
|
113
|
+
| `PORT` | `8080` | Port to listen on when `MCP_TRANSPORT=http` |
|
|
114
|
+
|
|
115
|
+
## Self-hosting
|
|
116
|
+
|
|
117
|
+
Run the backend yourself:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
cd backend
|
|
121
|
+
cp .env.example .env
|
|
122
|
+
# Set OPENAI_API_KEY in .env
|
|
123
|
+
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Then point the MCP server at your instance:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
SPECULATE_API_URL=http://localhost:8000 speculate-mcp
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Publishing to PyPI
|
|
133
|
+
|
|
134
|
+
Releases are automated via GitHub Actions. To publish a new version:
|
|
135
|
+
|
|
136
|
+
1. Update `version` in `pyproject.toml`
|
|
137
|
+
2. Tag and push:
|
|
138
|
+
```bash
|
|
139
|
+
git tag mcp-v0.3.0
|
|
140
|
+
git push --tags
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The `publish-mcp.yml` workflow builds and publishes via PyPI trusted publishing (no stored secret needed — requires a configured PyPI environment named `pypi`).
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# speculate-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for any OpenAPI-described API. Gives AI agents the ability to resolve, explore, and execute calls against any REST API with an OpenAPI spec — without writing custom integration code.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Five tools, covering the full integration loop:
|
|
8
|
+
|
|
9
|
+
| Tool | What it does |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `resolve_spec` | Load any API by name ("Stripe"), doc URL, or direct spec URL |
|
|
12
|
+
| `describe_auth` | Return exact auth headers, OAuth2 flows, and credential instructions |
|
|
13
|
+
| `search_endpoints` | Find relevant endpoints with a natural language query |
|
|
14
|
+
| `get_endpoint_schema` | Return path params, body fields, types, enums, and required flags |
|
|
15
|
+
| `execute_call` | Execute a live API call and return the response |
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
The recommended way — no global install needed:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uvx speculate-mcp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or install globally:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install speculate-mcp
|
|
29
|
+
# or
|
|
30
|
+
uv tool install speculate-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Configure your AI IDE
|
|
34
|
+
|
|
35
|
+
### Claude Code (`~/.claude.json` or project `.claude/settings.json`)
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"speculate": {
|
|
41
|
+
"command": "speculate-mcp",
|
|
42
|
+
"env": {
|
|
43
|
+
"SPECULATE_API_KEY": "your-key-here"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Cursor (`.cursor/mcp.json`)
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"speculate": {
|
|
56
|
+
"command": "speculate-mcp",
|
|
57
|
+
"env": {
|
|
58
|
+
"SPECULATE_API_KEY": "your-key-here"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Claude Desktop (`claude_desktop_config.json`)
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"speculate": {
|
|
71
|
+
"command": "speculate-mcp",
|
|
72
|
+
"env": {
|
|
73
|
+
"SPECULATE_API_KEY": "your-key-here"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Get your API key at [speculateapi.dev](https://speculateapi.dev). Free tier works without a key (rate-limited).
|
|
81
|
+
|
|
82
|
+
## Usage example
|
|
83
|
+
|
|
84
|
+
Once connected, your agent can do things like:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
Load the Stripe API, find the endpoint to create a payment intent,
|
|
88
|
+
and make a test call with amount=1000, currency=usd.
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The agent will call `resolve_spec("stripe")`, then `search_endpoints(...)`,
|
|
92
|
+
then `execute_call(...)` — all automatically.
|
|
93
|
+
|
|
94
|
+
## Environment variables
|
|
95
|
+
|
|
96
|
+
| Variable | Default | Description |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `SPECULATE_API_URL` | `https://mcp.speculateapi.dev` | Backend URL (for self-hosted deployments) |
|
|
99
|
+
| `SPECULATE_API_KEY` | _(empty)_ | Your Speculate API key |
|
|
100
|
+
| `MCP_TRANSPORT` | `stdio` | Set to `http` to run as an HTTP server instead of stdio |
|
|
101
|
+
| `PORT` | `8080` | Port to listen on when `MCP_TRANSPORT=http` |
|
|
102
|
+
|
|
103
|
+
## Self-hosting
|
|
104
|
+
|
|
105
|
+
Run the backend yourself:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cd backend
|
|
109
|
+
cp .env.example .env
|
|
110
|
+
# Set OPENAI_API_KEY in .env
|
|
111
|
+
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Then point the MCP server at your instance:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
SPECULATE_API_URL=http://localhost:8000 speculate-mcp
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Publishing to PyPI
|
|
121
|
+
|
|
122
|
+
Releases are automated via GitHub Actions. To publish a new version:
|
|
123
|
+
|
|
124
|
+
1. Update `version` in `pyproject.toml`
|
|
125
|
+
2. Tag and push:
|
|
126
|
+
```bash
|
|
127
|
+
git tag mcp-v0.3.0
|
|
128
|
+
git push --tags
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The `publish-mcp.yml` workflow builds and publishes via PyPI trusted publishing (no stored secret needed — requires a configured PyPI environment named `pypi`).
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "speculate-mcp"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "MCP server for any OpenAPI-described API — resolve, search, and execute"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"mcp>=1.0.0",
|
|
10
|
+
"httpx>=0.27.0",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
speculate-mcp = "speculate_mcp.server:main"
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://speculateapi.dev"
|
|
18
|
+
Repository = "https://github.com/mgalobll/speculate-app"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["hatchling"]
|
|
22
|
+
build-backend = "hatchling.build"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["speculate_mcp"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Speculate MCP server — universal API execution layer for AI agents."""
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Speculate MCP server.
|
|
3
|
+
|
|
4
|
+
Exposes three tools to AI agents:
|
|
5
|
+
resolve_spec — load any OpenAPI spec by name or URL
|
|
6
|
+
search_endpoints — semantic search over a loaded spec
|
|
7
|
+
execute_call — execute a live API call against the spec's base URL
|
|
8
|
+
|
|
9
|
+
Configuration (environment variables):
|
|
10
|
+
SPECULATE_API_URL — backend URL (default: https://mcp.speculateapi.dev)
|
|
11
|
+
SPECULATE_API_KEY — your Speculate API key (optional on free tier)
|
|
12
|
+
"""
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
from mcp.server.fastmcp import FastMCP
|
|
18
|
+
|
|
19
|
+
mcp = FastMCP(
|
|
20
|
+
"Speculate",
|
|
21
|
+
instructions=(
|
|
22
|
+
"Workflow: (1) call resolve_spec to load the API, "
|
|
23
|
+
"(2) call describe_auth to understand what credentials are needed, "
|
|
24
|
+
"(3) call search_endpoints to find the right endpoint, "
|
|
25
|
+
"(4) call get_endpoint_schema for the full parameter and body contract, "
|
|
26
|
+
"(5) call execute_call with auth in the headers dict. "
|
|
27
|
+
"Always use the exact path template from search_endpoints (e.g. '/v1/charges/{id}') "
|
|
28
|
+
"in get_endpoint_schema and execute_call — not the resolved path with real values."
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
_BASE_URL = os.getenv("SPECULATE_API_URL", "https://mcp.speculateapi.dev").rstrip("/")
|
|
33
|
+
_API_KEY = os.getenv("SPECULATE_API_KEY", "")
|
|
34
|
+
_TIMEOUT = 90.0 # spec indexing can take time on cache miss
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _headers() -> dict[str, str]:
|
|
38
|
+
h: dict[str, str] = {"Content-Type": "application/json"}
|
|
39
|
+
if _API_KEY:
|
|
40
|
+
h["X-Speculate-Key"] = _API_KEY
|
|
41
|
+
return h
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _raise_for(resp: httpx.Response) -> None:
|
|
45
|
+
if resp.status_code >= 400:
|
|
46
|
+
try:
|
|
47
|
+
detail = resp.json().get("detail", resp.text)
|
|
48
|
+
except Exception:
|
|
49
|
+
detail = resp.text
|
|
50
|
+
raise RuntimeError(f"Speculate API error {resp.status_code}: {detail}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@mcp.tool()
|
|
54
|
+
async def resolve_spec(query: str) -> dict:
|
|
55
|
+
"""
|
|
56
|
+
Load and index any OpenAPI-described API. Call this first before
|
|
57
|
+
searching or executing calls.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
query: API name (e.g. "Stripe", "GitHub"), documentation URL,
|
|
61
|
+
or direct spec URL (JSON or YAML).
|
|
62
|
+
|
|
63
|
+
Returns a dict with:
|
|
64
|
+
session_id — use this in search_endpoints and execute_call
|
|
65
|
+
title — API title from the spec
|
|
66
|
+
version — API version
|
|
67
|
+
endpoint_count — total number of endpoints indexed
|
|
68
|
+
base_url — base URL for live API calls
|
|
69
|
+
cache_hit — True if the spec was already indexed
|
|
70
|
+
confidence — resolution confidence: "high", "medium", or "low"
|
|
71
|
+
"""
|
|
72
|
+
async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
|
|
73
|
+
resp = await client.post(
|
|
74
|
+
f"{_BASE_URL}/mcp/resolve",
|
|
75
|
+
json={"query": query},
|
|
76
|
+
headers=_headers(),
|
|
77
|
+
)
|
|
78
|
+
_raise_for(resp)
|
|
79
|
+
return resp.json()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@mcp.tool()
|
|
83
|
+
async def search_endpoints(session_id: str, query: str, top_k: int = 8) -> dict:
|
|
84
|
+
"""
|
|
85
|
+
Search for API endpoints matching a natural language query.
|
|
86
|
+
Requires a session_id from resolve_spec.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
session_id: from resolve_spec
|
|
90
|
+
query: natural language description, e.g. "create a payment intent"
|
|
91
|
+
or "list all users with pagination"
|
|
92
|
+
top_k: number of results to return (1–20, default 8)
|
|
93
|
+
|
|
94
|
+
Returns a dict with:
|
|
95
|
+
results — list of matching endpoints, each with:
|
|
96
|
+
method — HTTP method (GET, POST, etc.)
|
|
97
|
+
path — endpoint path, e.g. "/v1/payment_intents"
|
|
98
|
+
summary — short description from the spec
|
|
99
|
+
tags — spec tags/categories
|
|
100
|
+
description — full description (may be empty)
|
|
101
|
+
score — relevance score (0–1)
|
|
102
|
+
"""
|
|
103
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
104
|
+
resp = await client.post(
|
|
105
|
+
f"{_BASE_URL}/mcp/search",
|
|
106
|
+
json={"session_id": session_id, "query": query, "top_k": top_k},
|
|
107
|
+
headers=_headers(),
|
|
108
|
+
)
|
|
109
|
+
_raise_for(resp)
|
|
110
|
+
return resp.json()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@mcp.tool()
|
|
114
|
+
async def execute_call(
|
|
115
|
+
session_id: str,
|
|
116
|
+
method: str,
|
|
117
|
+
path: str,
|
|
118
|
+
path_params: dict | None = None,
|
|
119
|
+
query_params: dict | None = None,
|
|
120
|
+
body: dict | None = None,
|
|
121
|
+
headers: dict | None = None,
|
|
122
|
+
) -> dict:
|
|
123
|
+
"""
|
|
124
|
+
Execute a live API call against the spec's base URL.
|
|
125
|
+
Requires a session_id from resolve_spec.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
session_id: from resolve_spec
|
|
129
|
+
method: HTTP method — "GET", "POST", "PUT", "PATCH", "DELETE"
|
|
130
|
+
path: endpoint path from the spec, e.g. "/v1/charges/{id}"
|
|
131
|
+
path_params: values for path template variables, e.g. {"id": "ch_123"}
|
|
132
|
+
query_params: URL query parameters, e.g. {"limit": "10", "cursor": "abc"}
|
|
133
|
+
body: request body as a dict (JSON-encoded automatically)
|
|
134
|
+
headers: request headers — include Authorization here,
|
|
135
|
+
e.g. {"Authorization": "Bearer sk-..."}
|
|
136
|
+
|
|
137
|
+
Returns a dict with:
|
|
138
|
+
status — HTTP response status code
|
|
139
|
+
body — response body as a string
|
|
140
|
+
headers — response headers
|
|
141
|
+
resolved_url — the full URL that was called
|
|
142
|
+
truncated — True if the response was truncated (>10 MB)
|
|
143
|
+
"""
|
|
144
|
+
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
145
|
+
resp = await client.post(
|
|
146
|
+
f"{_BASE_URL}/mcp/execute",
|
|
147
|
+
json={
|
|
148
|
+
"session_id": session_id,
|
|
149
|
+
"method": method,
|
|
150
|
+
"path": path,
|
|
151
|
+
"path_params": path_params or {},
|
|
152
|
+
"query_params": query_params or {},
|
|
153
|
+
"body": body,
|
|
154
|
+
"headers": headers or {},
|
|
155
|
+
},
|
|
156
|
+
headers=_headers(),
|
|
157
|
+
)
|
|
158
|
+
_raise_for(resp)
|
|
159
|
+
return resp.json()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@mcp.tool()
|
|
163
|
+
async def get_endpoint_schema(session_id: str, method: str, path: str) -> dict:
|
|
164
|
+
"""
|
|
165
|
+
Return the full schema for a specific endpoint: path parameters, query
|
|
166
|
+
parameters, request body fields with types and constraints, documented
|
|
167
|
+
response codes, and auth requirements.
|
|
168
|
+
|
|
169
|
+
Use the exact path string as returned by search_endpoints
|
|
170
|
+
(e.g. "/v1/charges/{id}", not "/v1/charges/ch_123").
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
session_id: from resolve_spec
|
|
174
|
+
method: HTTP method — "GET", "POST", "PUT", "PATCH", "DELETE"
|
|
175
|
+
path: endpoint path template, e.g. "/v1/payment_intents/{id}"
|
|
176
|
+
|
|
177
|
+
Returns a dict with:
|
|
178
|
+
method, path, summary, description
|
|
179
|
+
path_params — list of path parameters with name, type, required, example
|
|
180
|
+
query_params — list of query parameters with name, type, required, example
|
|
181
|
+
body_schema — JSON string of an example request body (null if none)
|
|
182
|
+
body_fields — list of request body fields with type, required, enum values
|
|
183
|
+
security — auth requirements for this endpoint
|
|
184
|
+
responses — dict of {status_code: description}
|
|
185
|
+
"""
|
|
186
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
187
|
+
resp = await client.post(
|
|
188
|
+
f"{_BASE_URL}/mcp/schema",
|
|
189
|
+
json={"session_id": session_id, "method": method, "path": path},
|
|
190
|
+
headers=_headers(),
|
|
191
|
+
)
|
|
192
|
+
_raise_for(resp)
|
|
193
|
+
return resp.json()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@mcp.tool()
|
|
197
|
+
async def describe_auth(session_id: str) -> dict:
|
|
198
|
+
"""
|
|
199
|
+
Return complete auth scaffolding for the loaded API.
|
|
200
|
+
|
|
201
|
+
Tells you exactly what credentials you need, how to obtain them,
|
|
202
|
+
and which header/parameter to inject them into. Covers API keys,
|
|
203
|
+
Bearer tokens, Basic auth, and OAuth2 flows with authorization URLs,
|
|
204
|
+
token endpoints, and required scopes.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
session_id: from resolve_spec
|
|
208
|
+
|
|
209
|
+
Returns a dict with:
|
|
210
|
+
has_auth — False if the API has no documented auth
|
|
211
|
+
schemes — list of auth schemes, each with:
|
|
212
|
+
name — scheme name from the spec
|
|
213
|
+
type — "apiKey", "http", "oauth2", "openIdConnect"
|
|
214
|
+
inject_as — "header", "query", or "cookie"
|
|
215
|
+
header_name — exact header name to set (e.g. "Authorization")
|
|
216
|
+
instructions — step-by-step instructions as a string
|
|
217
|
+
globally_required — True if required by all endpoints
|
|
218
|
+
oauth2_flows — OAuth2 flow details (authorization_url, token_url, scopes)
|
|
219
|
+
"""
|
|
220
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
221
|
+
resp = await client.post(
|
|
222
|
+
f"{_BASE_URL}/mcp/auth",
|
|
223
|
+
json={"session_id": session_id},
|
|
224
|
+
headers=_headers(),
|
|
225
|
+
)
|
|
226
|
+
_raise_for(resp)
|
|
227
|
+
return resp.json()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def main() -> None:
|
|
231
|
+
transport = os.getenv("MCP_TRANSPORT", "stdio")
|
|
232
|
+
if transport == "http":
|
|
233
|
+
mcp.run(
|
|
234
|
+
transport="streamable-http",
|
|
235
|
+
host="0.0.0.0",
|
|
236
|
+
port=int(os.getenv("PORT", "8080")),
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
mcp.run()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
main()
|