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()