@granoflow/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 Granoflow
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,203 @@
1
+ # Granoflow MCP Server
2
+
3
+ MCP server for Granoflow: exposes the Granoflow Local HTTP API as tools for AI
4
+ agents, IDEs, and automation.
5
+
6
+ This server is intentionally thin. It does not own Granoflow business logic,
7
+ database access, app orchestration, or release workflows. It resolves a local API
8
+ endpoint, forwards structured requests to the running Granoflow app, and returns
9
+ predictable MCP tool results.
10
+
11
+ ## Requirements
12
+
13
+ - Node.js 20 or newer.
14
+ - A running Granoflow app with the Local HTTP API enabled.
15
+
16
+ The default Granoflow API URL is:
17
+
18
+ ```text
19
+ http://127.0.0.1:56789
20
+ ```
21
+
22
+ You can override it with:
23
+
24
+ ```bash
25
+ export GRANOFLOW_API_BASE_URL="http://127.0.0.1:56789"
26
+ export GRANOFLOW_API_TOKEN="..."
27
+ ```
28
+
29
+ The MCP server can keep non-secret local connection defaults in:
30
+
31
+ ```text
32
+ ~/.config/granoflow-mcp/config.json
33
+ ```
34
+
35
+ Set `GRANOFLOW_MCP_CONFIG_PATH` to use a different config path for tests,
36
+ temporary setups, or advanced local installs. API tokens are not stored in this
37
+ file; keep `GRANOFLOW_API_TOKEN` in the MCP client environment.
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ npm install -g @granoflow/mcp-server
43
+ ```
44
+
45
+ For local development:
46
+
47
+ ```bash
48
+ npm install
49
+ npm run build
50
+ node dist/index.js
51
+ ```
52
+
53
+ Verify an installed package without starting an MCP stdio session:
54
+
55
+ ```bash
56
+ npx -y @granoflow/mcp-server --version
57
+ npx -y @granoflow/mcp-server --help
58
+ ```
59
+
60
+ Before publishing a release, verify the package contents:
61
+
62
+ ```bash
63
+ npm run check
64
+ npm pack --dry-run
65
+ ```
66
+
67
+ ## Tools
68
+
69
+ Initial tools:
70
+
71
+ - `granoflow_setup_status`
72
+ - `granoflow_setup_detect_local_api`
73
+ - `granoflow_setup_write_config`
74
+ - `granoflow_setup_open_config`
75
+ - `granoflow_setup_open_app`
76
+ - `granoflow_health`
77
+ - `granoflow_version`
78
+ - `granoflow_capabilities`
79
+ - `granoflow_ai_agent_tools`
80
+ - `granoflow_task_list`
81
+ - `granoflow_task_export`
82
+ - `granoflow_task_validate`
83
+ - `granoflow_task_import`
84
+ - `granoflow_task_create`
85
+ - `granoflow_task_create_structured`
86
+ - `granoflow_task_update`
87
+ - `granoflow_task_update_structured`
88
+ - `granoflow_task_complete`
89
+ - `granoflow_project_list`
90
+ - `granoflow_project_create`
91
+ - `granoflow_project_update`
92
+ - `granoflow_milestone_list`
93
+ - `granoflow_milestone_create`
94
+ - `granoflow_milestone_update`
95
+ - `granoflow_review_day_show`
96
+ - `granoflow_api_request`
97
+
98
+ Prefer the structured task, project, and milestone tools for common resource
99
+ operations. The JSON payload tools remain available as escape hatches when the
100
+ running app exposes newer fields before this package has first-class schemas.
101
+
102
+ Write tools default to dry-run behavior. Ask the tool to write only after you
103
+ have reviewed the preview or the user has explicitly requested a write.
104
+
105
+ ## Setup Diagnostics
106
+
107
+ Use the setup tools when an agent or MCP client needs to connect to a local
108
+ Granoflow app without hand-editing every setting first:
109
+
110
+ - `granoflow_setup_status` reports config path, env/config precedence, token
111
+ presence, Local HTTP API health, version metadata, and local Granoflow process
112
+ evidence without printing secrets.
113
+ - `granoflow_setup_detect_local_api` probes a small bounded localhost port list
114
+ only.
115
+ - `granoflow_setup_write_config` previews or writes non-secret config. It
116
+ defaults to dry-run.
117
+ - `granoflow_setup_open_config` creates and optionally opens the config file for
118
+ manual editing.
119
+ - `granoflow_setup_open_app` previews or opens the installed Granoflow app after
120
+ user approval. On macOS it tries the formal `/Applications/granoflow.app`
121
+ path before app-name fallbacks. It defaults to dry-run.
122
+
123
+ When setup status sees a configured localhost API URL that is unreachable, it
124
+ checks whether a local Granoflow process appears to be running. If not, it
125
+ returns a warning and asks the agent to confirm before opening the app.
126
+
127
+ ## Client Support
128
+
129
+ This package implements a standard MCP stdio server. The primary compatibility
130
+ contract is the MCP protocol plus the npm executable:
131
+
132
+ ```bash
133
+ npx -y @granoflow/mcp-server
134
+ ```
135
+
136
+ Cursor and Codex are the verified client targets for this repository. Other
137
+ MCP-compatible clients can use the same stdio command shape, but are not part of
138
+ the routine verification matrix.
139
+
140
+ ## Cursor
141
+
142
+ Add this to `.cursor/mcp.json` in a project or `~/.cursor/mcp.json` globally:
143
+
144
+ ```json
145
+ {
146
+ "mcpServers": {
147
+ "granoflow": {
148
+ "command": "npx",
149
+ "args": ["-y", "@granoflow/mcp-server"],
150
+ "env": {
151
+ "GRANOFLOW_API_BASE_URL": "http://127.0.0.1:56789"
152
+ }
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ ## Codex
159
+
160
+ Add this to `~/.codex/config.toml`:
161
+
162
+ ```toml
163
+ [mcp_servers.granoflow]
164
+ command = "npx"
165
+ args = ["-y", "@granoflow/mcp-server"]
166
+
167
+ [mcp_servers.granoflow.env]
168
+ GRANOFLOW_API_BASE_URL = "http://127.0.0.1:56789"
169
+ ```
170
+
171
+ Restart Codex after changing MCP configuration.
172
+
173
+ ## Other MCP-Compatible Clients
174
+
175
+ For clients that support local stdio MCP servers, configure the server with:
176
+
177
+ ```jsonc
178
+ {
179
+ "type": "stdio",
180
+ "command": "npx",
181
+ "args": ["-y", "@granoflow/mcp-server"],
182
+ "env": {
183
+ "GRANOFLOW_API_BASE_URL": "http://127.0.0.1:56789",
184
+ },
185
+ }
186
+ ```
187
+
188
+ ## Development
189
+
190
+ ```bash
191
+ npm install
192
+ npm run check
193
+ ```
194
+
195
+ `npm run check` runs Prettier, ESLint, TypeScript, and Vitest.
196
+
197
+ ## Security
198
+
199
+ - This server does not read or write Granoflow's SQLite/Drift database.
200
+ - This server does not run Granoflow app builds, screenshots, release jobs, or
201
+ scenario orchestration.
202
+ - Core operations go through the running app's Local HTTP API.
203
+ - API tokens are passed through environment variables and must not be logged.
package/dist/api.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { type RuntimeResolution } from "./config.js";
2
+ export interface ApiRequestOptions {
3
+ method?: "GET" | "POST" | "PATCH" | "DELETE";
4
+ path: string;
5
+ body?: unknown;
6
+ dryRun?: boolean;
7
+ }
8
+ export interface ApiResult {
9
+ ok: boolean;
10
+ code: string;
11
+ data?: unknown;
12
+ error?: {
13
+ message: string;
14
+ };
15
+ httpStatus?: number;
16
+ runtime: {
17
+ apiBaseUrl: string;
18
+ apiBaseUrlSource: RuntimeResolution["apiBaseUrlSource"];
19
+ apiToken: {
20
+ present: boolean;
21
+ source: RuntimeResolution["apiTokenSource"];
22
+ };
23
+ };
24
+ }
25
+ export declare function buildApiUrl(apiBaseUrl: string, path: string): URL;
26
+ export declare function requestGranoflowApi(options: ApiRequestOptions, env?: NodeJS.ProcessEnv): Promise<ApiResult>;
package/dist/api.js ADDED
@@ -0,0 +1,98 @@
1
+ import { resolveMcpRuntime } from "./config.js";
2
+ function runtimeSummary(runtime) {
3
+ return {
4
+ apiBaseUrl: runtime.apiBaseUrl,
5
+ apiBaseUrlSource: runtime.apiBaseUrlSource,
6
+ apiToken: {
7
+ present: runtime.hasApiToken,
8
+ source: runtime.apiTokenSource,
9
+ },
10
+ };
11
+ }
12
+ function pathWithLeadingSlash(path) {
13
+ return path.startsWith("/") ? path : `/${path}`;
14
+ }
15
+ export function buildApiUrl(apiBaseUrl, path) {
16
+ return new URL(pathWithLeadingSlash(path), apiBaseUrl.endsWith("/") ? apiBaseUrl : `${apiBaseUrl}/`);
17
+ }
18
+ async function parseResponseBody(response) {
19
+ const text = await response.text();
20
+ if (!text.trim()) {
21
+ return null;
22
+ }
23
+ try {
24
+ return JSON.parse(text);
25
+ }
26
+ catch {
27
+ return text;
28
+ }
29
+ }
30
+ export async function requestGranoflowApi(options, env = process.env) {
31
+ const runtime = await resolveMcpRuntime(env);
32
+ const method = options.method ?? "GET";
33
+ const path = pathWithLeadingSlash(options.path);
34
+ if (options.dryRun) {
35
+ return {
36
+ ok: true,
37
+ code: "dry_run",
38
+ data: {
39
+ method,
40
+ path,
41
+ body: options.body ?? null,
42
+ previewMode: "local_request_only",
43
+ },
44
+ runtime: runtimeSummary(runtime),
45
+ };
46
+ }
47
+ const headers = new Headers();
48
+ headers.set("accept", "application/json");
49
+ if (options.body !== undefined) {
50
+ headers.set("content-type", "application/json");
51
+ }
52
+ if (runtime.apiToken) {
53
+ headers.set("authorization", `Bearer ${runtime.apiToken}`);
54
+ }
55
+ try {
56
+ const response = await fetch(buildApiUrl(runtime.apiBaseUrl, path), {
57
+ method,
58
+ headers,
59
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
60
+ });
61
+ const body = await parseResponseBody(response);
62
+ if (!response.ok) {
63
+ return {
64
+ ok: false,
65
+ code: `http_${response.status}`,
66
+ data: body,
67
+ error: { message: `Granoflow Local HTTP API returned HTTP ${response.status}.` },
68
+ httpStatus: response.status,
69
+ runtime: runtimeSummary(runtime),
70
+ };
71
+ }
72
+ if (typeof body === "object" && body !== null && "ok" in body && "code" in body) {
73
+ return {
74
+ ...body,
75
+ httpStatus: response.status,
76
+ runtime: runtimeSummary(runtime),
77
+ };
78
+ }
79
+ return {
80
+ ok: true,
81
+ code: "ok",
82
+ data: body,
83
+ httpStatus: response.status,
84
+ runtime: runtimeSummary(runtime),
85
+ };
86
+ }
87
+ catch (error) {
88
+ return {
89
+ ok: false,
90
+ code: "network_error",
91
+ error: {
92
+ message: error instanceof Error ? error.message : String(error),
93
+ },
94
+ runtime: runtimeSummary(runtime),
95
+ };
96
+ }
97
+ }
98
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ export interface GranoflowMcpConfig {
2
+ apiBaseUrl?: string;
3
+ [key: string]: unknown;
4
+ }
5
+ export interface ConfigReadResult {
6
+ configPath: string;
7
+ exists: boolean;
8
+ config: GranoflowMcpConfig;
9
+ error?: string;
10
+ }
11
+ export interface RuntimeResolution {
12
+ configPath: string;
13
+ configExists: boolean;
14
+ configError?: string;
15
+ apiBaseUrl: string;
16
+ apiBaseUrlSource: "env" | "config" | "default";
17
+ hasApiToken: boolean;
18
+ apiTokenSource: "env" | "none";
19
+ apiToken?: string;
20
+ }
21
+ export interface WriteConfigInput {
22
+ apiBaseUrl?: string;
23
+ dryRun?: boolean;
24
+ }
25
+ export interface WriteConfigResult {
26
+ configPath: string;
27
+ dryRun: boolean;
28
+ written: boolean;
29
+ backupPath: string | null;
30
+ previousConfig: Record<string, unknown>;
31
+ nextConfig: Record<string, unknown>;
32
+ changedKeys: string[];
33
+ nextActions: string[];
34
+ }
35
+ export declare function getMcpConfigPath(env?: NodeJS.ProcessEnv): string;
36
+ export declare function redactConfig(config: GranoflowMcpConfig): Record<string, unknown>;
37
+ export declare function readMcpConfig(env?: NodeJS.ProcessEnv): Promise<ConfigReadResult>;
38
+ export declare function resolveMcpRuntime(env?: NodeJS.ProcessEnv): Promise<RuntimeResolution>;
39
+ export declare function writeMcpConfig(input: WriteConfigInput, env?: NodeJS.ProcessEnv): Promise<WriteConfigResult>;
40
+ export declare function configFileExists(configPath: string): Promise<boolean>;
package/dist/config.js ADDED
@@ -0,0 +1,148 @@
1
+ import { mkdir, open, readFile, rename, stat, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ const SECRET_KEY_PATTERN = /(token|secret|password|credential|key)/i;
5
+ const DEFAULT_API_BASE_URL = "http://127.0.0.1:56789";
6
+ function isObject(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+ function validateApiBaseUrl(apiBaseUrl) {
10
+ const url = new URL(apiBaseUrl);
11
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
12
+ throw new Error("apiBaseUrl must use http or https.");
13
+ }
14
+ }
15
+ function stableJson(value) {
16
+ return `${JSON.stringify(value, null, 2)}\n`;
17
+ }
18
+ export function getMcpConfigPath(env = process.env) {
19
+ if (env.GRANOFLOW_MCP_CONFIG_PATH) {
20
+ return env.GRANOFLOW_MCP_CONFIG_PATH;
21
+ }
22
+ if (env.XDG_CONFIG_HOME) {
23
+ return join(env.XDG_CONFIG_HOME, "granoflow-mcp", "config.json");
24
+ }
25
+ return join(homedir(), ".config", "granoflow-mcp", "config.json");
26
+ }
27
+ export function redactConfig(config) {
28
+ const redacted = {};
29
+ for (const [key, value] of Object.entries(config)) {
30
+ redacted[key] = SECRET_KEY_PATTERN.test(key) && value !== undefined ? "[REDACTED]" : value;
31
+ }
32
+ return redacted;
33
+ }
34
+ export async function readMcpConfig(env = process.env) {
35
+ const configPath = getMcpConfigPath(env);
36
+ try {
37
+ const text = await readFile(configPath, "utf8");
38
+ const parsed = JSON.parse(text);
39
+ if (!isObject(parsed)) {
40
+ return {
41
+ configPath,
42
+ exists: true,
43
+ config: {},
44
+ error: "Config file must contain a JSON object.",
45
+ };
46
+ }
47
+ return { configPath, exists: true, config: parsed };
48
+ }
49
+ catch (error) {
50
+ if (isObject(error) && "code" in error && error.code === "ENOENT") {
51
+ return { configPath, exists: false, config: {} };
52
+ }
53
+ return {
54
+ configPath,
55
+ exists: false,
56
+ config: {},
57
+ error: error instanceof Error ? error.message : String(error),
58
+ };
59
+ }
60
+ }
61
+ export async function resolveMcpRuntime(env = process.env) {
62
+ const configResult = await readMcpConfig(env);
63
+ const config = configResult.config;
64
+ const apiBaseUrl = env.GRANOFLOW_API_BASE_URL ??
65
+ (typeof config.apiBaseUrl === "string" ? config.apiBaseUrl : undefined) ??
66
+ DEFAULT_API_BASE_URL;
67
+ return {
68
+ configPath: configResult.configPath,
69
+ configExists: configResult.exists,
70
+ configError: configResult.error,
71
+ apiBaseUrl,
72
+ apiBaseUrlSource: env.GRANOFLOW_API_BASE_URL
73
+ ? "env"
74
+ : typeof config.apiBaseUrl === "string"
75
+ ? "config"
76
+ : "default",
77
+ hasApiToken: Boolean(env.GRANOFLOW_API_TOKEN),
78
+ apiTokenSource: env.GRANOFLOW_API_TOKEN ? "env" : "none",
79
+ apiToken: env.GRANOFLOW_API_TOKEN,
80
+ };
81
+ }
82
+ export async function writeMcpConfig(input, env = process.env) {
83
+ if (input.apiBaseUrl !== undefined) {
84
+ validateApiBaseUrl(input.apiBaseUrl);
85
+ }
86
+ const readResult = await readMcpConfig(env);
87
+ const nextConfig = { ...readResult.config };
88
+ if (input.apiBaseUrl !== undefined) {
89
+ nextConfig.apiBaseUrl = input.apiBaseUrl;
90
+ }
91
+ const previousConfig = redactConfig(readResult.config);
92
+ const redactedNextConfig = redactConfig(nextConfig);
93
+ const changedKeys = Object.keys(redactedNextConfig).filter((key) => previousConfig[key] !== redactedNextConfig[key]);
94
+ const dryRun = input.dryRun !== false;
95
+ if (dryRun) {
96
+ return {
97
+ configPath: readResult.configPath,
98
+ dryRun,
99
+ written: false,
100
+ backupPath: null,
101
+ previousConfig,
102
+ nextConfig: redactedNextConfig,
103
+ changedKeys,
104
+ nextActions: [
105
+ "Review the config preview.",
106
+ "Call this tool again with dryRun=false to write.",
107
+ ],
108
+ };
109
+ }
110
+ await mkdir(dirname(readResult.configPath), { recursive: true });
111
+ let backupPath = null;
112
+ if (readResult.exists) {
113
+ backupPath = `${readResult.configPath}.bak-${new Date().toISOString().replace(/[:.]/g, "-")}`;
114
+ await writeFile(backupPath, stableJson(readResult.config), { mode: 0o600 });
115
+ }
116
+ const tempPath = `${readResult.configPath}.tmp-${process.pid}`;
117
+ await writeFile(tempPath, stableJson(nextConfig), { mode: 0o600 });
118
+ await rename(tempPath, readResult.configPath);
119
+ const handle = await open(readResult.configPath, "r+");
120
+ await handle.chmod(0o600);
121
+ await handle.close();
122
+ return {
123
+ configPath: readResult.configPath,
124
+ dryRun,
125
+ written: true,
126
+ backupPath,
127
+ previousConfig,
128
+ nextConfig: redactedNextConfig,
129
+ changedKeys,
130
+ nextActions: [
131
+ "Restart or reload the MCP client if it keeps a long-running server process.",
132
+ "Call granoflow_setup_status to verify the resolved configuration.",
133
+ ],
134
+ };
135
+ }
136
+ export async function configFileExists(configPath) {
137
+ try {
138
+ await stat(configPath);
139
+ return true;
140
+ }
141
+ catch (error) {
142
+ if (isObject(error) && "code" in error && error.code === "ENOENT") {
143
+ return false;
144
+ }
145
+ throw error;
146
+ }
147
+ }
148
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { SERVER_NAME, SERVER_VERSION } from "./metadata.js";
5
+ import { registerGranoflowTools } from "./tools.js";
6
+ function printHelp() {
7
+ process.stdout.write(`${SERVER_NAME} ${SERVER_VERSION}
8
+
9
+ Usage:
10
+ granoflow-mcp-server
11
+ granoflow-mcp-server --help
12
+ granoflow-mcp-server --version
13
+
14
+ Starts the Granoflow MCP server over stdio.
15
+
16
+ Environment:
17
+ GRANOFLOW_API_BASE_URL Optional Granoflow Local HTTP API base URL.
18
+ GRANOFLOW_API_TOKEN Optional Granoflow Local HTTP API token.
19
+ GRANOFLOW_MCP_CONFIG_PATH Optional path for MCP-owned non-secret config.
20
+ `);
21
+ }
22
+ if (process.argv.includes("--version") || process.argv.includes("-v")) {
23
+ process.stdout.write(`${SERVER_VERSION}\n`);
24
+ process.exit(0);
25
+ }
26
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
27
+ printHelp();
28
+ process.exit(0);
29
+ }
30
+ const server = new McpServer({
31
+ name: SERVER_NAME,
32
+ version: SERVER_VERSION,
33
+ });
34
+ registerGranoflowTools(server);
35
+ const transport = new StdioServerTransport();
36
+ await server.connect(transport);
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD,SAAS,SAAS;IAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,WAAW,IAAI,cAAc;;;;;;;;;;;;;CAatD,CAAC,CAAC;AACH,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IACtE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,cAAc,IAAI,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IACnE,SAAS,EAAE,CAAC;IACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,cAAc;CACxB,CAAC,CAAC;AAEH,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAE/B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const SERVER_NAME = "granoflow-mcp-server";
2
+ export declare const SERVER_VERSION = "0.1.0";
@@ -0,0 +1,3 @@
1
+ export const SERVER_NAME = "granoflow-mcp-server";
2
+ export const SERVER_VERSION = "0.1.0";
3
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,sBAAsB,CAAC;AAClD,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC"}