fetchsandbox-mcp 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +114 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +118 -0
- package/dist/index.js.map +1 -0
- package/dist/session.d.ts +2 -0
- package/dist/session.js +47 -0
- package/dist/session.js.map +1 -0
- package/dist/tools/import_spec.d.ts +49 -0
- package/dist/tools/import_spec.js +46 -0
- package/dist/tools/import_spec.js.map +1 -0
- package/dist/tools/list_workflows.d.ts +34 -0
- package/dist/tools/list_workflows.js +36 -0
- package/dist/tools/list_workflows.js.map +1 -0
- package/dist/tools/run_workflow.d.ts +42 -0
- package/dist/tools/run_workflow.js +47 -0
- package/dist/tools/run_workflow.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FetchSandbox
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# fetchsandbox-mcp
|
|
2
|
+
|
|
3
|
+
Turn any OpenAPI spec into a working sandbox your AI agent can use, right from your IDE.
|
|
4
|
+
|
|
5
|
+
This is the Model Context Protocol (MCP) server for [FetchSandbox](https://fetchsandbox.com). It exposes three tools that let Claude Code, Cursor, Cline, or any MCP-compatible client ingest an OpenAPI spec, list its workflows, and run them — with realistic, schema-validated responses for every endpoint.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Agents read raw OpenAPI specs and hallucinate. They guess field names, invent IDs that won't exist, and produce broken curl commands. FetchSandbox turns the spec into a stateful, AJV-validated sandbox so the agent can actually call the API and see real-shaped responses.
|
|
10
|
+
|
|
11
|
+
Plug it into your IDE once, and any time you ask your agent "let me try the Stripe API" or "show me the GitHub issue lifecycle," it can do that — for real, end-to-end.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
The MCP runs as a stdio process spawned by your IDE. There's nothing to install globally — `npx` runs the latest published version on demand.
|
|
16
|
+
|
|
17
|
+
### Claude Code (`~/.config/claude/claude_code_settings.json` or your project's `.mcp.json`)
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"fetchsandbox": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "fetchsandbox-mcp"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Cursor (`~/.cursor/mcp.json`)
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"fetchsandbox": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "fetchsandbox-mcp"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Restart your IDE after editing the config. The tools `import_spec`, `list_workflows`, and `run_workflow` become available to your agent.
|
|
44
|
+
|
|
45
|
+
## Tools
|
|
46
|
+
|
|
47
|
+
### `import_spec`
|
|
48
|
+
|
|
49
|
+
Ingest an OpenAPI 3.x spec and get a sandbox you can call. Pass either a public URL or pasted content.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
url: "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json"
|
|
53
|
+
content: "<paste OpenAPI JSON or YAML here>"
|
|
54
|
+
name: "Optional friendly name"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Returns `spec_id`, `sandbox_id`, `base_url` (proxy that serves real-shaped responses), workflow list, and a `dashboard_url` to view everything in the browser.
|
|
58
|
+
|
|
59
|
+
### `list_workflows`
|
|
60
|
+
|
|
61
|
+
List the named, runnable workflows the engine inferred or curated for an imported spec.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
spec_id: "<id from import_spec>"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### `run_workflow`
|
|
68
|
+
|
|
69
|
+
Execute one workflow and return the step-by-step request/response trace. Template variables (`{{step1.id}}`) are resolved automatically between steps.
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
sandbox_id: "<id from import_spec>"
|
|
73
|
+
workflow_name: "<id or name from list_workflows>"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Configuration
|
|
77
|
+
|
|
78
|
+
| Env var | Default | Purpose |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `FETCHSANDBOX_BASE_URL` | `https://fetchsandbox.com` | Override for stage testing or self-hosted backends. |
|
|
81
|
+
| `FETCHSANDBOX_TELEMETRY` | (on) | Set to `0` to disable anonymous usage telemetry. |
|
|
82
|
+
|
|
83
|
+
### What we record
|
|
84
|
+
|
|
85
|
+
When telemetry is on, each tool call records: an opaque per-machine session id (random UUID stored at `~/.fetchsandbox/session.json`), the tool name, latency, success/failure, and the spec URL or `"pasted"`. We do **not** record spec content, request bodies, or credentials. We use this to count daily-active sessions and learn which APIs people are bringing to the platform — full breakdown is open-source in [`mcp_telemetry.py`](https://github.com/rnagulapalle/sandbox/blob/main/backend/app/flows/mcp_telemetry.py).
|
|
86
|
+
|
|
87
|
+
To opt out:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
export FETCHSANDBOX_TELEMETRY=0
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Example session
|
|
94
|
+
|
|
95
|
+
> **You:** Try the Stripe API. Use this spec: https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json
|
|
96
|
+
>
|
|
97
|
+
> **Agent:** *(calls `import_spec`)* Imported. 471 endpoints, 12 workflows including customer-create, subscription-lifecycle, refund-flow.
|
|
98
|
+
>
|
|
99
|
+
> **You:** Run the subscription-lifecycle one.
|
|
100
|
+
>
|
|
101
|
+
> **Agent:** *(calls `run_workflow`)* All 5 steps passed: created customer (cus_LJ4nQ...), attached payment method (pm_8K2...), created subscription (sub_R6F...), updated to a different price (sub.items.0 swap), canceled subscription. Here's the trace: ...
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT — see [LICENSE](LICENSE).
|
|
106
|
+
|
|
107
|
+
## Links
|
|
108
|
+
|
|
109
|
+
- [FetchSandbox](https://fetchsandbox.com)
|
|
110
|
+
- [Source code](https://github.com/rnagulapalle/sandbox/tree/main/mcp)
|
|
111
|
+
- [Issues](https://github.com/rnagulapalle/sandbox/issues)
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare class ToolError extends Error {
|
|
2
|
+
status?: number;
|
|
3
|
+
constructor(message: string, status?: number);
|
|
4
|
+
}
|
|
5
|
+
export declare function getBaseUrl(): string;
|
|
6
|
+
export declare function postJson<T>(path: string, body: unknown): Promise<T>;
|
|
7
|
+
export declare function getJson<T>(path: string): Promise<T>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin HTTP client for the FetchSandbox backend.
|
|
3
|
+
*
|
|
4
|
+
* Base URL defaults to https://fetchsandbox.com. Override via
|
|
5
|
+
* FETCHSANDBOX_BASE_URL for stage testing or self-hosted deployments.
|
|
6
|
+
*
|
|
7
|
+
* Errors are normalized into ToolError so tools can surface them to the
|
|
8
|
+
* agent as readable text instead of a stack trace.
|
|
9
|
+
*/
|
|
10
|
+
import { getSessionId } from "./session.js";
|
|
11
|
+
const DEFAULT_BASE_URL = "https://fetchsandbox.com";
|
|
12
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
13
|
+
// Retry only on transient failures: nginx 502/503/504 + network errors.
|
|
14
|
+
// 4xx responses (bad spec, validation errors) should fail fast.
|
|
15
|
+
const RETRY_STATUSES = new Set([502, 503, 504]);
|
|
16
|
+
const MAX_RETRIES = 1;
|
|
17
|
+
const RETRY_BACKOFF_MS = 1500;
|
|
18
|
+
export class ToolError extends Error {
|
|
19
|
+
status;
|
|
20
|
+
constructor(message, status) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "ToolError";
|
|
23
|
+
this.status = status;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function getBaseUrl() {
|
|
27
|
+
return (process.env.FETCHSANDBOX_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
28
|
+
}
|
|
29
|
+
function buildHeaders(extra) {
|
|
30
|
+
const headers = {
|
|
31
|
+
"user-agent": `fetchsandbox-mcp/0.1.0 (node ${process.version})`,
|
|
32
|
+
accept: "application/json",
|
|
33
|
+
...(extra ?? {}),
|
|
34
|
+
};
|
|
35
|
+
const sid = getSessionId();
|
|
36
|
+
if (sid)
|
|
37
|
+
headers["x-mcp-session-id"] = sid;
|
|
38
|
+
return headers;
|
|
39
|
+
}
|
|
40
|
+
async function readErrorMessage(res) {
|
|
41
|
+
try {
|
|
42
|
+
const body = await res.text();
|
|
43
|
+
if (!body)
|
|
44
|
+
return `HTTP ${res.status}`;
|
|
45
|
+
try {
|
|
46
|
+
const j = JSON.parse(body);
|
|
47
|
+
if (typeof j === "object" && j !== null && "detail" in j)
|
|
48
|
+
return String(j.detail);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// not JSON
|
|
52
|
+
}
|
|
53
|
+
return body.slice(0, 500);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return `HTTP ${res.status}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function fetchWithRetry(url, init, method, path) {
|
|
60
|
+
let lastErr;
|
|
61
|
+
let lastStatus = null;
|
|
62
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
63
|
+
const ctrl = new AbortController();
|
|
64
|
+
const timer = setTimeout(() => ctrl.abort(), REQUEST_TIMEOUT_MS);
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch(url, { ...init, signal: ctrl.signal });
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
if (res.ok)
|
|
69
|
+
return res;
|
|
70
|
+
// Retry on transient upstream errors only.
|
|
71
|
+
if (RETRY_STATUSES.has(res.status) && attempt < MAX_RETRIES) {
|
|
72
|
+
lastStatus = res.status;
|
|
73
|
+
await new Promise((r) => setTimeout(r, RETRY_BACKOFF_MS));
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// Non-retryable error — surface as ToolError immediately.
|
|
77
|
+
throw new ToolError(await readErrorMessage(res), res.status);
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
if (e instanceof ToolError)
|
|
82
|
+
throw e;
|
|
83
|
+
lastErr = e;
|
|
84
|
+
const errName = e.name;
|
|
85
|
+
const isAbort = errName === "AbortError";
|
|
86
|
+
// Retry network-level failures (DNS, ECONNRESET, timeouts)
|
|
87
|
+
if (attempt < MAX_RETRIES && (isAbort || errName === "TypeError" || errName === "FetchError")) {
|
|
88
|
+
await new Promise((r) => setTimeout(r, RETRY_BACKOFF_MS));
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (isAbort) {
|
|
92
|
+
throw new ToolError(`Request timed out after ${REQUEST_TIMEOUT_MS / 1000}s: ${method} ${path}`);
|
|
93
|
+
}
|
|
94
|
+
throw new ToolError(`Network error calling ${path}: ${e.message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Loop exhausted with retries exhausted on transient status code.
|
|
98
|
+
throw new ToolError(`Upstream returned ${lastStatus ?? "error"} after ${MAX_RETRIES + 1} attempts: ${method} ${path}`, lastStatus ?? undefined);
|
|
99
|
+
}
|
|
100
|
+
export async function postJson(path, body) {
|
|
101
|
+
const url = `${getBaseUrl()}${path}`;
|
|
102
|
+
const res = await fetchWithRetry(url, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: buildHeaders({ "content-type": "application/json" }),
|
|
105
|
+
body: JSON.stringify(body),
|
|
106
|
+
}, "POST", path);
|
|
107
|
+
return (await res.json());
|
|
108
|
+
}
|
|
109
|
+
export async function getJson(path) {
|
|
110
|
+
const url = `${getBaseUrl()}${path}`;
|
|
111
|
+
const res = await fetchWithRetry(url, { headers: buildHeaders() }, "GET", path);
|
|
112
|
+
return (await res.json());
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* fetchsandbox-mcp — Model Context Protocol server.
|
|
4
|
+
*
|
|
5
|
+
* Exposes three tools to any MCP client (Claude Code, Cursor, Cline, etc.):
|
|
6
|
+
* - import_spec Ingest an OpenAPI spec; get a sandbox you can call.
|
|
7
|
+
* - list_workflows List named, runnable workflows for an imported spec.
|
|
8
|
+
* - run_workflow Execute a workflow and return the step trace.
|
|
9
|
+
*
|
|
10
|
+
* Talks to the FetchSandbox backend over HTTPS. Defaults to fetchsandbox.com;
|
|
11
|
+
* override with FETCHSANDBOX_BASE_URL for stage or self-hosted use.
|
|
12
|
+
*
|
|
13
|
+
* Distribution: npx -y fetchsandbox-mcp.
|
|
14
|
+
*/
|
|
15
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
16
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
17
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
18
|
+
import { ToolError, getBaseUrl } from "./client.js";
|
|
19
|
+
import { importSpecTool, runImportSpec } from "./tools/import_spec.js";
|
|
20
|
+
import { listWorkflowsTool, runListWorkflows } from "./tools/list_workflows.js";
|
|
21
|
+
import { runRunWorkflow, runWorkflowTool } from "./tools/run_workflow.js";
|
|
22
|
+
const VERSION = "0.1.0";
|
|
23
|
+
const server = new Server({ name: "fetchsandbox", version: VERSION }, { capabilities: { tools: {} } });
|
|
24
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
25
|
+
tools: [importSpecTool, listWorkflowsTool, runWorkflowTool],
|
|
26
|
+
}));
|
|
27
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
28
|
+
const { name, arguments: args } = req.params;
|
|
29
|
+
try {
|
|
30
|
+
let result;
|
|
31
|
+
const a = (args ?? {});
|
|
32
|
+
switch (name) {
|
|
33
|
+
case importSpecTool.name:
|
|
34
|
+
result = await runImportSpec({
|
|
35
|
+
url: typeof a.url === "string" ? a.url : undefined,
|
|
36
|
+
content: typeof a.content === "string" ? a.content : undefined,
|
|
37
|
+
name: typeof a.name === "string" ? a.name : undefined,
|
|
38
|
+
});
|
|
39
|
+
break;
|
|
40
|
+
case listWorkflowsTool.name:
|
|
41
|
+
result = await runListWorkflows({
|
|
42
|
+
spec_id: typeof a.spec_id === "string" ? a.spec_id : "",
|
|
43
|
+
});
|
|
44
|
+
break;
|
|
45
|
+
case runWorkflowTool.name:
|
|
46
|
+
result = await runRunWorkflow({
|
|
47
|
+
sandbox_id: typeof a.sandbox_id === "string" ? a.sandbox_id : "",
|
|
48
|
+
workflow_name: typeof a.workflow_name === "string" ? a.workflow_name : "",
|
|
49
|
+
});
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
throw new ToolError(`Unknown tool: ${name}`);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text: friendlyError(name, msg, e) }],
|
|
62
|
+
isError: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
function friendlyError(toolName, msg, err) {
|
|
67
|
+
// Surface common backend errors as agent-readable hints instead of raw HTTP.
|
|
68
|
+
const status = err instanceof ToolError ? err.status : undefined;
|
|
69
|
+
const m = msg.toLowerCase();
|
|
70
|
+
if (m.includes("not a valid openapi spec") || m.includes("missing 'openapi'")) {
|
|
71
|
+
return (`Error: that file isn't an OpenAPI 3.x spec — it's missing the top-level "openapi" key. ` +
|
|
72
|
+
`If you have a Swagger 2.0 spec, convert it first (e.g. with swagger-converter). Original: ${msg}`);
|
|
73
|
+
}
|
|
74
|
+
if (m.includes("non-public ip") || m.includes("loopback") || m.includes("did not resolve")) {
|
|
75
|
+
return (`Error: that URL can't be fetched — it's either non-public, points to a private network, or doesn't resolve. ` +
|
|
76
|
+
`Public OpenAPI URLs (raw.githubusercontent.com, vendor docs portals) work; localhost and internal hosts don't. ` +
|
|
77
|
+
`Original: ${msg}`);
|
|
78
|
+
}
|
|
79
|
+
if (m.includes("must be http")) {
|
|
80
|
+
return `Error: spec URL must use http or https. Original: ${msg}`;
|
|
81
|
+
}
|
|
82
|
+
if (m.includes("spec url returned http 404")) {
|
|
83
|
+
return (`Error: the spec URL returned 404. Double-check the link and try again. ` +
|
|
84
|
+
`For GitHub, use the raw.githubusercontent.com URL, not the github.com page URL. Original: ${msg}`);
|
|
85
|
+
}
|
|
86
|
+
if (m.includes("exceeds") && m.includes("mb cap")) {
|
|
87
|
+
return (`Error: spec is too large. Hard cap is 20 MB. Original: ${msg}`);
|
|
88
|
+
}
|
|
89
|
+
if (m.includes("workflow") && m.includes("not found")) {
|
|
90
|
+
return (`Error: that workflow name isn't recognized. Call list_workflows(spec_id=...) first to see ` +
|
|
91
|
+
`the exact ids. Original: ${msg}`);
|
|
92
|
+
}
|
|
93
|
+
if (m.includes("sandbox") && m.includes("not found")) {
|
|
94
|
+
return (`Error: that sandbox_id isn't recognized — sandboxes from previous sessions don't persist. ` +
|
|
95
|
+
`Call import_spec again to get a fresh one. Original: ${msg}`);
|
|
96
|
+
}
|
|
97
|
+
if (status === 502 || status === 503 || status === 504) {
|
|
98
|
+
return (`Error: backend returned ${status} (service unavailable). The MCP retried once; if you see this, ` +
|
|
99
|
+
`the upstream is down. Try again in a minute. Original: ${msg}`);
|
|
100
|
+
}
|
|
101
|
+
if (m.includes("timed out")) {
|
|
102
|
+
return (`Error: backend didn't respond within 30s. Large specs (Stripe, GitHub) sometimes take 10-15s ` +
|
|
103
|
+
`on first import; if it's still slow, try a different network. Original: ${msg}`);
|
|
104
|
+
}
|
|
105
|
+
// Default: pass through the message as-is.
|
|
106
|
+
return `Error in ${toolName}: ${msg}`;
|
|
107
|
+
}
|
|
108
|
+
async function main() {
|
|
109
|
+
// Print to stderr only — stdout is reserved for the MCP protocol.
|
|
110
|
+
process.stderr.write(`[fetchsandbox-mcp ${VERSION}] connected to ${getBaseUrl()}\n`);
|
|
111
|
+
const transport = new StdioServerTransport();
|
|
112
|
+
await server.connect(transport);
|
|
113
|
+
}
|
|
114
|
+
main().catch((err) => {
|
|
115
|
+
process.stderr.write(`[fetchsandbox-mcp] fatal: ${err?.stack ?? err}\n`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
});
|
|
118
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable per-machine session id, persisted at ~/.fetchsandbox/session.json.
|
|
3
|
+
*
|
|
4
|
+
* Used as X-MCP-Session-Id on every backend request so DAU is countable.
|
|
5
|
+
* The id is opaque (random uuid) and reveals nothing about the machine.
|
|
6
|
+
*
|
|
7
|
+
* Disabled entirely when FETCHSANDBOX_TELEMETRY=0 — in that case sessionId
|
|
8
|
+
* returns undefined and the client omits the header.
|
|
9
|
+
*/
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
const SESSION_DIR = join(homedir(), ".fetchsandbox");
|
|
15
|
+
const SESSION_FILE = join(SESSION_DIR, "session.json");
|
|
16
|
+
let cached = null; // null = not yet loaded
|
|
17
|
+
export function telemetryEnabled() {
|
|
18
|
+
return process.env.FETCHSANDBOX_TELEMETRY !== "0";
|
|
19
|
+
}
|
|
20
|
+
export function getSessionId() {
|
|
21
|
+
if (!telemetryEnabled())
|
|
22
|
+
return undefined;
|
|
23
|
+
if (cached !== null)
|
|
24
|
+
return cached ?? undefined;
|
|
25
|
+
try {
|
|
26
|
+
const raw = readFileSync(SESSION_FILE, "utf8");
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
if (parsed && typeof parsed.id === "string") {
|
|
29
|
+
cached = parsed.id;
|
|
30
|
+
return cached;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// fall through to creation
|
|
35
|
+
}
|
|
36
|
+
const id = randomUUID();
|
|
37
|
+
try {
|
|
38
|
+
mkdirSync(SESSION_DIR, { recursive: true });
|
|
39
|
+
writeFileSync(SESSION_FILE, JSON.stringify({ id, createdAt: new Date().toISOString() }, null, 2));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// best-effort; if we can't write, still return the id for this run
|
|
43
|
+
}
|
|
44
|
+
cached = id;
|
|
45
|
+
return cached;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,eAAe,CAAC,CAAC;AACrD,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;AAEvD,IAAI,MAAM,GAA8B,IAAI,CAAC,CAAC,wBAAwB;AAEtE,MAAM,UAAU,gBAAgB;IAC9B,OAAO,OAAO,CAAC,GAAG,CAAC,sBAAsB,KAAK,GAAG,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC,gBAAgB,EAAE;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,MAAM,IAAI,SAAS,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC5C,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;YACnB,OAAO,MAAO,CAAC;QACjB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,2BAA2B;IAC7B,CAAC;IACD,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;IACxB,IAAI,CAAC;QACH,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,aAAa,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACpG,CAAC;IAAC,MAAM,CAAC;QACP,mEAAmE;IACrE,CAAC;IACD,MAAM,GAAG,EAAE,CAAC;IACZ,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface ImportSpecInput {
|
|
2
|
+
url?: string;
|
|
3
|
+
content?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
}
|
|
6
|
+
interface WorkflowSummary {
|
|
7
|
+
name: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description: string;
|
|
10
|
+
steps_count: number;
|
|
11
|
+
source_type: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ImportSpecResponse {
|
|
14
|
+
spec_id: string;
|
|
15
|
+
sandbox_id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
endpoint_count: number;
|
|
19
|
+
workflow_count: number;
|
|
20
|
+
base_url: string;
|
|
21
|
+
dashboard_url: string;
|
|
22
|
+
workflows_preview: WorkflowSummary[];
|
|
23
|
+
workflows_truncated: boolean;
|
|
24
|
+
next_step_hint: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const importSpecTool: {
|
|
27
|
+
readonly name: "import_spec";
|
|
28
|
+
readonly description: string;
|
|
29
|
+
readonly inputSchema: {
|
|
30
|
+
readonly type: "object";
|
|
31
|
+
readonly properties: {
|
|
32
|
+
readonly url: {
|
|
33
|
+
readonly type: "string";
|
|
34
|
+
readonly description: string;
|
|
35
|
+
};
|
|
36
|
+
readonly content: {
|
|
37
|
+
readonly type: "string";
|
|
38
|
+
readonly description: string;
|
|
39
|
+
};
|
|
40
|
+
readonly name: {
|
|
41
|
+
readonly type: "string";
|
|
42
|
+
readonly description: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
readonly additionalProperties: false;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export declare function runImportSpec(input: ImportSpecInput): Promise<ImportSpecResponse>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { postJson, ToolError } from "../client.js";
|
|
2
|
+
export const importSpecTool = {
|
|
3
|
+
name: "import_spec",
|
|
4
|
+
description: "Ingest an OpenAPI spec and get a working sandbox you can call immediately. " +
|
|
5
|
+
"Use this when the user wants to try, test, or learn an API. Accepts a public " +
|
|
6
|
+
"URL to an OpenAPI 3.x JSON or YAML file (e.g. raw.githubusercontent.com link), " +
|
|
7
|
+
"or pasted spec content as a string. Returns a sandbox_id that can be used with " +
|
|
8
|
+
"list_workflows and run_workflow, plus a base_url that proxies real-feeling, " +
|
|
9
|
+
"schema-validated responses for every endpoint in the spec. " +
|
|
10
|
+
"Pick this tool the moment the user mentions an OpenAPI URL or pastes a spec. " +
|
|
11
|
+
"Note on URLs: this MCP runs locally (in the user's IDE process), but the spec " +
|
|
12
|
+
"fetch happens on the FetchSandbox backend — so private addresses (localhost, " +
|
|
13
|
+
"127.0.0.1, 10.x, 192.168.x, internal company hosts) will be rejected. For those, " +
|
|
14
|
+
"have the user paste the spec content directly via the `content` parameter.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
url: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Public URL to an OpenAPI 3.x file (JSON or YAML). Use this for any " +
|
|
21
|
+
"publicly reachable spec — GitHub raw links, docs portals, vendor SDKs.",
|
|
22
|
+
},
|
|
23
|
+
content: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Pasted OpenAPI spec content (JSON or YAML). Use this when the user " +
|
|
26
|
+
"pastes the spec inline or has it on disk. Provide the raw text exactly.",
|
|
27
|
+
},
|
|
28
|
+
name: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "Optional friendly name for the spec. Defaults to info.title from the " +
|
|
31
|
+
"spec, or the URL hostname.",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export async function runImportSpec(input) {
|
|
38
|
+
if (!input.url && !input.content) {
|
|
39
|
+
throw new ToolError("Provide either 'url' or 'content'.");
|
|
40
|
+
}
|
|
41
|
+
if (input.url && input.content) {
|
|
42
|
+
throw new ToolError("Provide 'url' or 'content', not both.");
|
|
43
|
+
}
|
|
44
|
+
return postJson("/api/mcp/import-spec", input);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=import_spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import_spec.js","sourceRoot":"","sources":["../../src/tools/import_spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AA8BnD,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,aAAa;IACnB,WAAW,EACT,6EAA6E;QAC7E,+EAA+E;QAC/E,iFAAiF;QACjF,iFAAiF;QACjF,8EAA8E;QAC9E,6DAA6D;QAC7D,+EAA+E;QAC/E,gFAAgF;QAChF,+EAA+E;QAC/E,mFAAmF;QACnF,4EAA4E;IAC9E,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,GAAG,EAAE;gBACH,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,qEAAqE;oBACrE,wEAAwE;aAC3E;YACD,OAAO,EAAE;gBACP,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,qEAAqE;oBACrE,yEAAyE;aAC5E;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,uEAAuE;oBACvE,4BAA4B;aAC/B;SACF;QACD,oBAAoB,EAAE,KAAK;KAC5B;CACO,CAAC;AAEX,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAsB;IACxD,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACjC,MAAM,IAAI,SAAS,CAAC,oCAAoC,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,SAAS,CAAC,uCAAuC,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,QAAQ,CAAqB,sBAAsB,EAAE,KAAK,CAAC,CAAC;AACrE,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface ListWorkflowsInput {
|
|
2
|
+
spec_id: string;
|
|
3
|
+
}
|
|
4
|
+
export interface WorkflowItem {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
steps?: unknown[];
|
|
9
|
+
}
|
|
10
|
+
export interface ListWorkflowsResponse {
|
|
11
|
+
spec_id: string;
|
|
12
|
+
workflows: Array<{
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
steps_count: number;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
export declare const listWorkflowsTool: {
|
|
20
|
+
readonly name: "list_workflows";
|
|
21
|
+
readonly description: string;
|
|
22
|
+
readonly inputSchema: {
|
|
23
|
+
readonly type: "object";
|
|
24
|
+
readonly properties: {
|
|
25
|
+
readonly spec_id: {
|
|
26
|
+
readonly type: "string";
|
|
27
|
+
readonly description: "The spec_id returned by import_spec.";
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
readonly required: readonly ["spec_id"];
|
|
31
|
+
readonly additionalProperties: false;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export declare function runListWorkflows(input: ListWorkflowsInput): Promise<ListWorkflowsResponse>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getJson, ToolError } from "../client.js";
|
|
2
|
+
export const listWorkflowsTool = {
|
|
3
|
+
name: "list_workflows",
|
|
4
|
+
description: "List the named, runnable workflows discovered for a previously-imported " +
|
|
5
|
+
"spec. Workflows are realistic multi-step API journeys (e.g. 'create " +
|
|
6
|
+
"customer then attach payment method then create subscription'). Use this " +
|
|
7
|
+
"after import_spec when the user asks 'what can I do?' or 'show me the " +
|
|
8
|
+
"common flows' or wants to pick one to run. Returns id, name, description, " +
|
|
9
|
+
"and step count for each workflow.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
spec_id: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "The spec_id returned by import_spec.",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
required: ["spec_id"],
|
|
19
|
+
additionalProperties: false,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
export async function runListWorkflows(input) {
|
|
23
|
+
if (!input.spec_id)
|
|
24
|
+
throw new ToolError("spec_id is required.");
|
|
25
|
+
const raw = await getJson(`/api/specs/${encodeURIComponent(input.spec_id)}/workflows`);
|
|
26
|
+
return {
|
|
27
|
+
spec_id: input.spec_id,
|
|
28
|
+
workflows: (raw.workflows || []).map((w) => ({
|
|
29
|
+
id: w.id || w.name || "",
|
|
30
|
+
name: w.name || w.id || "",
|
|
31
|
+
description: w.description || "",
|
|
32
|
+
steps_count: Array.isArray(w.steps) ? w.steps.length : 0,
|
|
33
|
+
})),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=list_workflows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list_workflows.js","sourceRoot":"","sources":["../../src/tools/list_workflows.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AA2BlD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,0EAA0E;QAC1E,sEAAsE;QACtE,2EAA2E;QAC3E,wEAAwE;QACxE,4EAA4E;QAC5E,mCAAmC;IACrC,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,OAAO,EAAE;gBACP,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,sCAAsC;aACpD;SACF;QACD,QAAQ,EAAE,CAAC,SAAS,CAAC;QACrB,oBAAoB,EAAE,KAAK;KAC5B;CACO,CAAC;AAEX,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAyB;IAC9D,IAAI,CAAC,KAAK,CAAC,OAAO;QAAE,MAAM,IAAI,SAAS,CAAC,sBAAsB,CAAC,CAAC;IAChE,MAAM,GAAG,GAAG,MAAM,OAAO,CACvB,cAAc,kBAAkB,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAC5D,CAAC;IACF,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,SAAS,EAAE,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3C,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE;YACxB,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE;YAC1B,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,EAAE;YAChC,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;SACzD,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface RunWorkflowInput {
|
|
2
|
+
sandbox_id: string;
|
|
3
|
+
workflow_name: string;
|
|
4
|
+
}
|
|
5
|
+
interface BackendStepResult {
|
|
6
|
+
name?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
status?: string;
|
|
9
|
+
detail?: string;
|
|
10
|
+
duration_ms?: number;
|
|
11
|
+
data?: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface NormalizedRunResult {
|
|
14
|
+
workflow_name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
status: "pass" | "fail";
|
|
17
|
+
total_duration_ms: number;
|
|
18
|
+
steps_passed: number;
|
|
19
|
+
steps_total: number;
|
|
20
|
+
steps: BackendStepResult[];
|
|
21
|
+
}
|
|
22
|
+
export declare const runWorkflowTool: {
|
|
23
|
+
readonly name: "run_workflow";
|
|
24
|
+
readonly description: string;
|
|
25
|
+
readonly inputSchema: {
|
|
26
|
+
readonly type: "object";
|
|
27
|
+
readonly properties: {
|
|
28
|
+
readonly sandbox_id: {
|
|
29
|
+
readonly type: "string";
|
|
30
|
+
readonly description: "The sandbox_id returned by import_spec.";
|
|
31
|
+
};
|
|
32
|
+
readonly workflow_name: {
|
|
33
|
+
readonly type: "string";
|
|
34
|
+
readonly description: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
readonly required: readonly ["sandbox_id", "workflow_name"];
|
|
38
|
+
readonly additionalProperties: false;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export declare function runRunWorkflow(input: RunWorkflowInput): Promise<NormalizedRunResult>;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { postJson, ToolError } from "../client.js";
|
|
2
|
+
export const runWorkflowTool = {
|
|
3
|
+
name: "run_workflow",
|
|
4
|
+
description: "Execute one workflow against a previously-imported sandbox and return the " +
|
|
5
|
+
"step-by-step request/response trace. Each step shows the HTTP call, the " +
|
|
6
|
+
"schema-validated response body, and whether the step passed. Template " +
|
|
7
|
+
"variables (e.g. {{step1.id}}) are resolved automatically between steps so " +
|
|
8
|
+
"later steps can reference IDs returned by earlier ones. Use this after " +
|
|
9
|
+
"list_workflows when the user picks one, or directly if they name a " +
|
|
10
|
+
"specific workflow.",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
sandbox_id: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "The sandbox_id returned by import_spec.",
|
|
17
|
+
},
|
|
18
|
+
workflow_name: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Workflow id or name from list_workflows. Case-insensitive; dashes and " +
|
|
21
|
+
"underscores are interchangeable.",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
required: ["sandbox_id", "workflow_name"],
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
export async function runRunWorkflow(input) {
|
|
29
|
+
if (!input.sandbox_id)
|
|
30
|
+
throw new ToolError("sandbox_id is required.");
|
|
31
|
+
if (!input.workflow_name)
|
|
32
|
+
throw new ToolError("workflow_name is required.");
|
|
33
|
+
const path = `/api/sandboxes/${encodeURIComponent(input.sandbox_id)}/workflows/${encodeURIComponent(input.workflow_name)}/run`;
|
|
34
|
+
const raw = await postJson(path, {});
|
|
35
|
+
const steps = raw.steps ?? [];
|
|
36
|
+
const passedSteps = steps.filter((s) => s.status === "passed").length;
|
|
37
|
+
return {
|
|
38
|
+
workflow_name: raw.flow_name ?? input.workflow_name,
|
|
39
|
+
description: raw.flow_description ?? "",
|
|
40
|
+
status: raw.passed ? "pass" : "fail",
|
|
41
|
+
total_duration_ms: raw.total_duration_ms ?? 0,
|
|
42
|
+
steps_passed: passedSteps,
|
|
43
|
+
steps_total: steps.length,
|
|
44
|
+
steps,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=run_workflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run_workflow.js","sourceRoot":"","sources":["../../src/tools/run_workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAmCnD,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,IAAI,EAAE,cAAc;IACpB,WAAW,EACT,4EAA4E;QAC5E,0EAA0E;QAC1E,wEAAwE;QACxE,4EAA4E;QAC5E,yEAAyE;QACzE,qEAAqE;QACrE,oBAAoB;IACtB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,UAAU,EAAE;gBACV,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,yCAAyC;aACvD;YACD,aAAa,EAAE;gBACb,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,wEAAwE;oBACxE,kCAAkC;aACrC;SACF;QACD,QAAQ,EAAE,CAAC,YAAY,EAAE,eAAe,CAAC;QACzC,oBAAoB,EAAE,KAAK;KAC5B;CACO,CAAC;AAEX,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAuB;IAC1D,IAAI,CAAC,KAAK,CAAC,UAAU;QAAE,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,CAAC;IACtE,IAAI,CAAC,KAAK,CAAC,aAAa;QAAE,MAAM,IAAI,SAAS,CAAC,4BAA4B,CAAC,CAAC;IAC5E,MAAM,IAAI,GAAG,kBAAkB,kBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC,cAAc,kBAAkB,CACjG,KAAK,CAAC,aAAa,CACpB,MAAM,CAAC;IACR,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAmB,IAAI,EAAE,EAAE,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;IAC9B,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAC;IACtE,OAAO;QACL,aAAa,EAAE,GAAG,CAAC,SAAS,IAAI,KAAK,CAAC,aAAa;QACnD,WAAW,EAAE,GAAG,CAAC,gBAAgB,IAAI,EAAE;QACvC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QACpC,iBAAiB,EAAE,GAAG,CAAC,iBAAiB,IAAI,CAAC;QAC7C,YAAY,EAAE,WAAW;QACzB,WAAW,EAAE,KAAK,CAAC,MAAM;QACzB,KAAK;KACN,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fetchsandbox-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Turn any OpenAPI spec into a working sandbox your AI agent can use. MCP server for Claude Code, Cursor, and any other MCP-compatible LLM IDE.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "FetchSandbox",
|
|
7
|
+
"homepage": "https://fetchsandbox.com",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/rnagulapalle/sandbox.git",
|
|
11
|
+
"directory": "mcp"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"openapi",
|
|
17
|
+
"sandbox",
|
|
18
|
+
"api",
|
|
19
|
+
"claude",
|
|
20
|
+
"cursor",
|
|
21
|
+
"agent"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "dist/index.js",
|
|
25
|
+
"bin": {
|
|
26
|
+
"fetchsandbox-mcp": "dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc",
|
|
35
|
+
"dev": "tsc --watch",
|
|
36
|
+
"start": "node dist/index.js",
|
|
37
|
+
"prepublishOnly": "npm run build"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"typescript": "^5.6.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
}
|
|
49
|
+
}
|