@fluxpool/mcp-server 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fluxpool
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,219 @@
1
+ # @fluxpool/mcp-server
2
+
3
+ Generate images and videos with [Fluxpool.ai](https://fluxpool.ai) from Claude,
4
+ Cursor, or any MCP client — across Flux, Seedream, Seedance, Runway and more,
5
+ billed against a single pool of Fluxpool credits.
6
+
7
+ ## Tools
8
+
9
+ | Tool | What it does |
10
+ | --- | --- |
11
+ | `list_models` | The current model catalog — IDs and media type. Call first if unsure which model to use. |
12
+ | `create_generation` | Submit an image or video generation. Returns a generation ID immediately. |
13
+ | `check_generation` | Poll a generation's status; returns the URL once ready. |
14
+ | `get_generation_details` | Full metadata for one generation — prompt, params, credits charged. |
15
+ | `list_library` | Browse saved assets, filtered by media type or model. |
16
+ | `get_balance` | Credit balance for the API key. |
17
+
18
+ Generation is **asynchronous**: `create_generation` returns an ID, then
19
+ `check_generation` is polled until the status is `completed`. Output URLs are
20
+ presigned and **expire after one hour** — download promptly.
21
+
22
+ ## Get an API key
23
+
24
+ Create one at **<https://app.fluxpool.ai/api>**. Keys look like `fp_live_…`.
25
+
26
+ Treat it like a password: it can spend your credits. Put it in your MCP client
27
+ config (below), never in a file you commit.
28
+
29
+ ## Setup
30
+
31
+ Two ways to connect, both serving the same six tools.
32
+
33
+ ### Option 1 — Hosted endpoint (recommended)
34
+
35
+ If your client supports remote MCP servers over HTTP, connect directly. No
36
+ install, no local process, nothing to keep updated.
37
+
38
+ **Endpoint:** `https://api.fluxpool.ai/v1/mcp`
39
+ **Auth:** `Authorization: Bearer fp_live_…`
40
+
41
+ <details>
42
+ <summary>Claude Code</summary>
43
+
44
+ ```bash
45
+ claude mcp add --transport http fluxpool https://api.fluxpool.ai/v1/mcp \
46
+ --header "Authorization: Bearer fp_live_your_key_here"
47
+ ```
48
+ </details>
49
+
50
+ <details>
51
+ <summary>Claude Desktop / Claude web — custom connector</summary>
52
+
53
+ Settings → Connectors → **Add custom connector**, then supply the URL
54
+ `https://api.fluxpool.ai/v1/mcp` and the `Authorization` header above.
55
+ </details>
56
+
57
+ <details>
58
+ <summary>Cursor — <code>.cursor/mcp.json</code></summary>
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "fluxpool": {
64
+ "url": "https://api.fluxpool.ai/v1/mcp",
65
+ "headers": {
66
+ "Authorization": "Bearer fp_live_your_key_here"
67
+ }
68
+ }
69
+ }
70
+ }
71
+ ```
72
+ </details>
73
+
74
+ ### Option 2 — Local bridge via npx
75
+
76
+ For clients that only speak **stdio**. This package is a thin bridge: it
77
+ forwards JSON-RPC to the same hosted endpoint, so the tools are identical.
78
+
79
+ No install step — `npx` fetches it on first run.
80
+
81
+ <details open>
82
+ <summary>Claude Desktop — <code>claude_desktop_config.json</code></summary>
83
+
84
+ - **macOS** `~/Library/Application Support/Claude/claude_desktop_config.json`
85
+ - **Windows** `%APPDATA%\Claude\claude_desktop_config.json`
86
+ - **Linux** `~/.config/Claude/claude_desktop_config.json`
87
+
88
+ ```json
89
+ {
90
+ "mcpServers": {
91
+ "fluxpool": {
92
+ "command": "npx",
93
+ "args": ["-y", "@fluxpool/mcp-server"],
94
+ "env": {
95
+ "FLUXPOOL_API_KEY": "fp_live_your_key_here"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ Restart Claude Desktop fully — quit and reopen, not just close the window.
103
+ </details>
104
+
105
+ <details>
106
+ <summary>Cursor — <code>.cursor/mcp.json</code></summary>
107
+
108
+ ```json
109
+ {
110
+ "mcpServers": {
111
+ "fluxpool": {
112
+ "command": "npx",
113
+ "args": ["-y", "@fluxpool/mcp-server"],
114
+ "env": {
115
+ "FLUXPOOL_API_KEY": "fp_live_your_key_here"
116
+ }
117
+ }
118
+ }
119
+ }
120
+ ```
121
+ </details>
122
+
123
+ <details>
124
+ <summary>Claude Code</summary>
125
+
126
+ ```bash
127
+ claude mcp add fluxpool \
128
+ --env FLUXPOOL_API_KEY=fp_live_your_key_here \
129
+ -- npx -y @fluxpool/mcp-server
130
+ ```
131
+ </details>
132
+
133
+ Prefer a pinned global install over `npx`:
134
+
135
+ ```bash
136
+ npm install -g @fluxpool/mcp-server
137
+ ```
138
+
139
+ Then use `"command": "fluxpool-mcp"` with no `args`.
140
+
141
+ ## Verify it works
142
+
143
+ Ask your client: **"List my Fluxpool models."** It should call `list_models`
144
+ and print the catalog.
145
+
146
+ To check the transport by hand:
147
+
148
+ ```bash
149
+ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
150
+ | FLUXPOOL_API_KEY=fp_live_your_key_here npx -y @fluxpool/mcp-server
151
+ ```
152
+
153
+ That prints one JSON line listing all six tools.
154
+
155
+ ## Configuration
156
+
157
+ | Variable | Required | Default | Purpose |
158
+ | --- | --- | --- | --- |
159
+ | `FLUXPOOL_API_KEY` | yes | — | Your `fp_live_…` key. |
160
+ | `FLUXPOOL_MCP_URL` | no | `https://api.fluxpool.ai/v1/mcp` | Point at a different backend (local or staging). |
161
+
162
+ ## Troubleshooting
163
+
164
+ **"FLUXPOOL_API_KEY is not set"** — the key never reached the process. It goes
165
+ in the `env` block of the server entry, not at the top level of the config.
166
+
167
+ **"Invalid API key"** — the key was rejected. Confirm it is active at
168
+ <https://app.fluxpool.ai/api>; revoked keys fail this way.
169
+
170
+ **The server doesn't appear at all** — check the client's MCP logs. On Claude
171
+ Desktop: `~/Library/Logs/Claude/mcp*.log` (macOS) or `%APPDATA%\Claude\logs\`
172
+ (Windows). Diagnostics from this package are written to stderr and show up
173
+ there. Also confirm `node --version` is 20 or newer.
174
+
175
+ **Insufficient credits** — `create_generation` returns an
176
+ `insufficient_credits` error carrying the required and available amounts. Top
177
+ up at <https://app.fluxpool.ai/credits>.
178
+
179
+ ## How it works
180
+
181
+ ```
182
+ MCP client ──stdio──▶ @fluxpool/mcp-server ──HTTPS──▶ api.fluxpool.ai/v1/mcp
183
+ (Claude, Cursor) (this package) (tools live here)
184
+ ```
185
+
186
+ Tool definitions live on the server, not in this package, and reach clients
187
+ verbatim via `tools/list`. That is deliberate: the hosted endpoint and this
188
+ bridge expose one surface that cannot drift apart, and new tools become
189
+ available without anyone upgrading a package.
190
+
191
+ ## Development
192
+
193
+ ```bash
194
+ npm install
195
+ npm run build # tsc -> dist/
196
+ npm test # builds, then runs offline tests
197
+ ```
198
+
199
+ Tests stub `fetch` and use a dummy key — they never touch the network.
200
+
201
+ ### Releasing
202
+
203
+ ```bash
204
+ npm version patch && git push --follow-tags
205
+ ```
206
+
207
+ CI builds, tests, checks the tag matches `package.json`, and publishes via
208
+ npm Trusted Publishing (OIDC) — no npm token is stored in this repo.
209
+
210
+ ## Contributing
211
+
212
+ Issues and pull requests: <https://github.com/ZKcandy/fluxpool-mcp-server/issues>
213
+
214
+ Because tools are defined server-side, adding or changing one is a backend
215
+ change rather than a change here. Open an issue describing the tool you need.
216
+
217
+ ## License
218
+
219
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,32 @@
1
+ /**
2
+ * stdio <-> HTTP bridge.
3
+ *
4
+ * MCP clients that only speak stdio (Claude Desktop, Cursor) spawn this
5
+ * process and exchange newline-delimited JSON-RPC over stdin/stdout. Each
6
+ * inbound line is forwarded to Fluxpool's hosted MCP endpoint and the reply
7
+ * written back out.
8
+ *
9
+ * Two rules govern stdout and both are load-bearing:
10
+ * - Exactly one JSON message per line, nothing else. Any stray write
11
+ * corrupts the client's parser, which is why every log goes to stderr.
12
+ * - Notifications (a message with no `id`) get no reply, ever. Answering
13
+ * one is a protocol violation.
14
+ */
15
+ import type { Readable, Writable } from 'node:stream';
16
+ import { type FluxpoolClient } from './client.js';
17
+ export interface BridgeOptions {
18
+ client: FluxpoolClient;
19
+ input?: Readable;
20
+ output?: Writable;
21
+ /** Defaults to stderr. Never stdout — that channel is the protocol. */
22
+ log?: (message: string) => void;
23
+ }
24
+ export declare class Bridge {
25
+ #private;
26
+ constructor(options: BridgeOptions);
27
+ /** Pump stdin until it closes. Resolves when the stream ends. */
28
+ run(): Promise<void>;
29
+ /** Process one inbound line. Exposed for tests. */
30
+ handleLine(line: string): Promise<void>;
31
+ private write;
32
+ }
package/dist/bridge.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * stdio <-> HTTP bridge.
3
+ *
4
+ * MCP clients that only speak stdio (Claude Desktop, Cursor) spawn this
5
+ * process and exchange newline-delimited JSON-RPC over stdin/stdout. Each
6
+ * inbound line is forwarded to Fluxpool's hosted MCP endpoint and the reply
7
+ * written back out.
8
+ *
9
+ * Two rules govern stdout and both are load-bearing:
10
+ * - Exactly one JSON message per line, nothing else. Any stray write
11
+ * corrupts the client's parser, which is why every log goes to stderr.
12
+ * - Notifications (a message with no `id`) get no reply, ever. Answering
13
+ * one is a protocol violation.
14
+ */
15
+ import { createInterface } from 'node:readline';
16
+ import { errorResponse } from './client.js';
17
+ import { JSON_RPC } from './types.js';
18
+ export class Bridge {
19
+ #client;
20
+ #input;
21
+ #output;
22
+ #log;
23
+ constructor(options) {
24
+ this.#client = options.client;
25
+ this.#input = options.input ?? process.stdin;
26
+ this.#output = options.output ?? process.stdout;
27
+ this.#log = options.log ?? ((message) => process.stderr.write(`${message}\n`));
28
+ }
29
+ /** Pump stdin until it closes. Resolves when the stream ends. */
30
+ async run() {
31
+ const lines = createInterface({ input: this.#input, crlfDelay: Infinity });
32
+ // Messages are handled sequentially. MCP clients do allow concurrent
33
+ // requests, but ordering keeps stdout writes interleaving-free and the
34
+ // upstream calls are fast enough that head-of-line blocking is not a
35
+ // practical concern for six short request/response tools.
36
+ for await (const line of lines) {
37
+ await this.handleLine(line);
38
+ }
39
+ }
40
+ /** Process one inbound line. Exposed for tests. */
41
+ async handleLine(line) {
42
+ const trimmed = line.trim();
43
+ // Blank lines are framing, not messages. Forwarding one would make the
44
+ // endpoint answer a parse error the client never asked for.
45
+ if (trimmed === '')
46
+ return;
47
+ let message;
48
+ try {
49
+ message = JSON.parse(trimmed);
50
+ }
51
+ catch {
52
+ // Can't recover an id from unparseable input, so per JSON-RPC the id
53
+ // is null.
54
+ this.write(errorResponse(null, JSON_RPC.PARSE_ERROR, 'Invalid JSON received by the Fluxpool MCP bridge'));
55
+ return;
56
+ }
57
+ const id = extractId(message);
58
+ let response;
59
+ try {
60
+ response = await this.#client.send(message, id);
61
+ }
62
+ catch (cause) {
63
+ // The client is written not to throw, so reaching here means a bug
64
+ // rather than an expected failure. Report it instead of dying: killing
65
+ // the process would drop the client's whole session.
66
+ this.#log(`[fluxpool-mcp] unexpected bridge error: ${describe(cause)}`);
67
+ if (id === null)
68
+ return;
69
+ response = errorResponse(id, JSON_RPC.INTERNAL_ERROR, `Bridge error: ${describe(cause)}`);
70
+ }
71
+ if (response === null)
72
+ return; // notification — stay silent
73
+ this.write(response);
74
+ }
75
+ #writeRaw(payload) {
76
+ this.#output.write(`${JSON.stringify(payload)}\n`);
77
+ }
78
+ write(payload) {
79
+ this.#writeRaw(payload);
80
+ }
81
+ }
82
+ /**
83
+ * Pull the id out of an inbound message so a synthesised error can be
84
+ * addressed correctly.
85
+ *
86
+ * Returns null for notifications AND for batches. A batch has no single id;
87
+ * a transport failure mid-batch is reported as an id-less error, which is
88
+ * what the spec prescribes for a request that could not be routed.
89
+ */
90
+ function extractId(message) {
91
+ if (Array.isArray(message))
92
+ return null;
93
+ if (typeof message !== 'object' || message === null)
94
+ return null;
95
+ const id = message.id;
96
+ if (typeof id === 'string' || typeof id === 'number')
97
+ return id;
98
+ return null;
99
+ }
100
+ function describe(cause) {
101
+ return cause instanceof Error ? cause.message : String(cause);
102
+ }
103
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * HTTP client for Fluxpool's hosted MCP endpoint.
3
+ *
4
+ * One job: POST a JSON-RPC message and hand back what came out, normalising
5
+ * the cases where the endpoint answers with something that isn't JSON-RPC.
6
+ *
7
+ * Three responses have to be told apart, and the distinction matters because
8
+ * writing the wrong thing to stdout corrupts the client's protocol stream:
9
+ *
10
+ * 1. A JSON-RPC response -> relay verbatim.
11
+ * 2. HTTP 202, empty body -> a notification was accepted. Return null; the
12
+ * caller must stay silent, because notifications take no reply.
13
+ * 3. An auth/HTTP error -> arrives as OpenAI-shaped `{error:{message}}`
14
+ * with no `jsonrpc` field, since auth is checked before the JSON-RPC
15
+ * dispatcher runs. Convert to a proper JSON-RPC error.
16
+ */
17
+ import { type JsonRpcId, type JsonRpcResponse, type JsonValue, type Outbound } from './types.js';
18
+ export declare const DEFAULT_MCP_URL = "https://api.fluxpool.ai/v1/mcp";
19
+ export interface FluxpoolClientOptions {
20
+ apiKey: string;
21
+ /** Override the endpoint (local backend, staging). Defaults to production. */
22
+ url?: string;
23
+ /** Abort a request after this many ms. Defaults to 10 minutes. */
24
+ timeoutMs?: number;
25
+ /** Injection seam for tests — defaults to global fetch. */
26
+ fetchImpl?: typeof fetch;
27
+ }
28
+ export declare class FluxpoolClient {
29
+ #private;
30
+ constructor(options: FluxpoolClientOptions);
31
+ get url(): string;
32
+ /**
33
+ * Forward one JSON-RPC message (or batch) upstream.
34
+ *
35
+ * @param message Already-parsed JSON-RPC payload from the client.
36
+ * @param id Id to attach to any error we synthesise. Pass null for
37
+ * notifications so a failure can be dropped rather than
38
+ * sent back as a reply to a message that expects none.
39
+ * @returns The upstream response, or null when there is nothing to send.
40
+ */
41
+ send(message: JsonValue, id: JsonRpcId): Promise<Outbound | null>;
42
+ }
43
+ export declare function errorResponse(id: JsonRpcId, code: number, message: string, data?: JsonValue): JsonRpcResponse;
package/dist/client.js ADDED
@@ -0,0 +1,138 @@
1
+ /**
2
+ * HTTP client for Fluxpool's hosted MCP endpoint.
3
+ *
4
+ * One job: POST a JSON-RPC message and hand back what came out, normalising
5
+ * the cases where the endpoint answers with something that isn't JSON-RPC.
6
+ *
7
+ * Three responses have to be told apart, and the distinction matters because
8
+ * writing the wrong thing to stdout corrupts the client's protocol stream:
9
+ *
10
+ * 1. A JSON-RPC response -> relay verbatim.
11
+ * 2. HTTP 202, empty body -> a notification was accepted. Return null; the
12
+ * caller must stay silent, because notifications take no reply.
13
+ * 3. An auth/HTTP error -> arrives as OpenAI-shaped `{error:{message}}`
14
+ * with no `jsonrpc` field, since auth is checked before the JSON-RPC
15
+ * dispatcher runs. Convert to a proper JSON-RPC error.
16
+ */
17
+ import { JSON_RPC, isRestError, } from './types.js';
18
+ export const DEFAULT_MCP_URL = 'https://api.fluxpool.ai/v1/mcp';
19
+ /**
20
+ * Video generation is submitted asynchronously, so no single call should be
21
+ * slow — but a client may still be mid-poll when a network hiccup stalls the
22
+ * socket. Ten minutes is generous enough never to fire in normal use while
23
+ * still guaranteeing the process can't hang forever on a dead connection.
24
+ */
25
+ const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
26
+ export class FluxpoolClient {
27
+ #apiKey;
28
+ #url;
29
+ #timeoutMs;
30
+ #fetch;
31
+ constructor(options) {
32
+ this.#apiKey = options.apiKey;
33
+ this.#url = options.url ?? DEFAULT_MCP_URL;
34
+ this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
35
+ this.#fetch = options.fetchImpl ?? globalThis.fetch;
36
+ }
37
+ get url() {
38
+ return this.#url;
39
+ }
40
+ /**
41
+ * Forward one JSON-RPC message (or batch) upstream.
42
+ *
43
+ * @param message Already-parsed JSON-RPC payload from the client.
44
+ * @param id Id to attach to any error we synthesise. Pass null for
45
+ * notifications so a failure can be dropped rather than
46
+ * sent back as a reply to a message that expects none.
47
+ * @returns The upstream response, or null when there is nothing to send.
48
+ */
49
+ async send(message, id) {
50
+ const controller = new AbortController();
51
+ const timer = setTimeout(() => controller.abort(), this.#timeoutMs);
52
+ let response;
53
+ try {
54
+ response = await this.#fetch(this.#url, {
55
+ method: 'POST',
56
+ headers: {
57
+ 'Content-Type': 'application/json',
58
+ Accept: 'application/json',
59
+ Authorization: `Bearer ${this.#apiKey}`,
60
+ },
61
+ body: JSON.stringify(message),
62
+ signal: controller.signal,
63
+ });
64
+ }
65
+ catch (cause) {
66
+ // Network failure, DNS, TLS, or our own abort. A notification has no
67
+ // reply channel, so there is nothing useful to return.
68
+ if (id === null)
69
+ return null;
70
+ const reason = cause instanceof Error && cause.name === 'AbortError'
71
+ ? `Request timed out after ${this.#timeoutMs}ms`
72
+ : `Could not reach ${this.#url}: ${describe(cause)}`;
73
+ return errorResponse(id, JSON_RPC.TRANSPORT_ERROR, reason);
74
+ }
75
+ finally {
76
+ clearTimeout(timer);
77
+ }
78
+ const text = (await response.text()).trim();
79
+ // 202 + empty body: a notification was accepted. Nothing to relay.
80
+ if (text === '')
81
+ return null;
82
+ let parsed;
83
+ try {
84
+ parsed = JSON.parse(text);
85
+ }
86
+ catch {
87
+ if (id === null)
88
+ return null;
89
+ return errorResponse(id, JSON_RPC.TRANSPORT_ERROR, `Fluxpool returned a non-JSON response (HTTP ${response.status}): ${truncate(text)}`);
90
+ }
91
+ // Auth rejections and other pre-dispatch failures arrive in the REST
92
+ // error shape. Translate so the client sees valid JSON-RPC.
93
+ if (!response.ok && isRestError(parsed)) {
94
+ if (id === null)
95
+ return null;
96
+ const code = response.status === 401 || response.status === 403
97
+ ? JSON_RPC.AUTH_ERROR
98
+ : JSON_RPC.TRANSPORT_ERROR;
99
+ const hint = code === JSON_RPC.AUTH_ERROR
100
+ ? ' Check that FLUXPOOL_API_KEY is a valid key from https://app.fluxpool.ai/api'
101
+ : '';
102
+ return errorResponse(id, code, `${parsed.error.message}${hint}`, {
103
+ http_status: response.status,
104
+ code: parsed.error.code ?? null,
105
+ });
106
+ }
107
+ // Anything else that isn't JSON-RPC — an unexpected gateway body, say.
108
+ if (!response.ok && !isPassThrough(parsed)) {
109
+ if (id === null)
110
+ return null;
111
+ return errorResponse(id, JSON_RPC.TRANSPORT_ERROR, `Fluxpool returned HTTP ${response.status}: ${truncate(text)}`);
112
+ }
113
+ return parsed;
114
+ }
115
+ }
116
+ /** JSON-RPC responses and batches thereof pass through untouched. */
117
+ function isPassThrough(value) {
118
+ if (Array.isArray(value))
119
+ return true;
120
+ return (typeof value === 'object' &&
121
+ value !== null &&
122
+ value.jsonrpc === '2.0');
123
+ }
124
+ export function errorResponse(id, code, message, data) {
125
+ const error = { code, message };
126
+ if (data !== undefined)
127
+ error.data = data;
128
+ return { jsonrpc: '2.0', id, error };
129
+ }
130
+ function describe(cause) {
131
+ if (cause instanceof Error)
132
+ return cause.message;
133
+ return String(cause);
134
+ }
135
+ function truncate(text, max = 200) {
136
+ return text.length <= max ? text : `${text.slice(0, max)}...`;
137
+ }
138
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fluxpool/mcp-server — entry point.
4
+ *
5
+ * Reads FLUXPOOL_API_KEY from the environment, opens the stdio bridge to
6
+ * Fluxpool's hosted MCP endpoint, and relays until stdin closes.
7
+ *
8
+ * Tools are NOT defined here. They are served by the backend and reach the
9
+ * client verbatim through `tools/list`, so the two MCP surfaces — hosted
10
+ * HTTP and this local stdio bridge — can never drift apart.
11
+ */
12
+ export declare function main(env?: NodeJS.ProcessEnv): Promise<number>;
package/dist/index.js ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fluxpool/mcp-server — entry point.
4
+ *
5
+ * Reads FLUXPOOL_API_KEY from the environment, opens the stdio bridge to
6
+ * Fluxpool's hosted MCP endpoint, and relays until stdin closes.
7
+ *
8
+ * Tools are NOT defined here. They are served by the backend and reach the
9
+ * client verbatim through `tools/list`, so the two MCP surfaces — hosted
10
+ * HTTP and this local stdio bridge — can never drift apart.
11
+ */
12
+ import { Bridge } from './bridge.js';
13
+ import { pathToFileURL } from 'node:url';
14
+ import { DEFAULT_MCP_URL, FluxpoolClient } from './client.js';
15
+ /** Log to stderr. stdout carries the protocol and must stay clean. */
16
+ function log(message) {
17
+ process.stderr.write(`${message}\n`);
18
+ }
19
+ export async function main(env = process.env) {
20
+ const apiKey = env.FLUXPOOL_API_KEY?.trim();
21
+ if (!apiKey) {
22
+ log([
23
+ 'fluxpool-mcp: FLUXPOOL_API_KEY is not set.',
24
+ '',
25
+ 'Create a key at https://app.fluxpool.ai/api, then add it to your MCP',
26
+ 'client config. For Claude Desktop that means an "env" block:',
27
+ '',
28
+ ' "mcpServers": {',
29
+ ' "fluxpool": {',
30
+ ' "command": "npx",',
31
+ ' "args": ["-y", "@fluxpool/mcp-server"],',
32
+ ' "env": { "FLUXPOOL_API_KEY": "fp_live_..." }',
33
+ ' }',
34
+ ' }',
35
+ '',
36
+ 'See https://github.com/ZKcandy/fluxpool-mcp-server#setup for other clients.',
37
+ ].join('\n'));
38
+ return 1;
39
+ }
40
+ // Warn but continue: a malformed key still reaches the endpoint, and its
41
+ // 401 is more informative than anything guessed here. Key formats also
42
+ // change more often than this package ships.
43
+ if (!apiKey.startsWith('fp_live_')) {
44
+ log('fluxpool-mcp: warning — FLUXPOOL_API_KEY does not start with "fp_live_". Continuing anyway.');
45
+ }
46
+ const url = env.FLUXPOOL_MCP_URL?.trim() || DEFAULT_MCP_URL;
47
+ const client = new FluxpoolClient({ apiKey, url });
48
+ if (url !== DEFAULT_MCP_URL)
49
+ log(`fluxpool-mcp: using endpoint ${url}`);
50
+ await new Bridge({ client, log }).run();
51
+ return 0;
52
+ }
53
+ // Run only when executed directly, so tests can import main() freely.
54
+ // process.argv[1] is the resolved script path; compare against this module.
55
+ const invokedDirectly = process.argv[1] !== undefined &&
56
+ import.meta.url === pathToFileURL(process.argv[1]).href;
57
+ if (invokedDirectly) {
58
+ main()
59
+ .then((code) => {
60
+ process.exitCode = code;
61
+ })
62
+ .catch((cause) => {
63
+ log(`fluxpool-mcp: fatal — ${cause instanceof Error ? cause.message : String(cause)}`);
64
+ process.exitCode = 1;
65
+ });
66
+ }
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9D,sEAAsE;AACtE,SAAS,GAAG,CAAC,OAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,CAAC;IAE5C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,GAAG,CACD;YACE,4CAA4C;YAC5C,EAAE;YACF,sEAAsE;YACtE,8DAA8D;YAC9D,EAAE;YACF,mBAAmB;YACnB,mBAAmB;YACnB,yBAAyB;YACzB,+CAA+C;YAC/C,oDAAoD;YACpD,OAAO;YACP,KAAK;YACL,EAAE;YACF,6EAA6E;SAC9E,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,yEAAyE;IACzE,uEAAuE;IACvE,6CAA6C;IAC7C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACnC,GAAG,CAAC,6FAA6F,CAAC,CAAC;IACrG,CAAC;IAED,MAAM,GAAG,GAAG,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,IAAI,eAAe,CAAC;IAC5D,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IAEnD,IAAI,GAAG,KAAK,eAAe;QAAE,GAAG,CAAC,gCAAgC,GAAG,EAAE,CAAC,CAAC;IAExE,MAAM,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;IACxC,OAAO,CAAC,CAAC;AACX,CAAC;AAED,sEAAsE;AACtE,4EAA4E;AAC5E,MAAM,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS;IAC7B,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAE1D,IAAI,eAAe,EAAE,CAAC;IACpB,IAAI,EAAE;SACH,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACb,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC1B,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QACxB,GAAG,CAAC,yBAAyB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * JSON-RPC 2.0 + Fluxpool REST types.
3
+ *
4
+ * This package is a transport bridge, not a tool implementation: it relays
5
+ * JSON-RPC messages between a stdio MCP client and Fluxpool's hosted MCP
6
+ * endpoint. So the types here describe the *envelope*, not the tools. Tool
7
+ * schemas live on the backend (`functions/ingestion/index.js`) and reach
8
+ * clients verbatim via `tools/list` — deliberately, so the two surfaces can
9
+ * never drift.
10
+ */
11
+ /** A JSON value, as far as JSON-RPC is concerned. */
12
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
13
+ [key: string]: JsonValue;
14
+ };
15
+ /**
16
+ * JSON-RPC request id. Per spec this is a string, number, or null.
17
+ * A message with NO id is a notification and must not be replied to.
18
+ */
19
+ export type JsonRpcId = string | number | null;
20
+ /** A single JSON-RPC 2.0 request or notification travelling client -> server. */
21
+ export type JsonRpcRequest = {
22
+ jsonrpc: '2.0';
23
+ method: string;
24
+ /** Absent means notification: the server must stay silent. */
25
+ id?: JsonRpcId;
26
+ params?: JsonValue;
27
+ };
28
+ /** A single JSON-RPC 2.0 response travelling server -> client. */
29
+ export type JsonRpcResponse = {
30
+ jsonrpc: '2.0';
31
+ id: JsonRpcId;
32
+ result?: JsonValue;
33
+ error?: JsonRpcErrorObject;
34
+ };
35
+ export type JsonRpcErrorObject = {
36
+ code: number;
37
+ message: string;
38
+ data?: JsonValue;
39
+ };
40
+ /**
41
+ * Standard JSON-RPC error codes, plus the implementation-defined range we
42
+ * use for transport failures. -32000..-32099 is reserved for the server to
43
+ * define, which is where an HTTP-layer problem belongs: the request never
44
+ * reached a method handler.
45
+ */
46
+ export declare const JSON_RPC: {
47
+ readonly PARSE_ERROR: -32700;
48
+ readonly INVALID_REQUEST: -32600;
49
+ readonly METHOD_NOT_FOUND: -32601;
50
+ readonly INVALID_PARAMS: -32602;
51
+ readonly INTERNAL_ERROR: -32603;
52
+ /** Transport-level failure talking to the Fluxpool endpoint. */
53
+ readonly TRANSPORT_ERROR: -32001;
54
+ /** Auth rejected (bad or missing API key). */
55
+ readonly AUTH_ERROR: -32002;
56
+ };
57
+ /**
58
+ * Fluxpool's REST error envelope. The hosted MCP endpoint returns JSON-RPC
59
+ * for anything that reaches the dispatcher, but auth is checked *before*
60
+ * that — so a 401 arrives in this OpenAI-compatible shape with no `jsonrpc`
61
+ * field at all. The bridge has to recognise and translate it.
62
+ */
63
+ export interface FluxpoolRestError {
64
+ error: {
65
+ message: string;
66
+ type?: string;
67
+ param?: string | null;
68
+ code?: string;
69
+ };
70
+ }
71
+ /** Narrowing helper for the REST error shape above. */
72
+ export declare function isRestError(value: unknown): value is FluxpoolRestError;
73
+ /** True when a parsed payload already looks like JSON-RPC we can pass through. */
74
+ export declare function isJsonRpcMessage(value: unknown): boolean;
75
+ /**
76
+ * What the bridge writes to stdout: either a payload relayed verbatim from
77
+ * upstream (an opaque `JsonValue` — we do not re-validate what the backend
78
+ * sends) or an error this package synthesised. Keeping the union explicit
79
+ * avoids laundering typed shapes through `JsonValue`, which drops the very
80
+ * guarantees the types exist to provide.
81
+ */
82
+ export type Outbound = JsonValue | JsonRpcResponse;
package/dist/types.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * JSON-RPC 2.0 + Fluxpool REST types.
3
+ *
4
+ * This package is a transport bridge, not a tool implementation: it relays
5
+ * JSON-RPC messages between a stdio MCP client and Fluxpool's hosted MCP
6
+ * endpoint. So the types here describe the *envelope*, not the tools. Tool
7
+ * schemas live on the backend (`functions/ingestion/index.js`) and reach
8
+ * clients verbatim via `tools/list` — deliberately, so the two surfaces can
9
+ * never drift.
10
+ */
11
+ /**
12
+ * Standard JSON-RPC error codes, plus the implementation-defined range we
13
+ * use for transport failures. -32000..-32099 is reserved for the server to
14
+ * define, which is where an HTTP-layer problem belongs: the request never
15
+ * reached a method handler.
16
+ */
17
+ export const JSON_RPC = {
18
+ PARSE_ERROR: -32700,
19
+ INVALID_REQUEST: -32600,
20
+ METHOD_NOT_FOUND: -32601,
21
+ INVALID_PARAMS: -32602,
22
+ INTERNAL_ERROR: -32603,
23
+ /** Transport-level failure talking to the Fluxpool endpoint. */
24
+ TRANSPORT_ERROR: -32001,
25
+ /** Auth rejected (bad or missing API key). */
26
+ AUTH_ERROR: -32002,
27
+ };
28
+ /** Narrowing helper for the REST error shape above. */
29
+ export function isRestError(value) {
30
+ if (typeof value !== 'object' || value === null)
31
+ return false;
32
+ const err = value.error;
33
+ if (typeof err !== 'object' || err === null)
34
+ return false;
35
+ return typeof err.message === 'string';
36
+ }
37
+ /** True when a parsed payload already looks like JSON-RPC we can pass through. */
38
+ export function isJsonRpcMessage(value) {
39
+ if (typeof value !== 'object' || value === null)
40
+ return false;
41
+ return value.jsonrpc === '2.0';
42
+ }
43
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAwCH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,WAAW,EAAE,CAAC,KAAK;IACnB,eAAe,EAAE,CAAC,KAAK;IACvB,gBAAgB,EAAE,CAAC,KAAK;IACxB,cAAc,EAAE,CAAC,KAAK;IACtB,cAAc,EAAE,CAAC,KAAK;IACtB,gEAAgE;IAChE,eAAe,EAAE,CAAC,KAAK;IACvB,8CAA8C;IAC9C,UAAU,EAAE,CAAC,KAAK;CACV,CAAC;AAiBX,uDAAuD;AACvD,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,GAAG,GAAI,KAA6B,CAAC,KAAK,CAAC;IACjD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC1D,OAAO,OAAQ,GAA6B,CAAC,OAAO,KAAK,QAAQ,CAAC;AACpE,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAQ,KAA+B,CAAC,OAAO,KAAK,KAAK,CAAC;AAC5D,CAAC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@fluxpool/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Fluxpool.ai — generate images and videos across Flux, Seedream, Seedance, Runway and more from Claude, Cursor, or any MCP client.",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "fluxpool",
9
+ "ai",
10
+ "image-generation",
11
+ "video-generation",
12
+ "claude",
13
+ "cursor"
14
+ ],
15
+ "homepage": "https://fluxpool.ai",
16
+ "bugs": {
17
+ "url": "https://github.com/ZKcandy/fluxpool-mcp-server/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/ZKcandy/fluxpool-mcp-server.git"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Fluxpool",
25
+ "type": "module",
26
+ "bin": {
27
+ "fluxpool-mcp": "dist/index.js"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ "engines": {
35
+ "node": ">=20"
36
+ },
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "prepublishOnly": "npm run build && npm test",
40
+ "start": "node dist/index.js",
41
+ "test": "npm run build && node --test test/"
42
+ },
43
+ "dependencies": {
44
+ "@modelcontextprotocol/sdk": "^1.0.4"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^22.10.2",
48
+ "typescript": "^5.7.2"
49
+ }
50
+ }