tinyfish-mcp-lite 0.0.0-stage → 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TinyFish
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 CHANGED
@@ -1,3 +1,355 @@
1
- # Temporary Holding Version
1
+ # tinyfish-mcp-lite
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/tinyfish-mcp-lite)](https://www.npmjs.com/package/tinyfish-mcp-lite)
4
+
5
+ > **⚠️ You are on `tinyfish-mcp-lite`** — a fork that exposes **only the two
6
+ > free hosted tools** (`search`, `fetch_content`) and hides the 26 paid ones,
7
+ > so they neither burn client context tokens (~72% smaller `tools/list`) nor
8
+ > can ever be invoked through this proxy. **Start here:
9
+ > [Using this fork](#using-this-fork-tinyfish-mcp-lite)** · fork guide:
10
+ > [FORK.md](FORK.md). Everything below this section is inherited from
11
+ > upstream and describes the unfiltered behavior unless noted.
12
+
13
+ TinyFish MCP server: web search, page fetching and extraction, browser
14
+ automation and page monitoring for Claude, Cursor, VS Code and any MCP
15
+ client. Search and Fetch are free.
16
+
17
+ > **Using Claude or ChatGPT in the browser or desktop app?** Skip the setup and add the official
18
+ > TinyFish plugin for **[Claude](https://claude.ai/directory/tinyfish)** or **[ChatGPT](https://chatgpt.com/plugins/plugin_asdk_app_695325bae7348191b58ae9349a963d22)** in one click. It bundles
19
+ > these tools with ready-made skills and safety rules.
20
+
21
+ This package is a transparent reverse proxy that exposes a local
22
+ Streamable-HTTP MCP endpoint at `http://127.0.0.1:3711/mcp` and forwards every
23
+ request to the hosted TinyFish MCP server at `https://agent.tinyfish.ai/mcp`.
24
+
25
+ The proxy defines no tools and no schemas of its own. `tools/list`,
26
+ `tools/call`, `resources/*`, errors, and the `run_web_automation` SSE progress
27
+ stream all come from the hosted server: streaming (SSE) responses are relayed
28
+ byte-verbatim, and non-streaming JSON responses are relayed content-identical
29
+ (parsed and re-serialized, deep-equal to upstream). What the hosted server
30
+ says is what your client sees.
31
+
32
+ ## Using this fork (tinyfish-mcp-lite)
33
+
34
+ Everything below about install, API key, and client configuration applies to
35
+ this fork too — with exactly these differences:
36
+
37
+ - **Only the free tools are exposed.** `tools/list` returns exactly
38
+ `search` and `fetch_content` (their schemas verbatim from upstream). All
39
+ other tools are hidden.
40
+ - **Paid tools cannot be invoked.** A `tools/call` naming a hidden tool is
41
+ answered locally with the same error the hosted server itself produces for
42
+ an unknown tool (HTTP 400, JSON-RPC `-32602 "Unknown tool: <name>"`) — the
43
+ request never leaves your machine, so it can never bill. Notification-
44
+ shaped calls and JSON-RPC batches carrying hidden tools are blocked the
45
+ same way.
46
+ - **One extra env var, `TINYFISH_TOOLS`** (default `search,fetch_content`):
47
+ a comma-separated allowlist. Set `TINYFISH_TOOLS=*` to restore the full
48
+ upstream catalog (28 tools, unfiltered). Invalid values abort startup.
49
+ - **A second startup log line** states the active filter, e.g.
50
+ `tinyfish-mcp [info] tool filter: exposing [search, fetch_content] — set TINYFISH_TOOLS=* to expose every upstream tool`.
51
+
52
+ ### Install (this fork)
53
+
54
+ The fork ships on npm as **`tinyfish-mcp-lite`** (requires Node.js >= 22).
55
+ Two transports are available:
56
+
57
+ **stdio — one command, the client manages the process.** Most clients that
58
+ launch MCP servers as commands (Claude Code with a `command` entry, Cursor,
59
+ Zed, many launcher UIs) want this:
60
+
61
+ ```sh
62
+ # the client spawns it; add TINYFISH_API_KEY to the command's environment
63
+ npx -y tinyfish-mcp-lite --stdio
64
+
65
+ # or with the global install (`npm i -g tinyfish-mcp-lite`):
66
+ tinyfish-mcp --stdio
67
+ ```
68
+
69
+ In a client config that takes a command + env vars, that is:
70
+
71
+ ```json
72
+ {
73
+ "command": "npx",
74
+ "args": ["-y", "tinyfish-mcp-lite", "--stdio"],
75
+ "env": { "TINYFISH_API_KEY": "tf_..." }
76
+ }
77
+ ```
78
+
79
+ **HTTP — one shared server for several clients.** Start it once (terminal,
80
+ systemd, pm2) and point every client at the same URL:
81
+
82
+ ```sh
83
+ TINYFISH_API_KEY=tf_... npx tinyfish-mcp-lite # no --stdio flag
84
+ ```
85
+
86
+ ```sh
87
+ # or from a clone
88
+ git clone https://github.com/ByronFinn/tinyfish-mcp-lite.git
89
+ cd tinyfish-mcp-lite
90
+ npm ci && npm run build
91
+ TINYFISH_API_KEY=tf_... node dist/index.js # HTTP
92
+ # TINYFISH_API_KEY=tf_... node dist/index.js --stdio # stdio
93
+ ```
94
+
95
+ Both transports expose the same filtered proxy; stdio just has no port and
96
+ no manual startup — closing the client's pipe shuts the process down.
97
+
98
+ > **Note on git-based installs** (`npm i -g github:ByronFinn/tinyfish-mcp-lite`):
99
+ > npm ≥ 11.17 with the install-script policy enabled (an `allow-scripts`
100
+ > entry in your `~/.npmrc`) rejects them with `EALLOWSCRIPTS` — the policy
101
+ > leaks into the inner git-dep build via the environment. Use the npm or
102
+ > clone routes instead; registry installs (`npx tinyfish-mcp-lite`) are
103
+ > unaffected.
104
+
105
+ For the HTTP transport, point your MCP client at `http://127.0.0.1:3711/mcp`
106
+ as described in [Client configuration](#client-configuration) below.
107
+
108
+ Why a fork at all: the hosted server's paid tools (automation, browsers,
109
+ monitors…) are unusable without wallet funds, but their schemas still land
110
+ in every client session. If you only ever use the free search/fetch, this
111
+ fork keeps the client context lean and makes accidental paid calls
112
+ impossible. Fork maintenance and upstream-sync details: [FORK.md](FORK.md).
113
+
114
+ ## Tools
115
+
116
+ The hosted server exposes 28 tools. You can use them to search the web, fetch and
117
+ extract pages, automate browsers, run remote Chrome sessions (CDP), keep
118
+ signed-in Browser Context Profiles and schedule page and topic Monitors.
119
+ (In this fork only the `Search & Fetch` row is advertised — see
120
+ [Using this fork](#using-this-fork-tinyfish-mcp-lite).)
121
+
122
+ | Category | Tools |
123
+ |---|---|
124
+ | Search & Fetch | `search`, `fetch_content` |
125
+ | Web automation | `run_web_automation`, `run_web_automation_async`, `get_run`, `list_runs`, `cancel_run`, `batch_status`, `batch_cancel` |
126
+ | Browser sessions | `create_browser_session`, `list_browser_sessions`, `close_browser_session` |
127
+ | Browser Context Profiles | `create_profile`, `list_profiles`, `start_profile_setup_session`, `save_profile_setup_session`, `cancel_profile_setup_session` |
128
+ | Monitors | `create_monitor`, `get_monitor`, `list_monitors`, `run_monitor`, `pause_monitor`, `resume_monitor`, `cancel_monitor` |
129
+ | Account & usage | `get_wallet`, `get_search_usage`, `list_fetch_usage`, `guide_next_step` |
130
+
131
+ - [Tool reference](https://github.com/tinyfish-io/tinyfish-mcp-server/blob/main/docs/tools.md): every tool, verbatim, with parameters
132
+ - [Examples](https://github.com/tinyfish-io/tinyfish-mcp-server/blob/main/docs/examples.md): common workflows and prompts
133
+ - [MCP Integration guide](https://docs.tinyfish.ai/mcp-integration): client setup, auth, rates, troubleshooting
134
+ - Full documentation: [docs.tinyfish.ai](https://docs.tinyfish.ai)
135
+
136
+ ## Use the hosted server first
137
+
138
+ If your MCP client supports remote Streamable-HTTP servers (Claude Code,
139
+ Claude Desktop connectors, Cursor, VS Code, and most modern clients do),
140
+ connect it **directly** to the hosted server — no install, no local process:
141
+
142
+ ```text
143
+ https://agent.tinyfish.ai/mcp
144
+ ```
145
+
146
+ Use this package instead when:
147
+
148
+ - your client only talks to local MCP servers, or
149
+ - you want to authenticate with a **TinyFish API key** from your environment
150
+ instead of the hosted server's OAuth flow (CI, headless machines, scripts).
151
+
152
+ ## Install
153
+
154
+ ```sh
155
+ npm install -g @tiny-fish/mcp
156
+ ```
157
+
158
+ Or run it without installing:
159
+
160
+ ```sh
161
+ npx @tiny-fish/mcp
162
+ ```
163
+
164
+ Requires **Node.js >= 22**. (This is a deliberate deviation from the TinyFish
165
+ CLI's `>=24` requirement — nothing here needs Node 24.)
166
+
167
+ ## API key
168
+
169
+ The server reads `TINYFISH_API_KEY` from its environment at startup and sends
170
+ it upstream as `X-API-Key` on every call. Get a key at
171
+ [https://agent.tinyfish.ai](https://agent.tinyfish.ai).
172
+
173
+ ```sh
174
+ export TINYFISH_API_KEY=tf_...
175
+ tinyfish-mcp
176
+ ```
177
+
178
+ On success it prints one line to stderr:
179
+
180
+ ```text
181
+ tinyfish-mcp [info] listening on http://127.0.0.1:3711 — upstream https://agent.tinyfish.ai/mcp — v0.1.0
182
+ ```
183
+
184
+ The key is never logged and never echoed back to clients.
185
+
186
+ ## Client configuration
187
+
188
+ Start `tinyfish-mcp` (e.g. in a terminal, or under your process manager of
189
+ choice), then point your client at `http://127.0.0.1:3711/mcp`.
190
+
191
+ ### Claude Code
192
+
193
+ ```sh
194
+ claude mcp add --transport http tinyfish http://127.0.0.1:3711/mcp
195
+ ```
196
+
197
+ ### Claude Desktop
198
+
199
+ Settings → Connectors → Add custom connector, with URL
200
+ `http://127.0.0.1:3711/mcp`. On versions whose
201
+ `claude_desktop_config.json` supports URL-based servers:
202
+
203
+ ```json
204
+ {
205
+ "mcpServers": {
206
+ "tinyfish": {
207
+ "url": "http://127.0.0.1:3711/mcp"
208
+ }
209
+ }
210
+ }
211
+ ```
212
+
213
+ Note: Claude Desktop can also use the hosted `https://agent.tinyfish.ai/mcp`
214
+ directly as a custom connector — prefer that unless you need API-key auth.
215
+
216
+ ### Cursor
217
+
218
+ `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project):
219
+
220
+ ```json
221
+ {
222
+ "mcpServers": {
223
+ "tinyfish": {
224
+ "url": "http://127.0.0.1:3711/mcp"
225
+ }
226
+ }
227
+ }
228
+ ```
229
+
230
+ ### VS Code
231
+
232
+ `.vscode/mcp.json`:
233
+
234
+ ```json
235
+ {
236
+ "servers": {
237
+ "tinyfish": {
238
+ "type": "http",
239
+ "url": "http://127.0.0.1:3711/mcp"
240
+ }
241
+ }
242
+ }
243
+ ```
244
+
245
+ ### Any other client
246
+
247
+ Configure a Streamable-HTTP (remote/URL) MCP server with:
248
+
249
+ ```json
250
+ { "url": "http://127.0.0.1:3711/mcp" }
251
+ ```
252
+
253
+ The endpoint accepts `POST /mcp` only (matching the hosted server, which has
254
+ no GET SSE channel and no DELETE session teardown). `GET /healthz` returns
255
+ `200 ok` for debugging.
256
+
257
+ ## Environment variables
258
+
259
+ | Variable | Default | Description |
260
+ |---|---|---|
261
+ | `TINYFISH_API_KEY` | (required) | TinyFish API key, sent upstream as `X-API-Key`. The server refuses to start without it. |
262
+ | `PORT` | `3711` | Local listen port (integer 1-65535). No auto-increment: if the port is busy the server exits with an error. |
263
+ | `TINYFISH_UPSTREAM_URL` | `https://agent.tinyfish.ai/mcp` | Upstream MCP URL. Must be `https:`; `http:` is allowed only for `127.0.0.1`/`localhost` (local testing). |
264
+ | `TINYFISH_TOOLS` | `search,fetch_content` | **Fork only:** comma-separated allowlist of upstream tools to expose; `*` disables filtering and exposes the full upstream catalog. Invalid values abort startup. |
265
+
266
+ The proxy also sends attribution headers on every upstream call:
267
+ `X-TF-Request-Origin: tinyfish-mcp`, `X-TF-Client-Name: tinyfish-mcp`, and
268
+ `X-TF-Client-Version: <package version>`.
269
+
270
+ ## Security & trust model
271
+
272
+ This is a loopback-only server with a deliberate, documented trust boundary:
273
+
274
+ - **Loopback bind, always.** The server binds the `127.0.0.1` literal and this
275
+ is not configurable — it can never listen on `0.0.0.0` or a LAN interface.
276
+ - **Origin validation.** Requests carrying an `Origin` header are rejected
277
+ with `403` unless the origin is `http(s)://127.0.0.1` or
278
+ `http(s)://localhost` (any port). This blocks the DNS-rebinding attack the
279
+ MCP spec calls out for local HTTP servers. Requests without an `Origin`
280
+ header (curl, MCP SDKs, inspectors) are allowed.
281
+ - **Server-holds-key.** The process reads `TINYFISH_API_KEY` from its env;
282
+ clients send no credential on the local hop. This is the simplest model,
283
+ but it means **any local process that can reach `127.0.0.1:<port>` can use
284
+ your key and drive automations** (which can spend TinyFish credits). The
285
+ loopback bind and Origin check are the mitigations; treat the port as a
286
+ local trust boundary on a machine you trust.
287
+
288
+ ## Troubleshooting
289
+
290
+ **"Port 3711 is already in use"** — another process holds the port. Stop it,
291
+ or set `PORT` to a free port and update your client config to match.
292
+
293
+ **HTTP 502 with JSON-RPC error `-32001`** ("Upstream rejected the request …
294
+ check that TINYFISH_API_KEY is set to a valid TinyFish API key") — the hosted
295
+ server rejected your key. Verify `TINYFISH_API_KEY` is set in the environment
296
+ of the `tinyfish-mcp` process (not just your shell) and that the key is valid
297
+ at [https://agent.tinyfish.ai](https://agent.tinyfish.ai). The error's `data`
298
+ carries the upstream status and (truncated) body for diagnosis.
299
+
300
+ **HTTP 502 with JSON-RPC error `-32000`** ("cannot reach …") — the upstream
301
+ server is unreachable: check your network/proxy/VPN; the hosted server may
302
+ also be temporarily down. If you overrode `TINYFISH_UPSTREAM_URL`, check it.
303
+ If a streamed `run_web_automation` call fails mid-stream you get the same
304
+ `-32000` as the final SSE frame, with a "the run may still be executing"
305
+ warning and the `runId` when known — check the run's status instead of
306
+ retrying blindly.
307
+
308
+ **Batch requests don't work** — MCP forbids JSON-RPC batching and the hosted
309
+ server does not support batch arrays. The proxy forwards a batch as-is and
310
+ the upstream answers its own error; send one JSON-RPC message per request.
311
+
312
+ **Claude/other client can't connect** — make sure `tinyfish-mcp` is actually
313
+ running (it's a standalone server; clients do not launch it) and that
314
+ `GET http://127.0.0.1:3711/healthz` answers `ok`.
315
+
316
+ ## Development
317
+
318
+ ```sh
319
+ npm ci
320
+ npm run build # tsc → dist/, marks dist/index.js executable
321
+ npm test # unit tests (offline, mock upstream)
322
+ npm run test:watch # unit tests in watch mode
323
+ npm run lint # eslint over src/ tests/
324
+ npm run format # prettier
325
+ npm run type-check # tsc --noEmit over the whole tree
326
+ ```
327
+
328
+ Layout: `src/core/` is the transport-agnostic proxy core (upstream client,
329
+ session bridge, SSE relay, error shaping); `src/http/` is the thin HTTP
330
+ adapter (routing, Origin check); `tests/` holds the unit suites plus
331
+ `tests/helpers/mock-upstream.ts`, a mock of the hosted endpoint that the unit
332
+ tests run against entirely offline.
333
+
334
+ Integration tests hit the **real** hosted upstream and are gated: without
335
+ `TINYFISH_API_KEY` they skip with a printed notice.
336
+
337
+ ```sh
338
+ TINYFISH_API_KEY=... npm run test:integration
339
+ ```
340
+
341
+ Set `TINYFISH_UPSTREAM_URL` to point them at a sandbox deployment instead of
342
+ production. Note the `run_web_automation` integration test executes a real
343
+ automation and spends credits.
344
+
345
+ ## Maintenance
346
+
347
+ Maintained by [@Zechereh](https://github.com/Zechereh). Bugs and feature
348
+ requests: [GitHub issues](https://github.com/tinyfish-io/tinyfish-mcp-server/issues).
349
+ This fork is maintained at
350
+ [ByronFinn/tinyfish-mcp-lite](https://github.com/ByronFinn/tinyfish-mcp-lite);
351
+ fork-specific questions live in [FORK.md](FORK.md).
352
+
353
+ ## License
354
+
355
+ MIT
@@ -0,0 +1,17 @@
1
+ export declare const DEFAULT_PORT = 3711;
2
+ export declare const DEFAULT_UPSTREAM_URL = "https://agent.tinyfish.ai/mcp";
3
+ export declare const API_KEY_GUIDANCE = "Set TINYFISH_API_KEY \u2014 get a key at https://agent.tinyfish.ai";
4
+ export interface Config {
5
+ /** Never log this field. */
6
+ apiKey: string;
7
+ port: number;
8
+ upstreamUrl: string;
9
+ }
10
+ /** Thrown by parseConfig; `message` is the actionable stderr line(s) for the user. */
11
+ export declare class ConfigError extends Error {
12
+ }
13
+ /**
14
+ * Pure env → Config parser. Throws ConfigError with an actionable message on
15
+ * invalid input; performs no I/O and never touches process state.
16
+ */
17
+ export declare function parseConfig(env: Record<string, string | undefined>): Config;
package/dist/config.js ADDED
@@ -0,0 +1,77 @@
1
+ import { z } from "zod";
2
+ export const DEFAULT_PORT = 3711;
3
+ export const DEFAULT_UPSTREAM_URL = "https://agent.tinyfish.ai/mcp";
4
+ export const API_KEY_GUIDANCE = "Set TINYFISH_API_KEY — get a key at https://agent.tinyfish.ai";
5
+ /** Thrown by parseConfig; `message` is the actionable stderr line(s) for the user. */
6
+ export class ConfigError extends Error {
7
+ }
8
+ const envSchema = z.object({
9
+ TINYFISH_API_KEY: z.string({ error: API_KEY_GUIDANCE }).min(1, { error: API_KEY_GUIDANCE }),
10
+ PORT: z
11
+ .string()
12
+ .optional()
13
+ .transform((value, ctx) => {
14
+ if (value === undefined)
15
+ return DEFAULT_PORT;
16
+ const port = /^\d+$/.test(value) ? Number(value) : NaN;
17
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
18
+ ctx.addIssue({
19
+ code: "custom",
20
+ message: `Invalid PORT "${value}" — must be an integer between 1 and 65535`,
21
+ });
22
+ return z.NEVER;
23
+ }
24
+ return port;
25
+ }),
26
+ TINYFISH_UPSTREAM_URL: z
27
+ .string()
28
+ .optional()
29
+ .transform((value, ctx) => {
30
+ const raw = value ?? DEFAULT_UPSTREAM_URL;
31
+ let url;
32
+ // Error messages never echo the raw value: it may embed credentials or
33
+ // query-string secrets, and these messages go to stderr.
34
+ try {
35
+ url = new URL(raw);
36
+ }
37
+ catch {
38
+ ctx.addIssue({
39
+ code: "custom",
40
+ message: "Invalid TINYFISH_UPSTREAM_URL — must be an absolute URL",
41
+ });
42
+ return z.NEVER;
43
+ }
44
+ if (url.username !== "" || url.password !== "") {
45
+ ctx.addIssue({
46
+ code: "custom",
47
+ message: "Invalid TINYFISH_UPSTREAM_URL — URL credentials are not allowed",
48
+ });
49
+ return z.NEVER;
50
+ }
51
+ const isLoopback = url.hostname === "127.0.0.1" || url.hostname === "localhost";
52
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback)) {
53
+ ctx.addIssue({
54
+ code: "custom",
55
+ message: "Invalid TINYFISH_UPSTREAM_URL — scheme must be https " +
56
+ "(http is allowed only for 127.0.0.1/localhost)",
57
+ });
58
+ return z.NEVER;
59
+ }
60
+ return raw;
61
+ }),
62
+ });
63
+ /**
64
+ * Pure env → Config parser. Throws ConfigError with an actionable message on
65
+ * invalid input; performs no I/O and never touches process state.
66
+ */
67
+ export function parseConfig(env) {
68
+ const parsed = envSchema.safeParse(env);
69
+ if (!parsed.success) {
70
+ throw new ConfigError(parsed.error.issues.map((issue) => issue.message).join("\n"));
71
+ }
72
+ return {
73
+ apiKey: parsed.data.TINYFISH_API_KEY,
74
+ port: parsed.data.PORT,
75
+ upstreamUrl: parsed.data.TINYFISH_UPSTREAM_URL,
76
+ };
77
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Typed transport-level errors thrown by the proxy core, plus the only
3
+ * client-facing shaping functions: `toJsonRpcError` for pre-stream failures
4
+ * and `toStreamErrorFrame` for failures after an SSE relay started. Every
5
+ * adapter catch path routes through these two functions — no ad-hoc error
6
+ * bodies anywhere else, with exactly two deliberate exceptions: the adapter's
7
+ * ParseError reply (http/adapter.ts — built where the unparseable body is
8
+ * caught, since there is nothing to route), and the last-resort -32603
9
+ * backstop in http/index.ts's invokeSafely. Messages must never contain the
10
+ * API key (they describe network/protocol conditions only).
11
+ *
12
+ * HTTP status decision for locally shaped errors: the hosted server maps
13
+ * client-error JSON-RPC codes to HTTP 400 and everything else to 500. The
14
+ * proxy mirrors the 400 for client errors (-32700 ParseError) and picks
15
+ * **502 Bad Gateway** for the upstream-leg failures it shapes itself (-32000
16
+ * unreachable/stream-failed, -32001 auth rejection): the proxy is healthy,
17
+ * the upstream hop failed — distinguishing these from a genuine local proxy
18
+ * bug, which stays **500** with -32603 InternalError. Upstream-originated
19
+ * JSON-RPC errors are never shaped at all: they forward verbatim under
20
+ * upstream's own HTTP status.
21
+ */
22
+ /** JSON-RPC error codes used by locally shaped errors. */
23
+ export declare const JsonRpcErrorCodes: {
24
+ /** Upstream unreachable / upstream stream failed (server-side, HTTP 502). */
25
+ readonly UpstreamUnavailable: -32000;
26
+ /** Upstream rejected auth — check TINYFISH_API_KEY (HTTP 502). */
27
+ readonly UpstreamAuth: -32001;
28
+ /** Local proxy bug (HTTP 500). */
29
+ readonly InternalError: -32603;
30
+ /** Malformed client JSON (HTTP 400, id -1 — mirrors upstream). */
31
+ readonly ParseError: -32700;
32
+ };
33
+ /** Base class for all proxy-core errors (transport level, not JSON-RPC). */
34
+ export declare class ProxyCoreError extends Error {
35
+ constructor(message: string, options?: ErrorOptions);
36
+ }
37
+ /** The upstream server could not be reached (DNS, TLS, refused, reset). */
38
+ export declare class UpstreamUnreachableError extends ProxyCoreError {
39
+ /** Upstream "host[:port]" when known — used in the client-facing message. */
40
+ host?: string;
41
+ }
42
+ /**
43
+ * Upstream answered 401/403 with a body that is NOT a JSON-RPC message (a
44
+ * JSON-RPC error body, whatever its HTTP status, forwards verbatim instead).
45
+ * Carries the upstream status and body text so the shaped
46
+ * client error can include them as diagnostics. The body text is truncated to
47
+ * ~2KB at construction; the Error message itself never includes it.
48
+ */
49
+ export declare class UpstreamAuthError extends ProxyCoreError {
50
+ readonly status: number;
51
+ /** Upstream response body text, truncated to AUTH_BODY_LIMIT chars. */
52
+ readonly bodyText: string;
53
+ constructor(status: number, bodyText: string, options?: ErrorOptions);
54
+ }
55
+ /**
56
+ * ~2KB cap on the upstream auth-failure body relayed in error data.
57
+ *
58
+ * Measured in UTF-16 code units (String.prototype.slice), not bytes: for
59
+ * multi-byte scripts the UTF-8 wire size can reach ~3× (≤ ~6KB) — bounded
60
+ * either way, which is all the "~2KB" contract promises. A slice boundary can
61
+ * split a surrogate pair; harmless, since Node's well-formed JSON.stringify
62
+ * escapes the lone surrogate and the response stays valid JSON.
63
+ */
64
+ export declare const AUTH_BODY_LIMIT = 2048;
65
+ /**
66
+ * Delivering a relayed SSE frame to the LOCAL client failed (the transport's
67
+ * onEvent callback rejected — e.g. the client socket died mid-write). This is
68
+ * a client-side condition, never an upstream one: it must not be logged or
69
+ * classified as "Upstream unreachable".
70
+ */
71
+ export declare class LocalWriteError extends ProxyCoreError {
72
+ }
73
+ /** An in-flight upstream request was aborted locally (session close / shutdown). */
74
+ export declare class UpstreamAbortedError extends ProxyCoreError {
75
+ }
76
+ /**
77
+ * Upstream answered with something the core cannot interpret (non-JSON body,
78
+ * SSE stream that ends without a final response frame, unexpected empty body).
79
+ */
80
+ export declare class UpstreamProtocolError extends ProxyCoreError {
81
+ /** Upstream HTTP status when one was received before the failure. */
82
+ readonly status?: number;
83
+ constructor(message: string, status?: number, options?: ErrorOptions);
84
+ }
85
+ /** True for the AbortError DOMException fetch throws when its signal fires. */
86
+ export declare function isAbortError(err: unknown): boolean;
87
+ export interface JsonRpcErrorBody {
88
+ jsonrpc: "2.0";
89
+ error: {
90
+ code: number;
91
+ message: string;
92
+ data?: unknown;
93
+ };
94
+ id: unknown;
95
+ }
96
+ /** A shaped failure: the HTTP status to answer with plus the JSON-RPC body. */
97
+ export interface ShapedJsonRpcError {
98
+ httpStatus: number;
99
+ body: JsonRpcErrorBody;
100
+ }
101
+ /**
102
+ * Map a failure the upstream never answered (or answered unusably) to the
103
+ * client-facing JSON-RPC error + local HTTP status.
104
+ * Only failures upstream never saw as JSON-RPC get shaped here —
105
+ * upstream JSON-RPC errors forward verbatim and never reach this function.
106
+ * Never includes the API key: transport-error messages describe network and
107
+ * protocol conditions only, and unexpected local errors get a generic message
108
+ * (their stack goes to stderr at the catch site, not to the client).
109
+ */
110
+ export declare function toJsonRpcError(failure: unknown, requestId: unknown): ShapedJsonRpcError;
111
+ /**
112
+ * Build the final SSE-framed JSON-RPC error for a failure AFTER the local SSE
113
+ * relay started (mid-stream upstream disconnect). A
114
+ * tools/call may have side effects, so the message warns that the run may
115
+ * still be executing and is never retried silently; when a run id was already
116
+ * seen in a progress frame's `_meta.runId` it is included in `data.runId`
117
+ * (camelCase, matching upstream's `_meta.runId` convention) and named in the
118
+ * message, else `data` is omitted entirely.
119
+ *
120
+ * The -32000 message differentiates the failure kind: a locally aborted
121
+ * upstream request (session close / shutdown mid-stream) reads differently
122
+ * from an upstream that died or broke protocol — but both keep the "run may
123
+ * still be executing" guidance, because in either case a live run could be
124
+ * left behind upstream.
125
+ */
126
+ export declare function toStreamErrorFrame(failure: unknown, requestId: unknown, runId?: string): JsonRpcErrorBody;