@vjsr1982/flowforge-mcp 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/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # flowforge-mcp
2
+
3
+ [Model Context Protocol](https://modelcontextprotocol.io) server for [FlowForge](https://flowforge.pick-app.com.mx) — expose your local-first ETL pipelines to LLM clients (Claude Desktop, Cursor, GitHub Copilot, OpenAI Desktop, custom Agent SDK apps).
4
+
5
+ > **The LLM never receives your raw rows** unless it explicitly asks for them through `query_sql` or `preview_dataset`. Pipelines run locally; only summaries/schemas cross the wire.
6
+
7
+ ---
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ # As a one-off (recommended for client config files)
13
+ npx flowforge-mcp --help
14
+
15
+ # Or globally
16
+ npm i -g flowforge-mcp
17
+ ```
18
+
19
+ Requires Node.js ≥ 20.
20
+
21
+ ## Modes
22
+
23
+ | Mode | Transport | Auth | Use case |
24
+ |---|---|---|---|
25
+ | `--remote` (default) | stdio ↔ HTTPS | `Authorization: Bearer ff_pat_…` | Web/cloud users |
26
+ | `--local` | stdio ↔ Tauri sidecar IPC | none (per-launch secret) | Desktop app users |
27
+
28
+ ### Get a token (remote mode)
29
+
30
+ 1. Sign in at <https://flowforge.pick-app.com.mx>.
31
+ 2. Settings → API Tokens → **Create token** (scope: `mcp` minimum).
32
+ 3. Copy the token (shown **once**) — format `ff_pat_<32 chars>`.
33
+ 4. Save it as `FLOWFORGE_API_TOKEN` env var, or in `~/.flowforge/token`.
34
+
35
+ ## Claude Desktop config
36
+
37
+ `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or
38
+ `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "flowforge": {
44
+ "command": "npx",
45
+ "args": ["-y", "flowforge-mcp"],
46
+ "env": { "FLOWFORGE_API_TOKEN": "ff_pat_..." }
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ ## CLI
53
+
54
+ ```text
55
+ flowforge-mcp [--remote] [--base-url <url>] [--token <ff_pat_…>]
56
+ flowforge-mcp --local
57
+ ```
58
+
59
+ | Env var | Default | Notes |
60
+ |---|---|---|
61
+ | `FLOWFORGE_API_TOKEN` | — | required for remote mode |
62
+ | `FLOWFORGE_BASE_URL` | `https://flowforge.pick-app.com.mx` | override for self-hosted |
63
+
64
+ ## Tools (v1)
65
+
66
+ Always available:
67
+ - `list_pipelines`, `get_pipeline`, `validate_pipeline`, `create_pipeline`
68
+
69
+ Available when the engine reports `canExecute` (currently desktop sidecar only):
70
+ - `run_pipeline`, `query_sql`, `list_datasets`, `describe_dataset`, `preview_dataset`
71
+
72
+ ## Resources
73
+
74
+ - `pipeline:///{id}` — pipeline JSON document
75
+ - `pipelines://list` — index of pipelines
76
+
77
+ ## Prompts
78
+
79
+ - `etl/clean-csv` — design a cleanup pipeline for a dataset
80
+ - `etl/explain-pipeline` — explain a pipeline in plain English
81
+ - `etl/optimize-pipeline` — suggest performance improvements
82
+
83
+ ## Security
84
+
85
+ - Token format: `ff_pat_<32 base62>`. Hashed (SHA-256) at rest by the FlowForge backend.
86
+ - `query_sql` is read-only — DDL, `ATTACH`, `PRAGMA` and side-effect statements are rejected by the backend's `sql-validator`.
87
+ - Local mode never sends data to the network. The desktop sidecar is keyed by a per-launch secret stored mode `0600` in the OS config dir.
88
+
89
+ ## Development
90
+
91
+ ```bash
92
+ npm install
93
+ npm run build
94
+ npm test
95
+ ```
96
+
97
+ License: MIT
package/dist/auth.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ export interface ResolvedAuth {
2
+ token: string;
3
+ baseUrl: string;
4
+ }
5
+ export interface ResolveAuthOptions {
6
+ cliToken?: string;
7
+ cliBaseUrl?: string;
8
+ /** Override env reader for tests. */
9
+ env?: NodeJS.ProcessEnv;
10
+ /** Override config-file reader for tests. */
11
+ readConfigFile?: () => Promise<string | null>;
12
+ }
13
+ export declare function resolveAuth(opts?: ResolveAuthOptions): Promise<ResolvedAuth>;
14
+ export declare class AuthMissingError extends Error {
15
+ constructor(baseUrl: string, detail?: string);
16
+ }
17
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAChB;AAID,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,qCAAqC;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CAC9C;AAED,wBAAsB,WAAW,CAAC,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAAC,YAAY,CAAC,CAoBtF;AAaD,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAgB7C"}
package/dist/auth.js ADDED
@@ -0,0 +1,51 @@
1
+ // Resolve the FlowForge API token + base URL from CLI flags or env vars.
2
+ // Order of precedence: CLI flag > environment variable > config file.
3
+ import { readFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ const DEFAULT_BASE_URL = 'https://flowforge.pick-app.com.mx';
7
+ export async function resolveAuth(opts = {}) {
8
+ const env = opts.env ?? process.env;
9
+ const baseUrl = (opts.cliBaseUrl ?? env.FLOWFORGE_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/$/, '');
10
+ let token = opts.cliToken ?? env.FLOWFORGE_API_TOKEN;
11
+ if (!token) {
12
+ const fromFile = opts.readConfigFile ? await opts.readConfigFile() : await readDefaultConfig();
13
+ if (fromFile)
14
+ token = fromFile;
15
+ }
16
+ if (!token) {
17
+ throw new AuthMissingError(baseUrl);
18
+ }
19
+ if (!token.startsWith('ff_pat_')) {
20
+ throw new AuthMissingError(baseUrl, `Token must start with "ff_pat_". Got: ${token.slice(0, 4)}…`);
21
+ }
22
+ return { token, baseUrl };
23
+ }
24
+ async function readDefaultConfig() {
25
+ const path = join(homedir(), '.flowforge', 'token');
26
+ try {
27
+ const content = await readFile(path, 'utf8');
28
+ const trimmed = content.trim();
29
+ return trimmed || null;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ export class AuthMissingError extends Error {
36
+ constructor(baseUrl, detail) {
37
+ const url = `${baseUrl}/settings/tokens`;
38
+ super([
39
+ 'No FlowForge API token found.',
40
+ detail,
41
+ `Create one at ${url} and pass it via:`,
42
+ ' • environment: FLOWFORGE_API_TOKEN=ff_pat_…',
43
+ ' • CLI flag: --token ff_pat_…',
44
+ ` • or save it to ~/.flowforge/token (one line, no quotes)`,
45
+ ]
46
+ .filter(Boolean)
47
+ .join('\n'));
48
+ this.name = 'AuthMissingError';
49
+ }
50
+ }
51
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,sEAAsE;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAOhC,MAAM,gBAAgB,GAAG,mCAAmC,CAAA;AAW5D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAA2B,EAAE;IAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAA;IACnC,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,GAAG,CAAC,kBAAkB,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IAElG,IAAI,KAAK,GAAG,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC,mBAAmB,CAAA;IACpD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,MAAM,iBAAiB,EAAE,CAAA;QAC9F,IAAI,QAAQ;YAAE,KAAK,GAAG,QAAQ,CAAA;IAChC,CAAC;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACrC,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,gBAAgB,CACxB,OAAO,EACP,yCAAyC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAC9D,CAAA;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AAC3B,CAAC;AAED,KAAK,UAAU,iBAAiB;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,OAAO,CAAC,CAAA;IACnD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAA;QAC9B,OAAO,OAAO,IAAI,IAAI,CAAA;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzC,YAAY,OAAe,EAAE,MAAe;QAC1C,MAAM,GAAG,GAAG,GAAG,OAAO,kBAAkB,CAAA;QACxC,KAAK,CACH;YACE,+BAA+B;YAC/B,MAAM;YACN,iBAAiB,GAAG,mBAAmB;YACvC,+CAA+C;YAC/C,mCAAmC;YACnC,4DAA4D;SAC7D;aACE,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,IAAI,CAAC,CACd,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAA;IAChC,CAAC;CACF"}
@@ -0,0 +1,38 @@
1
+ import { request } from 'undici';
2
+ import { type DatasetSchema, type DatasetSummary, type Engine, type PipelineJson, type PipelineSummary, type PreviewResult, type QueryResult, type RunResult, type ValidationResult } from './types.js';
3
+ export interface RemoteHttpEngineOptions {
4
+ baseUrl: string;
5
+ token: string;
6
+ /** Optional fetch wrapper for tests. */
7
+ fetcher?: typeof request;
8
+ }
9
+ export declare class RemoteHttpEngine implements Engine {
10
+ private readonly baseUrl;
11
+ private readonly token;
12
+ private readonly fetcher;
13
+ constructor(opts: RemoteHttpEngineOptions);
14
+ capabilities(): ReturnType<Engine['capabilities']>;
15
+ listPipelines(filter: {
16
+ q?: string;
17
+ limit?: number;
18
+ }): Promise<PipelineSummary[]>;
19
+ getPipeline(id: string): Promise<PipelineJson>;
20
+ createPipeline(input: {
21
+ name: string;
22
+ description?: string;
23
+ content: Record<string, unknown>;
24
+ tags?: string[];
25
+ }): Promise<{
26
+ id: string;
27
+ }>;
28
+ validatePipeline(content: Record<string, unknown>): Promise<ValidationResult>;
29
+ runPipeline(_id: string, _inputs?: Record<string, string>, _outputFormat?: 'json' | 'csv' | 'arrow'): Promise<RunResult>;
30
+ listDatasets(): Promise<DatasetSummary[]>;
31
+ describeDataset(_id: string): Promise<DatasetSchema>;
32
+ previewDataset(_id: string, _limit?: number): Promise<PreviewResult>;
33
+ querySql(_datasetId: string, _sql: string, _limit?: number): Promise<QueryResult>;
34
+ private get;
35
+ private post;
36
+ private exec;
37
+ }
38
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/engine/http.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAChC,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,MAAM,EAEX,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACtB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,wCAAwC;IACxC,OAAO,CAAC,EAAE,OAAO,OAAO,CAAA;CACzB;AAOD,qBAAa,gBAAiB,YAAW,MAAM;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;gBAE5B,IAAI,EAAE,uBAAuB;IAMzC,YAAY,IAAI,UAAU,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAK5C,aAAa,CAAC,MAAM,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAWjF,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI9C,cAAc,CAAC,KAAK,EAAE;QAC1B,IAAI,EAAE,MAAM,CAAA;QACZ,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAChC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAChB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAUrB,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAI7E,WAAW,CACf,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,aAAa,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GACvC,OAAO,CAAC,SAAS,CAAC;IAMf,YAAY,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAKzC,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAIpD,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAIpE,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;YAMzE,GAAG;YAIH,IAAI;YAIJ,IAAI;CAwCnB"}
@@ -0,0 +1,107 @@
1
+ // Remote engine — issues authenticated REST calls to the FlowForge backend
2
+ // using the user's Personal Access Token (Bearer ff_pat_...).
3
+ //
4
+ // Execution-heavy tools (run_pipeline, query_sql) are not yet exposed in
5
+ // remote v1: the backend returns 501 and `capabilities()` reflects that, so
6
+ // the MCP server simply omits those tools from the registry.
7
+ import { request } from 'undici';
8
+ import { EngineUnavailableError, } from './types.js';
9
+ export class RemoteHttpEngine {
10
+ baseUrl;
11
+ token;
12
+ fetcher;
13
+ constructor(opts) {
14
+ this.baseUrl = opts.baseUrl.replace(/\/$/, '');
15
+ this.token = opts.token;
16
+ this.fetcher = opts.fetcher ?? request;
17
+ }
18
+ capabilities() {
19
+ // v1 remote: read + create + validate only.
20
+ return { canExecute: false, canQuerySql: false, hasDatasets: false };
21
+ }
22
+ async listPipelines(filter) {
23
+ const params = new URLSearchParams();
24
+ if (filter.q)
25
+ params.set('q', filter.q);
26
+ if (filter.limit)
27
+ params.set('limit', String(filter.limit));
28
+ const qs = params.toString();
29
+ const json = await this.get(`/api/v1/mcp/pipelines${qs ? `?${qs}` : ''}`);
30
+ return json.data;
31
+ }
32
+ async getPipeline(id) {
33
+ return await this.get(`/api/v1/mcp/pipelines/${encodeURIComponent(id)}`);
34
+ }
35
+ async createPipeline(input) {
36
+ const json = await this.post(`/api/v1/mcp/pipelines`, {
37
+ name: input.name,
38
+ description: input.description,
39
+ content: input.content,
40
+ tags: input.tags ?? [],
41
+ });
42
+ return { id: json.id };
43
+ }
44
+ async validatePipeline(content) {
45
+ return await this.post(`/api/v1/mcp/pipelines/validate`, { content });
46
+ }
47
+ async runPipeline(_id, _inputs, _outputFormat) {
48
+ throw new EngineUnavailableError('Pipeline execution is not available over the remote MCP transport. Run flowforge-mcp in --local mode (alongside the FlowForge desktop app) to execute pipelines.');
49
+ }
50
+ async listDatasets() {
51
+ const json = await this.get(`/api/v1/mcp/datasets`);
52
+ return json.data;
53
+ }
54
+ async describeDataset(_id) {
55
+ throw new EngineUnavailableError('describe_dataset requires the local MCP transport.');
56
+ }
57
+ async previewDataset(_id, _limit) {
58
+ throw new EngineUnavailableError('preview_dataset requires the local MCP transport.');
59
+ }
60
+ async querySql(_datasetId, _sql, _limit) {
61
+ throw new EngineUnavailableError('query_sql requires the local MCP transport.');
62
+ }
63
+ // ------------------------------------------------------------------ HTTP
64
+ async get(path) {
65
+ return await this.exec('GET', path);
66
+ }
67
+ async post(path, body) {
68
+ return await this.exec('POST', path, body);
69
+ }
70
+ async exec(method, path, body) {
71
+ const res = await this.fetcher(`${this.baseUrl}${path}`, {
72
+ method,
73
+ headers: {
74
+ Authorization: `Bearer ${this.token}`,
75
+ 'Content-Type': 'application/json',
76
+ Accept: 'application/json',
77
+ },
78
+ body: body === undefined ? undefined : JSON.stringify(body),
79
+ });
80
+ const status = res.statusCode;
81
+ const text = await res.body.text();
82
+ if (status >= 400) {
83
+ let parsed = null;
84
+ try {
85
+ parsed = JSON.parse(text);
86
+ }
87
+ catch {
88
+ // not JSON
89
+ }
90
+ const msg = parsed?.message ?? text ?? `HTTP ${status}`;
91
+ if (status === 401) {
92
+ throw new EngineUnavailableError(`Authentication failed (HTTP 401). Check FLOWFORGE_API_TOKEN. Server said: ${msg}`, 'UNAUTHENTICATED');
93
+ }
94
+ if (status === 403) {
95
+ throw new EngineUnavailableError(`Permission denied (HTTP 403). Token scope insufficient. Server said: ${msg}`, 'FORBIDDEN');
96
+ }
97
+ if (status === 501) {
98
+ throw new EngineUnavailableError(msg, 'NOT_IMPLEMENTED');
99
+ }
100
+ throw new Error(`FlowForge API ${method} ${path} failed (${status}): ${msg}`);
101
+ }
102
+ if (!text)
103
+ return {};
104
+ return JSON.parse(text);
105
+ }
106
+ }
107
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/engine/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { request } from 'undici';
2
+ import { type DatasetSchema, type DatasetSummary, type Engine, type PipelineJson, type PipelineSummary, type PreviewResult, type QueryResult, type RunResult, type ValidationResult } from './types.js';
3
+ export interface LocalTauriEngineOptions {
4
+ /** Override for tests. */
5
+ configDir?: string;
6
+ /** Override for tests. */
7
+ fetcher?: typeof request;
8
+ }
9
+ /** Resolve the platform-specific app config dir for `com.flowforge.app`. */
10
+ export declare function defaultConfigDir(): string;
11
+ export declare class LocalTauriEngine implements Engine {
12
+ private readonly configDir;
13
+ private readonly fetcher;
14
+ private conn;
15
+ constructor(opts?: LocalTauriEngineOptions);
16
+ capabilities(): ReturnType<Engine['capabilities']>;
17
+ listPipelines(filter: {
18
+ q?: string;
19
+ limit?: number;
20
+ }): Promise<PipelineSummary[]>;
21
+ getPipeline(id: string): Promise<PipelineJson>;
22
+ createPipeline(): Promise<{
23
+ id: string;
24
+ }>;
25
+ validatePipeline(): Promise<ValidationResult>;
26
+ runPipeline(): Promise<RunResult>;
27
+ listDatasets(): Promise<DatasetSummary[]>;
28
+ describeDataset(): Promise<DatasetSchema>;
29
+ previewDataset(): Promise<PreviewResult>;
30
+ querySql(): Promise<QueryResult>;
31
+ private bail;
32
+ private connect;
33
+ private get;
34
+ }
35
+ //# sourceMappingURL=local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/engine/local.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAIhC,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,MAAM,EAEX,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACtB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,uBAAuB;IACtC,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0BAA0B;IAC1B,OAAO,CAAC,EAAE,OAAO,OAAO,CAAA;CACzB;AAOD,4EAA4E;AAC5E,wBAAgB,gBAAgB,IAAI,MAAM,CAUzC;AAED,qBAAa,gBAAiB,YAAW,MAAM;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,IAAI,CAAuC;gBAEvC,IAAI,GAAE,uBAA4B;IAK9C,YAAY,IAAI,UAAU,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAO5C,aAAa,CAAC,MAAM,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAWjF,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI9C,cAAc,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAIzC,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAI7C,WAAW,IAAI,OAAO,CAAC,SAAS,CAAC;IAIjC,YAAY,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAIzC,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC;IAIzC,cAAc,IAAI,OAAO,CAAC,aAAa,CAAC;IAIxC,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC;IAMtC,OAAO,CAAC,IAAI;YAME,OAAO;YA+BP,GAAG;CA0BlB"}
@@ -0,0 +1,131 @@
1
+ // Local engine — talks to the FlowForge desktop app's HTTP loopback
2
+ // sidecar. On startup the desktop app writes:
3
+ // • <config>/flowforge/mcp-secret — 64 hex chars, mode 0600
4
+ // • <config>/flowforge/mcp-port — decimal TCP port on 127.0.0.1
5
+ //
6
+ // We read both at construction time, then issue HTTP requests against
7
+ // http://127.0.0.1:<port> with the `X-FlowForge-Secret` header.
8
+ //
9
+ // v0.2 scope: read-only list/get against the file-backed registry the
10
+ // renderer populates via the `register_pipeline` Tauri command. Run /
11
+ // query-SQL / dataset endpoints will be added once the sidecar exposes
12
+ // them.
13
+ import { request } from 'undici';
14
+ import { promises as fs } from 'node:fs';
15
+ import { homedir, platform } from 'node:os';
16
+ import { join } from 'node:path';
17
+ import { EngineUnavailableError, } from './types.js';
18
+ /** Resolve the platform-specific app config dir for `com.flowforge.app`. */
19
+ export function defaultConfigDir() {
20
+ const home = homedir();
21
+ switch (platform()) {
22
+ case 'win32':
23
+ return join(process.env.APPDATA ?? join(home, 'AppData', 'Roaming'), 'com.flowforge.app');
24
+ case 'darwin':
25
+ return join(home, 'Library', 'Application Support', 'com.flowforge.app');
26
+ default:
27
+ return join(process.env.XDG_CONFIG_HOME ?? join(home, '.config'), 'com.flowforge.app');
28
+ }
29
+ }
30
+ export class LocalTauriEngine {
31
+ configDir;
32
+ fetcher;
33
+ conn = null;
34
+ constructor(opts = {}) {
35
+ this.configDir = opts.configDir ?? defaultConfigDir();
36
+ this.fetcher = opts.fetcher ?? request;
37
+ }
38
+ capabilities() {
39
+ // v0.2: read-only pipeline registry; execution still pending.
40
+ return { canExecute: false, canQuerySql: false, hasDatasets: false };
41
+ }
42
+ // -------------------------------------------------------------- Engine API
43
+ async listPipelines(filter) {
44
+ const data = await this.get('/v1/pipelines');
45
+ let out = data;
46
+ if (filter.q) {
47
+ const q = filter.q.toLowerCase();
48
+ out = out.filter((p) => p.name?.toLowerCase().includes(q));
49
+ }
50
+ if (filter.limit && filter.limit > 0)
51
+ out = out.slice(0, filter.limit);
52
+ return out;
53
+ }
54
+ async getPipeline(id) {
55
+ return await this.get(`/v1/pipelines/${encodeURIComponent(id)}`);
56
+ }
57
+ async createPipeline() {
58
+ this.bail('create_pipeline');
59
+ }
60
+ async validatePipeline() {
61
+ this.bail('validate_pipeline');
62
+ }
63
+ async runPipeline() {
64
+ this.bail('run_pipeline');
65
+ }
66
+ async listDatasets() {
67
+ this.bail('list_datasets');
68
+ }
69
+ async describeDataset() {
70
+ this.bail('describe_dataset');
71
+ }
72
+ async previewDataset() {
73
+ this.bail('preview_dataset');
74
+ }
75
+ async querySql() {
76
+ this.bail('query_sql');
77
+ }
78
+ // ----------------------------------------------------------------- Helpers
79
+ bail(method) {
80
+ throw new EngineUnavailableError(`${method} is not yet exposed by the desktop sidecar (v0.2 read-only). Tracked in the v0.3 milestone.`);
81
+ }
82
+ async connect() {
83
+ if (!this.conn) {
84
+ this.conn = (async () => {
85
+ const ffDir = join(this.configDir, 'flowforge');
86
+ const secretPath = join(ffDir, 'mcp-secret');
87
+ const portPath = join(ffDir, 'mcp-port');
88
+ const [secretRaw, portRaw] = await Promise.all([
89
+ fs.readFile(secretPath, 'utf8').catch((err) => {
90
+ throw new EngineUnavailableError(`Cannot read ${secretPath} — is the FlowForge desktop app running? (${err.message})`);
91
+ }),
92
+ fs.readFile(portPath, 'utf8').catch((err) => {
93
+ throw new EngineUnavailableError(`Cannot read ${portPath} — is the FlowForge desktop app running? (${err.message})`);
94
+ }),
95
+ ]);
96
+ const secret = secretRaw.trim();
97
+ const port = Number.parseInt(portRaw.trim(), 10);
98
+ if (!secret || !Number.isFinite(port) || port <= 0 || port > 65535) {
99
+ throw new EngineUnavailableError(`Invalid sidecar credentials in ${ffDir}. Restart the FlowForge desktop app.`);
100
+ }
101
+ return { baseUrl: `http://127.0.0.1:${port}`, secret };
102
+ })();
103
+ }
104
+ return await this.conn;
105
+ }
106
+ async get(path) {
107
+ const { baseUrl, secret } = await this.connect();
108
+ const res = await this.fetcher(`${baseUrl}${path}`, {
109
+ method: 'GET',
110
+ headers: {
111
+ 'X-FlowForge-Secret': secret,
112
+ Accept: 'application/json',
113
+ },
114
+ });
115
+ const status = res.statusCode;
116
+ const text = await res.body.text();
117
+ if (status === 401 || status === 403) {
118
+ throw new EngineUnavailableError(`Sidecar rejected the secret (HTTP ${status}). Restart the FlowForge desktop app to rotate.`, 'UNAUTHENTICATED');
119
+ }
120
+ if (status === 404) {
121
+ throw new EngineUnavailableError(`Not found: ${path}`);
122
+ }
123
+ if (status >= 400) {
124
+ throw new Error(`Sidecar GET ${path} failed (${status}): ${text}`);
125
+ }
126
+ if (!text)
127
+ return {};
128
+ return JSON.parse(text);
129
+ }
130
+ }
131
+ //# sourceMappingURL=local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/engine/local.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ import { z } from 'zod';
2
+ export declare const PipelineSummary: z.ZodObject<{
3
+ id: z.ZodString;
4
+ name: z.ZodString;
5
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
6
+ tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
7
+ schemaVersion: z.ZodOptional<z.ZodNumber>;
8
+ updatedAt: z.ZodString;
9
+ }, "strip", z.ZodTypeAny, {
10
+ id: string;
11
+ name: string;
12
+ tags: string[];
13
+ updatedAt: string;
14
+ description?: string | null | undefined;
15
+ schemaVersion?: number | undefined;
16
+ }, {
17
+ id: string;
18
+ name: string;
19
+ updatedAt: string;
20
+ description?: string | null | undefined;
21
+ tags?: string[] | undefined;
22
+ schemaVersion?: number | undefined;
23
+ }>;
24
+ export type PipelineSummary = z.infer<typeof PipelineSummary>;
25
+ export declare const PipelineJson: z.ZodObject<{
26
+ id: z.ZodString;
27
+ name: z.ZodString;
28
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
29
+ content: z.ZodRecord<z.ZodString, z.ZodUnknown>;
30
+ tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
31
+ schemaVersion: z.ZodOptional<z.ZodNumber>;
32
+ }, "strip", z.ZodTypeAny, {
33
+ id: string;
34
+ name: string;
35
+ tags: string[];
36
+ content: Record<string, unknown>;
37
+ description?: string | null | undefined;
38
+ schemaVersion?: number | undefined;
39
+ }, {
40
+ id: string;
41
+ name: string;
42
+ content: Record<string, unknown>;
43
+ description?: string | null | undefined;
44
+ tags?: string[] | undefined;
45
+ schemaVersion?: number | undefined;
46
+ }>;
47
+ export type PipelineJson = z.infer<typeof PipelineJson>;
48
+ export interface RunResult {
49
+ rows: number;
50
+ schema: {
51
+ name: string;
52
+ type: string;
53
+ }[];
54
+ elapsedMs: number;
55
+ downloadUrl?: string;
56
+ }
57
+ export interface QueryResult {
58
+ rows: unknown[][];
59
+ schema: {
60
+ name: string;
61
+ type: string;
62
+ }[];
63
+ truncated: boolean;
64
+ }
65
+ export interface DatasetSchema {
66
+ columns: {
67
+ name: string;
68
+ type: string;
69
+ nullCount?: number;
70
+ }[];
71
+ rowCount?: number;
72
+ }
73
+ export interface PreviewResult {
74
+ rows: unknown[][];
75
+ schema: {
76
+ name: string;
77
+ type: string;
78
+ }[];
79
+ }
80
+ export interface DatasetSummary {
81
+ id: string;
82
+ name: string;
83
+ format: string;
84
+ sizeBytes?: number;
85
+ createdAt: string;
86
+ }
87
+ export interface ValidationResult {
88
+ valid: boolean;
89
+ issues: {
90
+ code: string;
91
+ message: string;
92
+ nodeId?: string;
93
+ }[];
94
+ }
95
+ export interface Engine {
96
+ listPipelines(filter: {
97
+ q?: string;
98
+ limit?: number;
99
+ }): Promise<PipelineSummary[]>;
100
+ getPipeline(id: string): Promise<PipelineJson>;
101
+ createPipeline(input: {
102
+ name: string;
103
+ description?: string;
104
+ content: Record<string, unknown>;
105
+ tags?: string[];
106
+ }): Promise<{
107
+ id: string;
108
+ }>;
109
+ validatePipeline(content: Record<string, unknown>): Promise<ValidationResult>;
110
+ runPipeline(id: string, inputs?: Record<string, string>, outputFormat?: 'json' | 'csv' | 'arrow'): Promise<RunResult>;
111
+ listDatasets(): Promise<DatasetSummary[]>;
112
+ describeDataset(id: string): Promise<DatasetSchema>;
113
+ previewDataset(id: string, limit?: number): Promise<PreviewResult>;
114
+ querySql(datasetId: string, sql: string, limit?: number): Promise<QueryResult>;
115
+ capabilities(): {
116
+ canExecute: boolean;
117
+ canQuerySql: boolean;
118
+ hasDatasets: boolean;
119
+ };
120
+ }
121
+ /** Thrown by engines when the operation is unavailable in this mode. */
122
+ export declare class EngineUnavailableError extends Error {
123
+ readonly message: string;
124
+ readonly code: string;
125
+ constructor(message: string, code?: string);
126
+ }
127
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/engine/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ // Shared engine contract for MCP tools. Two implementations:
2
+ // • RemoteHttpEngine — talks to flowforge.pick-app.com.mx via PAT
3
+ // • LocalTauriEngine — talks to the desktop app's local IPC channel
4
+ import { z } from 'zod';
5
+ export const PipelineSummary = z.object({
6
+ id: z.string(),
7
+ name: z.string(),
8
+ description: z.string().nullable().optional(),
9
+ tags: z.array(z.string()).default([]),
10
+ schemaVersion: z.number().optional(),
11
+ updatedAt: z.string(),
12
+ });
13
+ export const PipelineJson = z.object({
14
+ id: z.string(),
15
+ name: z.string(),
16
+ description: z.string().nullable().optional(),
17
+ content: z.record(z.unknown()),
18
+ tags: z.array(z.string()).default([]),
19
+ schemaVersion: z.number().optional(),
20
+ });
21
+ /** Thrown by engines when the operation is unavailable in this mode. */
22
+ export class EngineUnavailableError extends Error {
23
+ message;
24
+ code;
25
+ constructor(message, code = 'NOT_IMPLEMENTED') {
26
+ super(message);
27
+ this.message = message;
28
+ this.code = code;
29
+ this.name = 'EngineUnavailableError';
30
+ }
31
+ }
32
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/engine/types.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,oEAAoE;AACpE,sEAAsE;AACtE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC7C,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAAC,CAAA;AAGF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC7C,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC9B,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAA;AAsEF,wEAAwE;AACxE,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAEpB;IACT;IAFlB,YAC2B,OAAe,EACxB,OAAe,iBAAiB;QAEhD,KAAK,CAAC,OAAO,CAAC,CAAA;QAHW,YAAO,GAAP,OAAO,CAAQ;QACxB,SAAI,GAAJ,IAAI,CAA4B;QAGhD,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAA;IACtC,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ // CLI entrypoint. Two modes:
3
+ // • --remote (default) → HTTP transport, talks to FlowForge backend
4
+ // • --local → stdio transport, talks to FlowForge desktop sidecar
5
+ //
6
+ // Argument parsing is intentionally minimal — no external dep.
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { resolveAuth, AuthMissingError } from './auth.js';
9
+ import { LocalTauriEngine } from './engine/local.js';
10
+ import { RemoteHttpEngine } from './engine/http.js';
11
+ import { createServer } from './server.js';
12
+ function parseArgs(argv) {
13
+ const opts = { mode: 'remote', help: false };
14
+ for (let i = 0; i < argv.length; i++) {
15
+ const arg = argv[i];
16
+ switch (arg) {
17
+ case '--local':
18
+ opts.mode = 'local';
19
+ break;
20
+ case '--remote':
21
+ opts.mode = 'remote';
22
+ break;
23
+ case '--base-url':
24
+ opts.baseUrl = argv[++i];
25
+ break;
26
+ case '--token':
27
+ opts.token = argv[++i];
28
+ break;
29
+ case '-h':
30
+ case '--help':
31
+ opts.help = true;
32
+ break;
33
+ }
34
+ }
35
+ return opts;
36
+ }
37
+ function printHelp() {
38
+ process.stderr.write(`flowforge-mcp — Model Context Protocol server for FlowForge
39
+
40
+ Usage:
41
+ flowforge-mcp [--remote] [--base-url <url>] [--token <ff_pat_…>]
42
+ flowforge-mcp --local
43
+
44
+ Options:
45
+ --remote Connect to a hosted FlowForge backend (default).
46
+ --local Use the FlowForge desktop sidecar (no token required).
47
+ --base-url <url> Override base URL (default: env FLOWFORGE_BASE_URL or
48
+ https://flowforge.pick-app.com.mx).
49
+ --token <token> Personal Access Token (overrides env FLOWFORGE_API_TOKEN).
50
+ -h, --help Show this help.
51
+
52
+ Environment:
53
+ FLOWFORGE_API_TOKEN PAT created at /settings/tokens.
54
+ FLOWFORGE_BASE_URL Override the API base URL.
55
+
56
+ Examples:
57
+ FLOWFORGE_API_TOKEN=ff_pat_… flowforge-mcp
58
+ flowforge-mcp --local
59
+ `);
60
+ }
61
+ async function main() {
62
+ const opts = parseArgs(process.argv.slice(2));
63
+ if (opts.help) {
64
+ printHelp();
65
+ return;
66
+ }
67
+ const engine = opts.mode === 'local'
68
+ ? new LocalTauriEngine()
69
+ : new RemoteHttpEngine(await resolveAuth({ cliToken: opts.token, cliBaseUrl: opts.baseUrl }).catch((err) => {
70
+ if (err instanceof AuthMissingError) {
71
+ process.stderr.write(`${err.message}\n`);
72
+ process.exit(2);
73
+ }
74
+ throw err;
75
+ }));
76
+ const server = createServer({ engine });
77
+ const transport = new StdioServerTransport();
78
+ await server.connect(transport);
79
+ // server.connect resolves once the transport closes
80
+ }
81
+ main().catch((err) => {
82
+ const msg = err instanceof Error ? err.stack ?? err.message : String(err);
83
+ process.stderr.write(`flowforge-mcp fatal: ${msg}\n`);
84
+ process.exit(1);
85
+ });
86
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+ export interface PromptTemplate {
3
+ name: string;
4
+ title: string;
5
+ description: string;
6
+ argsSchema?: Record<string, z.ZodTypeAny>;
7
+ render: (args: Record<string, string>) => string;
8
+ }
9
+ export declare const PROMPT_TEMPLATES: PromptTemplate[];
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/prompts/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAA;IACzC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAA;CACjD;AAED,eAAO,MAAM,gBAAgB,EAAE,cAAc,EAoD5C,CAAA"}
@@ -0,0 +1,52 @@
1
+ // Bundled prompt templates surfaced to MCP clients via prompts/list.
2
+ // Keep them short — clients render them as starter messages.
3
+ import { z } from 'zod';
4
+ export const PROMPT_TEMPLATES = [
5
+ {
6
+ name: 'etl/clean-csv',
7
+ title: 'Suggest a cleanup pipeline',
8
+ description: 'Given a dataset id, ask the model to design a cleanup pipeline (drop nulls, cast types, dedupe).',
9
+ argsSchema: { datasetId: z.string().describe('Dataset id to inspect') },
10
+ render: (args) => [
11
+ `You are designing a FlowForge ETL pipeline for dataset \`${args.datasetId ?? '<datasetId>'}\`.`,
12
+ '',
13
+ '1. Call `describe_dataset` to inspect the schema.',
14
+ '2. Identify columns with high null counts, inconsistent casing, or wrong types.',
15
+ '3. Propose a sequence of FlowForge nodes (filter_rows / cast_types / handle_nulls / trim_strings / deduplicate) that produces a clean output.',
16
+ '4. Build the pipeline JSON, then call `validate_pipeline` followed by `create_pipeline`.',
17
+ ].join('\n'),
18
+ },
19
+ {
20
+ name: 'etl/explain-pipeline',
21
+ title: 'Explain a pipeline in plain English',
22
+ description: 'Summarise what a pipeline does, step by step, for non-technical users.',
23
+ argsSchema: { id: z.string().describe('Pipeline id') },
24
+ render: (args) => [
25
+ `Fetch pipeline \`${args.id ?? '<id>'}\` via \`get_pipeline\` and explain in plain English:`,
26
+ '',
27
+ '• What datasets it reads.',
28
+ '• Each transformation, in order.',
29
+ '• What the final output looks like.',
30
+ '',
31
+ 'Use bullet points. Avoid SQL jargon.',
32
+ ].join('\n'),
33
+ },
34
+ {
35
+ name: 'etl/optimize-pipeline',
36
+ title: 'Suggest pipeline optimizations',
37
+ description: 'Look for redundant nodes, ordering that limits push-down, and ways to reduce row counts earlier.',
38
+ argsSchema: { id: z.string().describe('Pipeline id') },
39
+ render: (args) => [
40
+ `Fetch pipeline \`${args.id ?? '<id>'}\` and analyse it for performance.`,
41
+ '',
42
+ 'Look specifically for:',
43
+ ' – filter_rows that could move earlier in the pipeline.',
44
+ ' – drop_columns missing before expensive joins.',
45
+ ' – duplicate / redundant cast_types or handle_nulls steps.',
46
+ ' – aggregate before a sort that processes the full table.',
47
+ '',
48
+ 'Return a numbered list of suggestions, each with a one-line rationale.',
49
+ ].join('\n'),
50
+ },
51
+ ];
52
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/prompts/index.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,6DAA6D;AAC7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAUvB,MAAM,CAAC,MAAM,gBAAgB,GAAqB;IAChD;QACE,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,4BAA4B;QACnC,WAAW,EACT,kGAAkG;QACpG,UAAU,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE;QACvE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CACf;YACE,4DAA4D,IAAI,CAAC,SAAS,IAAI,aAAa,KAAK;YAChG,EAAE;YACF,mDAAmD;YACnD,iFAAiF;YACjF,+IAA+I;YAC/I,0FAA0F;SAC3F,CAAC,IAAI,CAAC,IAAI,CAAC;KACf;IACD;QACE,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,qCAAqC;QAC5C,WAAW,EAAE,wEAAwE;QACrF,UAAU,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE;QACtD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CACf;YACE,oBAAoB,IAAI,CAAC,EAAE,IAAI,MAAM,uDAAuD;YAC5F,EAAE;YACF,2BAA2B;YAC3B,kCAAkC;YAClC,qCAAqC;YACrC,EAAE;YACF,sCAAsC;SACvC,CAAC,IAAI,CAAC,IAAI,CAAC;KACf;IACD;QACE,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,gCAAgC;QACvC,WAAW,EACT,kGAAkG;QACpG,UAAU,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE;QACtD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CACf;YACE,oBAAoB,IAAI,CAAC,EAAE,IAAI,MAAM,oCAAoC;YACzE,EAAE;YACF,wBAAwB;YACxB,0DAA0D;YAC1D,kDAAkD;YAClD,6DAA6D;YAC7D,4DAA4D;YAC5D,EAAE;YACF,wEAAwE;SACzE,CAAC,IAAI,CAAC,IAAI,CAAC;KACf;CACF,CAAA"}
@@ -0,0 +1,9 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { Engine } from './engine/types.js';
3
+ export interface CreateServerOptions {
4
+ engine: Engine;
5
+ /** Override version reported to clients. */
6
+ version?: string;
7
+ }
8
+ export declare function createServer(opts: CreateServerOptions): McpServer;
9
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAEnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAI/C,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,SAAS,CAiOjE"}
package/dist/server.js ADDED
@@ -0,0 +1,173 @@
1
+ // MCP server registration: tools, resources, prompts.
2
+ // Dynamic registration based on engine.capabilities() — execution tools
3
+ // only appear when the engine can actually run them.
4
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
+ import { z } from 'zod';
6
+ import { EngineUnavailableError } from './engine/types.js';
7
+ import { PROMPT_TEMPLATES } from './prompts/index.js';
8
+ export function createServer(opts) {
9
+ const { engine } = opts;
10
+ const caps = engine.capabilities();
11
+ const server = new McpServer({
12
+ name: 'flowforge-mcp',
13
+ version: opts.version ?? '0.1.0',
14
+ });
15
+ // ───────────────────────────────────────────────── Tools — pipelines
16
+ server.registerTool('list_pipelines', {
17
+ title: 'List pipelines',
18
+ description: 'List the user\'s FlowForge pipelines. Optional fuzzy search via `q`. Returns id, name, description, tags and updatedAt.',
19
+ inputSchema: {
20
+ q: z.string().optional().describe('Optional case-insensitive fuzzy match on name/description'),
21
+ limit: z.number().int().positive().max(100).optional(),
22
+ },
23
+ }, async ({ q, limit }) => {
24
+ const pipelines = await engine.listPipelines({ q, limit });
25
+ return jsonResult(pipelines);
26
+ });
27
+ server.registerTool('get_pipeline', {
28
+ title: 'Get pipeline',
29
+ description: 'Fetch the full JSON of a pipeline by id, including its node graph.',
30
+ inputSchema: { id: z.string().min(1) },
31
+ }, async ({ id }) => {
32
+ const pipeline = await engine.getPipeline(id);
33
+ return jsonResult(pipeline);
34
+ });
35
+ server.registerTool('validate_pipeline', {
36
+ title: 'Validate pipeline',
37
+ description: 'Validate a pipeline JSON document for structural correctness (shape, unique node ids, edge endpoints, no cycles). Use this before calling create_pipeline.',
38
+ inputSchema: { content: z.record(z.unknown()) },
39
+ }, async ({ content }) => {
40
+ const result = await engine.validatePipeline(content);
41
+ return jsonResult(result);
42
+ });
43
+ server.registerTool('create_pipeline', {
44
+ title: 'Create pipeline',
45
+ description: 'Create a new pipeline from a JSON document. The content must follow FlowForge schemaVersion 1+ (use validate_pipeline first to catch structural errors).',
46
+ inputSchema: {
47
+ name: z.string().min(1).max(255),
48
+ description: z.string().max(2000).optional(),
49
+ content: z.record(z.unknown()),
50
+ tags: z.array(z.string()).optional(),
51
+ },
52
+ }, async ({ name, description, content, tags }) => {
53
+ const created = await engine.createPipeline({ name, description, content, tags });
54
+ return jsonResult(created);
55
+ });
56
+ if (caps.canExecute) {
57
+ server.registerTool('run_pipeline', {
58
+ title: 'Run pipeline',
59
+ description: 'Execute a pipeline end-to-end and return row count + schema. Optionally provide input file overrides via `inputs` (map of node-id → file path / dataset id).',
60
+ inputSchema: {
61
+ id: z.string().min(1),
62
+ inputs: z.record(z.string()).optional(),
63
+ outputFormat: z.enum(['json', 'csv', 'arrow']).optional(),
64
+ },
65
+ }, async ({ id, inputs, outputFormat }) => {
66
+ const result = await engine.runPipeline(id, inputs, outputFormat);
67
+ return jsonResult(result);
68
+ });
69
+ }
70
+ // ───────────────────────────────────────────────── Tools — datasets / SQL
71
+ if (caps.hasDatasets) {
72
+ server.registerTool('list_datasets', {
73
+ title: 'List datasets',
74
+ description: 'List datasets registered with the FlowForge engine.',
75
+ inputSchema: {},
76
+ }, async () => {
77
+ const datasets = await engine.listDatasets();
78
+ return jsonResult(datasets);
79
+ });
80
+ server.registerTool('describe_dataset', {
81
+ title: 'Describe dataset',
82
+ description: 'Return the schema (columns + types) and row count of a dataset.',
83
+ inputSchema: { datasetId: z.string().min(1) },
84
+ }, async ({ datasetId }) => {
85
+ const schema = await engine.describeDataset(datasetId);
86
+ return jsonResult(schema);
87
+ });
88
+ server.registerTool('preview_dataset', {
89
+ title: 'Preview dataset',
90
+ description: 'Return the first N rows of a dataset (max 100). Useful before writing SQL.',
91
+ inputSchema: {
92
+ datasetId: z.string().min(1),
93
+ limit: z.number().int().positive().max(100).optional(),
94
+ },
95
+ }, async ({ datasetId, limit }) => {
96
+ const preview = await engine.previewDataset(datasetId, limit);
97
+ return jsonResult(preview);
98
+ });
99
+ }
100
+ if (caps.canQuerySql) {
101
+ server.registerTool('query_sql', {
102
+ title: 'Query SQL',
103
+ description: 'Run a read-only DuckDB SELECT/WITH query against a registered dataset. DDL, ATTACH, PRAGMA and side-effect statements are rejected. Result is automatically truncated to `limit` (default 1000, max 10000) rows.',
104
+ inputSchema: {
105
+ datasetId: z.string().min(1),
106
+ sql: z.string().min(1).max(10_000),
107
+ limit: z.number().int().positive().max(10_000).optional(),
108
+ },
109
+ }, async ({ datasetId, sql, limit }) => {
110
+ const result = await engine.querySql(datasetId, sql, limit);
111
+ return jsonResult(result);
112
+ });
113
+ }
114
+ // ───────────────────────────────────────────────── Resources
115
+ server.registerResource('pipeline', new URL('pipeline:///{id}').toString(), {
116
+ title: 'Pipeline',
117
+ description: 'A FlowForge pipeline JSON document.',
118
+ mimeType: 'application/json',
119
+ }, async (uri) => {
120
+ const id = extractIdFromUri(uri.toString(), 'pipeline');
121
+ const pipeline = await engine.getPipeline(id);
122
+ return {
123
+ contents: [
124
+ {
125
+ uri: uri.toString(),
126
+ mimeType: 'application/json',
127
+ text: JSON.stringify(pipeline, null, 2),
128
+ },
129
+ ],
130
+ };
131
+ });
132
+ server.registerResource('pipelines-list', 'pipelines://list', {
133
+ title: 'All pipelines',
134
+ description: 'Index of every pipeline owned by the authenticated user.',
135
+ mimeType: 'application/json',
136
+ }, async (uri) => {
137
+ const pipelines = await engine.listPipelines({});
138
+ return {
139
+ contents: [
140
+ {
141
+ uri: uri.toString(),
142
+ mimeType: 'application/json',
143
+ text: JSON.stringify(pipelines, null, 2),
144
+ },
145
+ ],
146
+ };
147
+ });
148
+ // ───────────────────────────────────────────────── Prompts
149
+ for (const tpl of PROMPT_TEMPLATES) {
150
+ server.registerPrompt(tpl.name, { title: tpl.title, description: tpl.description, argsSchema: tpl.argsSchema ?? {} }, async (args) => ({
151
+ messages: [
152
+ {
153
+ role: 'user',
154
+ content: { type: 'text', text: tpl.render(args) },
155
+ },
156
+ ],
157
+ }));
158
+ }
159
+ return server;
160
+ }
161
+ function jsonResult(value) {
162
+ return {
163
+ content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
164
+ };
165
+ }
166
+ function extractIdFromUri(uri, scheme) {
167
+ const prefix = `${scheme}://`;
168
+ const idx = uri.indexOf(prefix);
169
+ if (idx === -1)
170
+ throw new EngineUnavailableError(`Bad URI: ${uri}`);
171
+ return decodeURIComponent(uri.slice(idx + prefix.length).replace(/^\/+/, ''));
172
+ }
173
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,wEAAwE;AACxE,qDAAqD;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAQrD,MAAM,UAAU,YAAY,CAAC,IAAyB;IACpD,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IACvB,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,EAAE,CAAA;IAElC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,OAAO;KACjC,CAAC,CAAA;IAEF,sEAAsE;IAEtE,MAAM,CAAC,YAAY,CACjB,gBAAgB,EAChB;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EACT,yHAAyH;QAC3H,WAAW,EAAE;YACX,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2DAA2D,CAAC;YAC9F,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;SACvD;KACF,EACD,KAAK,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;QACrB,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;QAC1D,OAAO,UAAU,CAAC,SAAS,CAAC,CAAA;IAC9B,CAAC,CACF,CAAA;IAED,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,oEAAoE;QACjF,WAAW,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;KACvC,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;QACf,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;QAC7C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAA;IAC7B,CAAC,CACF,CAAA;IAED,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EACT,4JAA4J;QAC9J,WAAW,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE;KAChD,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACpB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;QACrD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAA;IAC3B,CAAC,CACF,CAAA;IAED,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,KAAK,EAAE,iBAAiB;QACxB,WAAW,EACT,0JAA0J;QAC5J,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;YAChC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;YAC5C,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;SACrC;KACF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE;QAC7C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QACjF,OAAO,UAAU,CAAC,OAAO,CAAC,CAAA;IAC5B,CAAC,CACF,CAAA;IAED,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;YACE,KAAK,EAAE,cAAc;YACrB,WAAW,EACT,8JAA8J;YAChK,WAAW,EAAE;gBACX,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;gBACrB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;gBACvC,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE;aAC1D;SACF,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE;YACrC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,CAAA;YACjE,OAAO,UAAU,CAAC,MAAM,CAAC,CAAA;QAC3B,CAAC,CACF,CAAA;IACH,CAAC;IAED,2EAA2E;IAE3E,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;YACE,KAAK,EAAE,eAAe;YACtB,WAAW,EAAE,qDAAqD;YAClE,WAAW,EAAE,EAAE;SAChB,EACD,KAAK,IAAI,EAAE;YACT,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,YAAY,EAAE,CAAA;YAC5C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAA;QAC7B,CAAC,CACF,CAAA;QAED,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;YACE,KAAK,EAAE,kBAAkB;YACzB,WAAW,EAAE,iEAAiE;YAC9E,WAAW,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;SAC9C,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;YACtB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,SAAS,CAAC,CAAA;YACtD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAA;QAC3B,CAAC,CACF,CAAA;QAED,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;YACE,KAAK,EAAE,iBAAiB;YACxB,WAAW,EAAE,4EAA4E;YACzF,WAAW,EAAE;gBACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC5B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;aACvD;SACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE;YAC7B,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;YAC7D,OAAO,UAAU,CAAC,OAAO,CAAC,CAAA;QAC5B,CAAC,CACF,CAAA;IACH,CAAC;IAED,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,MAAM,CAAC,YAAY,CACjB,WAAW,EACX;YACE,KAAK,EAAE,WAAW;YAClB,WAAW,EACT,kNAAkN;YACpN,WAAW,EAAE;gBACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC5B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;gBAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;aAC1D;SACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE;YAClC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;YAC3D,OAAO,UAAU,CAAC,MAAM,CAAC,CAAA;QAC3B,CAAC,CACF,CAAA;IACH,CAAC;IAED,8DAA8D;IAE9D,MAAM,CAAC,gBAAgB,CACrB,UAAU,EACV,IAAI,GAAG,CAAC,kBAAkB,CAAC,CAAC,QAAQ,EAAE,EACtC;QACE,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,qCAAqC;QAClD,QAAQ,EAAE,kBAAkB;KAC7B,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,UAAU,CAAC,CAAA;QACvD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;QAC7C,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;oBACnB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;iBACxC;aACF;SACF,CAAA;IACH,CAAC,CACF,CAAA;IAED,MAAM,CAAC,gBAAgB,CACrB,gBAAgB,EAChB,kBAAkB,EAClB;QACE,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,0DAA0D;QACvE,QAAQ,EAAE,kBAAkB;KAC7B,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA;QAChD,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;oBACnB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;iBACzC;aACF;SACF,CAAA;IACH,CAAC,CACF,CAAA;IAED,4DAA4D;IAE5D,KAAK,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACnC,MAAM,CAAC,cAAc,CACnB,GAAG,CAAC,IAAI,EACR,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,EAAE,EAAE,EACpF,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YACf,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAA8B,CAAC,EAAE;iBAC5E;aACF;SACF,CAAC,CACH,CAAA;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;KAClE,CAAA;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAW,EAAE,MAAc;IACnD,MAAM,MAAM,GAAG,GAAG,MAAM,KAAK,CAAA;IAC7B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAC/B,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,MAAM,IAAI,sBAAsB,CAAC,YAAY,GAAG,EAAE,CAAC,CAAA;IACnE,OAAO,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAA;AAC/E,CAAC"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@vjsr1982/flowforge-mcp",
3
+ "version": "0.2.0",
4
+ "description": "Model Context Protocol server for FlowForge — expose your local-first ETL pipelines to LLM clients (Claude Desktop, Cursor, Copilot, etc.)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "flowforge-mcp": "./dist/index.js"
9
+ },
10
+ "main": "./dist/server.js",
11
+ "exports": {
12
+ ".": "./dist/server.js",
13
+ "./engine": "./dist/engine/types.js"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json",
24
+ "dev": "tsx src/index.ts",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "typecheck": "tsc --noEmit"
28
+ },
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.0.4",
31
+ "eventsource-parser": "^3.0.0",
32
+ "undici": "^6.21.0",
33
+ "zod": "^3.23.8"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.9.0",
37
+ "tsx": "^4.19.2",
38
+ "typescript": "^5.6.3",
39
+ "vitest": "^2.1.5"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "keywords": [
45
+ "mcp",
46
+ "model-context-protocol",
47
+ "flowforge",
48
+ "etl",
49
+ "duckdb",
50
+ "llm",
51
+ "ai-agent"
52
+ ],
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/flowforge/flow-forge",
56
+ "directory": "flowforge-mcp"
57
+ }
58
+ }