@studiometa/forge-mcp 0.1.0 → 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/dist/server.d.ts CHANGED
@@ -9,6 +9,8 @@
9
9
  *
10
10
  * Usage:
11
11
  * forge-mcp-server
12
+ * forge-mcp-server --read-only
13
+ * FORGE_READ_ONLY=true forge-mcp-server
12
14
  * PORT=3000 forge-mcp-server
13
15
  *
14
16
  * Endpoints:
@@ -19,8 +21,15 @@
19
21
  * GET /health - Health check
20
22
  */
21
23
  import { type Server } from "node:http";
24
+ /**
25
+ * Options for the HTTP server.
26
+ */
27
+ export interface HttpStartOptions {
28
+ /** When true, forge_write tool is not registered. */
29
+ readOnly?: boolean;
30
+ }
22
31
  /**
23
32
  * Start the HTTP server with Streamable HTTP transport.
24
33
  */
25
- export declare function startHttpServer(port?: number, host?: string): Promise<Server>;
34
+ export declare function startHttpServer(port?: number, host?: string, options?: HttpStartOptions): Promise<Server>;
26
35
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAStD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,GAAE,MAAqB,EAC3B,IAAI,GAAE,MAAqB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAwCjB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAUtD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,GAAE,MAAqB,EAC3B,IAAI,GAAE,MAAqB,EAC3B,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,MAAM,CAAC,CA8CjB"}
package/dist/server.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { t as VERSION } from "./version-Cw8OGt4r.js";
3
- import { a as SessionManager, n as createMcpRequestHandler, t as createHealthApp } from "./http-BJUKoZdb.js";
2
+ import { t as VERSION } from "./version-DaD5zvGh.js";
3
+ import { n as parseReadOnlyFlag } from "./src-BdwavqrN.js";
4
+ import { a as SessionManager, n as createMcpRequestHandler, t as createHealthApp } from "./http-CfjqK_e4.js";
4
5
  import { toNodeHandler } from "h3/node";
5
6
  import { createServer } from "node:http";
6
7
  /**
@@ -13,6 +14,8 @@ import { createServer } from "node:http";
13
14
  *
14
15
  * Usage:
15
16
  * forge-mcp-server
17
+ * forge-mcp-server --read-only
18
+ * FORGE_READ_ONLY=true forge-mcp-server
16
19
  * PORT=3000 forge-mcp-server
17
20
  *
18
21
  * Endpoints:
@@ -27,9 +30,10 @@ var DEFAULT_HOST = "0.0.0.0";
27
30
  /**
28
31
  * Start the HTTP server with Streamable HTTP transport.
29
32
  */
30
- function startHttpServer(port = DEFAULT_PORT, host = DEFAULT_HOST) {
33
+ function startHttpServer(port = DEFAULT_PORT, host = DEFAULT_HOST, options) {
31
34
  return new Promise((resolve) => {
32
- const handleMcp = createMcpRequestHandler(new SessionManager());
35
+ const readOnly = options?.readOnly ?? false;
36
+ const handleMcp = createMcpRequestHandler(new SessionManager(), { readOnly });
33
37
  const healthHandler = toNodeHandler(createHealthApp());
34
38
  const server = createServer(async (req, res) => {
35
39
  const url = req.url ?? "/";
@@ -41,7 +45,8 @@ function startHttpServer(port = DEFAULT_PORT, host = DEFAULT_HOST) {
41
45
  });
42
46
  server.listen(port, host, () => {
43
47
  const displayHost = host === "0.0.0.0" ? "localhost" : host;
44
- console.log(`Forge MCP server v${VERSION}`);
48
+ const mode = readOnly ? " (read-only)" : "";
49
+ console.log(`Forge MCP server v${VERSION}${mode}`);
45
50
  console.log(`Node.js ${process.version}`);
46
51
  console.log("");
47
52
  console.log(`Running at http://${displayHost}:${port}`);
@@ -53,12 +58,16 @@ function startHttpServer(port = DEFAULT_PORT, host = DEFAULT_HOST) {
53
58
  console.log("Authentication:");
54
59
  console.log(" Bearer token in Authorization header");
55
60
  console.log(" Token format: your raw Forge API token");
61
+ if (readOnly) {
62
+ console.log("");
63
+ console.log("Mode: READ-ONLY (write operations disabled)");
64
+ }
56
65
  console.log("");
57
66
  resolve(server);
58
67
  });
59
68
  });
60
69
  }
61
- if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("/forge-mcp-server") || process.argv[1]?.endsWith("\\forge-mcp-server")) startHttpServer(Number.parseInt(process.env.PORT || String(DEFAULT_PORT), 10), process.env.HOST || DEFAULT_HOST).catch((error) => {
70
+ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("/forge-mcp-server") || process.argv[1]?.endsWith("\\forge-mcp-server")) startHttpServer(Number.parseInt(process.env.PORT || String(DEFAULT_PORT), 10), process.env.HOST || DEFAULT_HOST, { readOnly: parseReadOnlyFlag() }).catch((error) => {
62
71
  console.error("Fatal error:", error);
63
72
  process.exit(1);
64
73
  });
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Forge MCP Server - HTTP Transport (Streamable HTTP)\n *\n * Implements the official MCP Streamable HTTP transport specification.\n * Credentials are passed via Bearer token in the Authorization header.\n *\n * Token format: raw Forge API token\n *\n * Usage:\n * forge-mcp-server\n * PORT=3000 forge-mcp-server\n *\n * Endpoints:\n * POST /mcp - MCP Streamable HTTP (JSON-RPC messages)\n * GET /mcp - MCP Streamable HTTP (SSE stream for server notifications)\n * DELETE /mcp - MCP Streamable HTTP (session termination)\n * GET / - Service info\n * GET /health - Health check\n */\n\nimport { toNodeHandler } from \"h3/node\";\nimport { createServer, type Server } from \"node:http\";\n\nimport { createHealthApp, createMcpRequestHandler } from \"./http.ts\";\nimport { SessionManager } from \"./sessions.ts\";\nimport { VERSION } from \"./version.ts\";\n\nconst DEFAULT_PORT = 3000;\nconst DEFAULT_HOST = \"0.0.0.0\";\n\n/**\n * Start the HTTP server with Streamable HTTP transport.\n */\nexport function startHttpServer(\n port: number = DEFAULT_PORT,\n host: string = DEFAULT_HOST,\n): Promise<Server> {\n return new Promise((resolve) => {\n const sessions = new SessionManager();\n const handleMcp = createMcpRequestHandler(sessions);\n const healthApp = createHealthApp();\n const healthHandler = toNodeHandler(healthApp);\n\n const server = createServer(async (req, res) => {\n const url = req.url ?? \"/\";\n\n // Route /mcp to MCP Streamable HTTP transport\n if (url === \"/mcp\" || url.startsWith(\"/mcp?\")) {\n await handleMcp(req, res);\n return;\n }\n\n // Everything else goes to h3 (health checks, service info)\n healthHandler(req, res);\n });\n\n server.listen(port, host, () => {\n const displayHost = host === \"0.0.0.0\" ? \"localhost\" : host;\n console.log(`Forge MCP server v${VERSION}`);\n console.log(`Node.js ${process.version}`);\n console.log(\"\");\n console.log(`Running at http://${displayHost}:${port}`);\n console.log(\"\");\n console.log(\"Endpoints:\");\n console.log(\n ` POST/GET/DELETE http://${displayHost}:${port}/mcp - MCP Streamable HTTP endpoint`,\n );\n console.log(` GET http://${displayHost}:${port}/health - Health check`);\n console.log(\"\");\n console.log(\"Authentication:\");\n console.log(\" Bearer token in Authorization header\");\n console.log(\" Token format: your raw Forge API token\");\n console.log(\"\");\n resolve(server);\n });\n });\n}\n\n// Start server when run directly\nconst isMainModule =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith(\"/forge-mcp-server\") ||\n process.argv[1]?.endsWith(\"\\\\forge-mcp-server\");\n\nif (isMainModule) {\n const port = Number.parseInt(process.env.PORT || String(DEFAULT_PORT), 10);\n const host = process.env.HOST || DEFAULT_HOST;\n\n startHttpServer(port, host).catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA6BA,IAAM,eAAe;AACrB,IAAM,eAAe;;;;AAKrB,SAAgB,gBACd,OAAe,cACf,OAAe,cACE;AACjB,QAAO,IAAI,SAAS,YAAY;EAE9B,MAAM,YAAY,wBADD,IAAI,gBAAgB,CACc;EAEnD,MAAM,gBAAgB,cADJ,iBAAiB,CACW;EAE9C,MAAM,SAAS,aAAa,OAAO,KAAK,QAAQ;GAC9C,MAAM,MAAM,IAAI,OAAO;AAGvB,OAAI,QAAQ,UAAU,IAAI,WAAW,QAAQ,EAAE;AAC7C,UAAM,UAAU,KAAK,IAAI;AACzB;;AAIF,iBAAc,KAAK,IAAI;IACvB;AAEF,SAAO,OAAO,MAAM,YAAY;GAC9B,MAAM,cAAc,SAAS,YAAY,cAAc;AACvD,WAAQ,IAAI,qBAAqB,UAAU;AAC3C,WAAQ,IAAI,WAAW,QAAQ,UAAU;AACzC,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,qBAAqB,YAAY,GAAG,OAAO;AACvD,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,aAAa;AACzB,WAAQ,IACN,4BAA4B,YAAY,GAAG,KAAK,qCACjD;AACD,WAAQ,IAAI,iBAAiB,YAAY,GAAG,KAAK,wBAAwB;AACzE,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,kBAAkB;AAC9B,WAAQ,IAAI,yCAAyC;AACrD,WAAQ,IAAI,2CAA2C;AACvD,WAAQ,IAAI,GAAG;AACf,WAAQ,OAAO;IACf;GACF;;AASJ,IAJE,OAAO,KAAK,QAAQ,UAAU,QAAQ,KAAK,QAC3C,QAAQ,KAAK,IAAI,SAAS,oBAAoB,IAC9C,QAAQ,KAAK,IAAI,SAAS,qBAAqB,CAM/C,iBAHa,OAAO,SAAS,QAAQ,IAAI,QAAQ,OAAO,aAAa,EAAE,GAAG,EAC7D,QAAQ,IAAI,QAAQ,aAEN,CAAC,OAAO,UAAU;AAC3C,SAAQ,MAAM,gBAAgB,MAAM;AACpC,SAAQ,KAAK,EAAE;EACf"}
1
+ {"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Forge MCP Server - HTTP Transport (Streamable HTTP)\n *\n * Implements the official MCP Streamable HTTP transport specification.\n * Credentials are passed via Bearer token in the Authorization header.\n *\n * Token format: raw Forge API token\n *\n * Usage:\n * forge-mcp-server\n * forge-mcp-server --read-only\n * FORGE_READ_ONLY=true forge-mcp-server\n * PORT=3000 forge-mcp-server\n *\n * Endpoints:\n * POST /mcp - MCP Streamable HTTP (JSON-RPC messages)\n * GET /mcp - MCP Streamable HTTP (SSE stream for server notifications)\n * DELETE /mcp - MCP Streamable HTTP (session termination)\n * GET / - Service info\n * GET /health - Health check\n */\n\nimport { toNodeHandler } from \"h3/node\";\nimport { createServer, type Server } from \"node:http\";\n\nimport { createHealthApp, createMcpRequestHandler } from \"./http.ts\";\nimport { parseReadOnlyFlag } from \"./index.ts\";\nimport { SessionManager } from \"./sessions.ts\";\nimport { VERSION } from \"./version.ts\";\n\nconst DEFAULT_PORT = 3000;\nconst DEFAULT_HOST = \"0.0.0.0\";\n\n/**\n * Options for the HTTP server.\n */\nexport interface HttpStartOptions {\n /** When true, forge_write tool is not registered. */\n readOnly?: boolean;\n}\n\n/**\n * Start the HTTP server with Streamable HTTP transport.\n */\nexport function startHttpServer(\n port: number = DEFAULT_PORT,\n host: string = DEFAULT_HOST,\n options?: HttpStartOptions,\n): Promise<Server> {\n return new Promise((resolve) => {\n const readOnly = options?.readOnly ?? false;\n const sessions = new SessionManager();\n const handleMcp = createMcpRequestHandler(sessions, { readOnly });\n const healthApp = createHealthApp();\n const healthHandler = toNodeHandler(healthApp);\n\n const server = createServer(async (req, res) => {\n const url = req.url ?? \"/\";\n\n // Route /mcp to MCP Streamable HTTP transport\n if (url === \"/mcp\" || url.startsWith(\"/mcp?\")) {\n await handleMcp(req, res);\n return;\n }\n\n // Everything else goes to h3 (health checks, service info)\n healthHandler(req, res);\n });\n\n server.listen(port, host, () => {\n const displayHost = host === \"0.0.0.0\" ? \"localhost\" : host;\n const mode = readOnly ? \" (read-only)\" : \"\";\n console.log(`Forge MCP server v${VERSION}${mode}`);\n console.log(`Node.js ${process.version}`);\n console.log(\"\");\n console.log(`Running at http://${displayHost}:${port}`);\n console.log(\"\");\n console.log(\"Endpoints:\");\n console.log(\n ` POST/GET/DELETE http://${displayHost}:${port}/mcp - MCP Streamable HTTP endpoint`,\n );\n console.log(` GET http://${displayHost}:${port}/health - Health check`);\n console.log(\"\");\n console.log(\"Authentication:\");\n console.log(\" Bearer token in Authorization header\");\n console.log(\" Token format: your raw Forge API token\");\n if (readOnly) {\n console.log(\"\");\n console.log(\"Mode: READ-ONLY (write operations disabled)\");\n }\n console.log(\"\");\n resolve(server);\n });\n });\n}\n\n// Start server when run directly\nconst isMainModule =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith(\"/forge-mcp-server\") ||\n process.argv[1]?.endsWith(\"\\\\forge-mcp-server\");\n\nif (isMainModule) {\n const port = Number.parseInt(process.env.PORT || String(DEFAULT_PORT), 10);\n const host = process.env.HOST || DEFAULT_HOST;\n const readOnly = parseReadOnlyFlag();\n\n startHttpServer(port, host, { readOnly }).catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAM,eAAe;AACrB,IAAM,eAAe;;;;AAarB,SAAgB,gBACd,OAAe,cACf,OAAe,cACf,SACiB;AACjB,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,WAAW,SAAS,YAAY;EAEtC,MAAM,YAAY,wBADD,IAAI,gBAAgB,EACe,EAAE,UAAU,CAAC;EAEjE,MAAM,gBAAgB,cADJ,iBAAiB,CACW;EAE9C,MAAM,SAAS,aAAa,OAAO,KAAK,QAAQ;GAC9C,MAAM,MAAM,IAAI,OAAO;AAGvB,OAAI,QAAQ,UAAU,IAAI,WAAW,QAAQ,EAAE;AAC7C,UAAM,UAAU,KAAK,IAAI;AACzB;;AAIF,iBAAc,KAAK,IAAI;IACvB;AAEF,SAAO,OAAO,MAAM,YAAY;GAC9B,MAAM,cAAc,SAAS,YAAY,cAAc;GACvD,MAAM,OAAO,WAAW,iBAAiB;AACzC,WAAQ,IAAI,qBAAqB,UAAU,OAAO;AAClD,WAAQ,IAAI,WAAW,QAAQ,UAAU;AACzC,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,qBAAqB,YAAY,GAAG,OAAO;AACvD,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,aAAa;AACzB,WAAQ,IACN,4BAA4B,YAAY,GAAG,KAAK,qCACjD;AACD,WAAQ,IAAI,iBAAiB,YAAY,GAAG,KAAK,wBAAwB;AACzE,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,kBAAkB;AAC9B,WAAQ,IAAI,yCAAyC;AACrD,WAAQ,IAAI,2CAA2C;AACvD,OAAI,UAAU;AACZ,YAAQ,IAAI,GAAG;AACf,YAAQ,IAAI,8CAA8C;;AAE5D,WAAQ,IAAI,GAAG;AACf,WAAQ,OAAO;IACf;GACF;;AASJ,IAJE,OAAO,KAAK,QAAQ,UAAU,QAAQ,KAAK,QAC3C,QAAQ,KAAK,IAAI,SAAS,oBAAoB,IAC9C,QAAQ,KAAK,IAAI,SAAS,qBAAqB,CAO/C,iBAJa,OAAO,SAAS,QAAQ,IAAI,QAAQ,OAAO,aAAa,EAAE,GAAG,EAC7D,QAAQ,IAAI,QAAQ,cAGL,EAAE,UAFb,mBAAmB,EAEI,CAAC,CAAC,OAAO,UAAU;AACzD,SAAQ,MAAM,gBAAgB,MAAM;AACpC,SAAQ,KAAK,EAAE;EACf"}
@@ -0,0 +1,189 @@
1
+ import { a as INSTRUCTIONS, i as getTools, n as executeToolWithCredentials, r as STDIO_ONLY_TOOLS, t as VERSION } from "./version-DaD5zvGh.js";
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+ import { getToken, setToken } from "@studiometa/forge-api";
6
+ /**
7
+ * Get all available tools (including stdio-only configuration tools).
8
+ *
9
+ * @param options - Optional filtering. When `readOnly` is true, forge_write is excluded.
10
+ */
11
+ function getAvailableTools(options) {
12
+ return [...getTools(options), ...STDIO_ONLY_TOOLS];
13
+ }
14
+ /**
15
+ * Handle the forge_configure tool.
16
+ */
17
+ function handleConfigureTool(args) {
18
+ if (!args.apiToken || typeof args.apiToken !== "string" || args.apiToken.trim().length === 0) return {
19
+ content: [{
20
+ type: "text",
21
+ text: "Error: apiToken is required and must be a non-empty string."
22
+ }],
23
+ structuredContent: {
24
+ success: false,
25
+ error: "apiToken is required and must be a non-empty string."
26
+ },
27
+ isError: true
28
+ };
29
+ setToken(args.apiToken);
30
+ const data = {
31
+ success: true,
32
+ message: "Laravel Forge API token configured successfully",
33
+ apiToken: `***${args.apiToken.slice(-4)}`
34
+ };
35
+ return {
36
+ content: [{
37
+ type: "text",
38
+ text: JSON.stringify(data, null, 2)
39
+ }],
40
+ structuredContent: data
41
+ };
42
+ }
43
+ /**
44
+ * Handle the forge_get_config tool.
45
+ */
46
+ function handleGetConfigTool() {
47
+ const token = getToken();
48
+ const data = {
49
+ apiToken: token ? `***${token.slice(-4)}` : "not configured",
50
+ configured: !!token
51
+ };
52
+ return {
53
+ content: [{
54
+ type: "text",
55
+ text: JSON.stringify(data, null, 2)
56
+ }],
57
+ structuredContent: data
58
+ };
59
+ }
60
+ /**
61
+ * Handle a tool call request.
62
+ *
63
+ * Routes to the appropriate handler based on tool name:
64
+ * - forge_configure / forge_get_config — stdio-only config tools
65
+ * - forge — read-only operations (list, get, help, schema)
66
+ * - forge_write — write operations (create, update, delete, deploy, etc.)
67
+ */
68
+ async function handleToolCall(name, args, options) {
69
+ if (name === "forge_configure") return handleConfigureTool(args);
70
+ if (name === "forge_get_config") return handleGetConfigTool();
71
+ if (name === "forge_write" && options?.readOnly) return {
72
+ content: [{
73
+ type: "text",
74
+ text: "Error: Server is running in read-only mode. Write operations are disabled."
75
+ }],
76
+ structuredContent: {
77
+ success: false,
78
+ error: "Server is running in read-only mode. Write operations are disabled."
79
+ },
80
+ isError: true
81
+ };
82
+ if (name === "forge" || name === "forge_write") {
83
+ const apiToken = getToken();
84
+ if (!apiToken) return {
85
+ content: [{
86
+ type: "text",
87
+ text: "Error: Forge API token not configured. Use \"forge_configure\" tool or set FORGE_API_TOKEN environment variable."
88
+ }],
89
+ structuredContent: {
90
+ success: false,
91
+ error: "Forge API token not configured. Use \"forge_configure\" tool or set FORGE_API_TOKEN environment variable."
92
+ },
93
+ isError: true
94
+ };
95
+ return executeToolWithCredentials(name, args, { apiToken });
96
+ }
97
+ return {
98
+ content: [{
99
+ type: "text",
100
+ text: `Error: Unknown tool "${name}".`
101
+ }],
102
+ structuredContent: {
103
+ success: false,
104
+ error: `Unknown tool "${name}".`
105
+ },
106
+ isError: true
107
+ };
108
+ }
109
+ /**
110
+ * Forge MCP Server — Stdio Transport
111
+ *
112
+ * This is the local execution mode using stdio transport.
113
+ * For remote HTTP deployment, use server.ts instead.
114
+ *
115
+ * Usage:
116
+ * npx @studiometa/forge-mcp
117
+ * npx @studiometa/forge-mcp --read-only
118
+ * FORGE_READ_ONLY=true npx @studiometa/forge-mcp
119
+ *
120
+ * Or in Claude Desktop config:
121
+ * {
122
+ * "mcpServers": {
123
+ * "forge": {
124
+ * "command": "forge-mcp",
125
+ * "args": ["--read-only"],
126
+ * "env": { "FORGE_API_TOKEN": "your-token" }
127
+ * }
128
+ * }
129
+ * }
130
+ */
131
+ /**
132
+ * Parse read-only flag from process.argv and environment.
133
+ */
134
+ function parseReadOnlyFlag() {
135
+ return process.argv.includes("--read-only") || process.env.FORGE_READ_ONLY === "true";
136
+ }
137
+ /**
138
+ * Create and configure the MCP server.
139
+ */
140
+ function createStdioServer(options) {
141
+ const readOnly = options?.readOnly ?? false;
142
+ const server = new Server({
143
+ name: "forge-mcp",
144
+ version: VERSION
145
+ }, {
146
+ capabilities: { tools: {} },
147
+ instructions: INSTRUCTIONS
148
+ });
149
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
150
+ return { tools: getAvailableTools({ readOnly }) };
151
+ });
152
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
153
+ const { name, arguments: args } = request.params;
154
+ try {
155
+ return await handleToolCall(name, args ?? {}, { readOnly });
156
+ } catch (error) {
157
+ const message = error instanceof Error ? error.message : String(error);
158
+ return {
159
+ content: [{
160
+ type: "text",
161
+ text: `Error: ${message}`
162
+ }],
163
+ structuredContent: {
164
+ success: false,
165
+ error: message
166
+ },
167
+ isError: true
168
+ };
169
+ }
170
+ });
171
+ return server;
172
+ }
173
+ /**
174
+ * Start the stdio server.
175
+ */
176
+ async function startStdioServer(options) {
177
+ const server = createStdioServer(options);
178
+ const transport = new StdioServerTransport();
179
+ await server.connect(transport);
180
+ const mode = options?.readOnly ? " (read-only)" : "";
181
+ console.error(`Forge MCP server v${VERSION} running on stdio${mode}`);
182
+ }
183
+ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("/forge-mcp") || process.argv[1]?.endsWith("\\forge-mcp")) startStdioServer({ readOnly: parseReadOnlyFlag() }).catch((error) => {
184
+ console.error("Fatal error:", error);
185
+ process.exit(1);
186
+ });
187
+ export { parseReadOnlyFlag as n, startStdioServer as r, createStdioServer as t };
188
+
189
+ //# sourceMappingURL=src-BdwavqrN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-BdwavqrN.js","names":[],"sources":["../src/stdio.ts","../src/index.ts"],"sourcesContent":["import { getToken, setToken } from \"@studiometa/forge-api\";\n\nimport type { ToolResult } from \"./handlers/types.ts\";\n\nimport { executeToolWithCredentials } from \"./handlers/index.ts\";\nimport { getTools, STDIO_ONLY_TOOLS } from \"./tools.ts\";\nimport type { GetToolsOptions } from \"./tools.ts\";\n\nexport type { ToolResult };\n\n/**\n * Get all available tools (including stdio-only configuration tools).\n *\n * @param options - Optional filtering. When `readOnly` is true, forge_write is excluded.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function getAvailableTools(options?: GetToolsOptions): any[] {\n return [...getTools(options), ...STDIO_ONLY_TOOLS];\n}\n\n/**\n * Handle the forge_configure tool.\n */\nexport function handleConfigureTool(args: { apiToken: string }): ToolResult {\n if (!args.apiToken || typeof args.apiToken !== \"string\" || args.apiToken.trim().length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"Error: apiToken is required and must be a non-empty string.\",\n },\n ],\n structuredContent: {\n success: false,\n error: \"apiToken is required and must be a non-empty string.\",\n },\n isError: true,\n };\n }\n\n setToken(args.apiToken);\n\n const maskedToken = `***${args.apiToken.slice(-4)}`;\n const data = {\n success: true,\n message: \"Laravel Forge API token configured successfully\",\n apiToken: maskedToken,\n };\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(data, null, 2),\n },\n ],\n structuredContent: data,\n };\n}\n\n/**\n * Handle the forge_get_config tool.\n */\nexport function handleGetConfigTool(): ToolResult {\n const token = getToken();\n\n const data = {\n apiToken: token ? `***${token.slice(-4)}` : \"not configured\",\n configured: !!token,\n };\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(data, null, 2),\n },\n ],\n structuredContent: data,\n };\n}\n\n/**\n * Options for handleToolCall.\n */\nexport interface HandleToolCallOptions {\n /** When true, forge_write is rejected with an error. */\n readOnly?: boolean;\n}\n\n/**\n * Handle a tool call request.\n *\n * Routes to the appropriate handler based on tool name:\n * - forge_configure / forge_get_config — stdio-only config tools\n * - forge — read-only operations (list, get, help, schema)\n * - forge_write — write operations (create, update, delete, deploy, etc.)\n */\nexport async function handleToolCall(\n name: string,\n args: Record<string, unknown>,\n options?: HandleToolCallOptions,\n): Promise<ToolResult> {\n if (name === \"forge_configure\") {\n return handleConfigureTool(args as { apiToken: string });\n }\n\n if (name === \"forge_get_config\") {\n return handleGetConfigTool();\n }\n\n // Reject forge_write in read-only mode\n if (name === \"forge_write\" && options?.readOnly) {\n return {\n content: [\n {\n type: \"text\",\n text: \"Error: Server is running in read-only mode. Write operations are disabled.\",\n },\n ],\n structuredContent: {\n success: false,\n error: \"Server is running in read-only mode. Write operations are disabled.\",\n },\n isError: true,\n };\n }\n\n // Both forge and forge_write require authentication\n if (name === \"forge\" || name === \"forge_write\") {\n const apiToken = getToken();\n if (!apiToken) {\n return {\n content: [\n {\n type: \"text\",\n text: 'Error: Forge API token not configured. Use \"forge_configure\" tool or set FORGE_API_TOKEN environment variable.',\n },\n ],\n structuredContent: {\n success: false,\n error:\n 'Forge API token not configured. Use \"forge_configure\" tool or set FORGE_API_TOKEN environment variable.',\n },\n isError: true,\n };\n }\n\n return executeToolWithCredentials(name, args, { apiToken });\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Unknown tool \"${name}\".`,\n },\n ],\n structuredContent: { success: false, error: `Unknown tool \"${name}\".` },\n isError: true,\n };\n}\n","#!/usr/bin/env node\n\n/**\n * Forge MCP Server — Stdio Transport\n *\n * This is the local execution mode using stdio transport.\n * For remote HTTP deployment, use server.ts instead.\n *\n * Usage:\n * npx @studiometa/forge-mcp\n * npx @studiometa/forge-mcp --read-only\n * FORGE_READ_ONLY=true npx @studiometa/forge-mcp\n *\n * Or in Claude Desktop config:\n * {\n * \"mcpServers\": {\n * \"forge\": {\n * \"command\": \"forge-mcp\",\n * \"args\": [\"--read-only\"],\n * \"env\": { \"FORGE_API_TOKEN\": \"your-token\" }\n * }\n * }\n * }\n */\n\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\n\nimport { INSTRUCTIONS } from \"./instructions.ts\";\nimport { getAvailableTools, handleToolCall } from \"./stdio.ts\";\nimport { VERSION } from \"./version.ts\";\n\n/**\n * Options for the stdio MCP server.\n */\nexport interface StdioServerOptions {\n /** When true, forge_write tool is not registered and write operations are rejected. */\n readOnly?: boolean;\n}\n\n/**\n * Parse read-only flag from process.argv and environment.\n */\nexport function parseReadOnlyFlag(): boolean {\n return process.argv.includes(\"--read-only\") || process.env.FORGE_READ_ONLY === \"true\";\n}\n\n/**\n * Create and configure the MCP server.\n */\nexport function createStdioServer(options?: StdioServerOptions): Server {\n const readOnly = options?.readOnly ?? false;\n\n const server = new Server(\n {\n name: \"forge-mcp\",\n version: VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n instructions: INSTRUCTIONS,\n },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: getAvailableTools({ readOnly }) };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n const result = await handleToolCall(name, (args as Record<string, unknown>) ?? {}, {\n readOnly,\n });\n return result as unknown as Record<string, unknown>;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: \"text\" as const, text: `Error: ${message}` }],\n structuredContent: { success: false, error: message },\n isError: true,\n };\n }\n });\n\n return server;\n}\n\n/**\n * Start the stdio server.\n */\nexport async function startStdioServer(options?: StdioServerOptions): Promise<void> {\n const server = createStdioServer(options);\n const transport = new StdioServerTransport();\n await server.connect(transport);\n const mode = options?.readOnly ? \" (read-only)\" : \"\";\n console.error(`Forge MCP server v${VERSION} running on stdio${mode}`);\n}\n\n// Start server when run directly\nconst isMainModule =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith(\"/forge-mcp\") ||\n process.argv[1]?.endsWith(\"\\\\forge-mcp\");\n\nif (isMainModule) {\n const readOnly = parseReadOnlyFlag();\n startStdioServer({ readOnly }).catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n });\n}\n"],"mappings":"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"}
package/dist/stdio.d.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  import type { ToolResult } from "./handlers/types.ts";
2
+ import type { GetToolsOptions } from "./tools.ts";
2
3
  export type { ToolResult };
3
4
  /**
4
5
  * Get all available tools (including stdio-only configuration tools).
6
+ *
7
+ * @param options - Optional filtering. When `readOnly` is true, forge_write is excluded.
5
8
  */
6
- export declare function getAvailableTools(): any[];
9
+ export declare function getAvailableTools(options?: GetToolsOptions): any[];
7
10
  /**
8
11
  * Handle the forge_configure tool.
9
12
  */
@@ -14,8 +17,20 @@ export declare function handleConfigureTool(args: {
14
17
  * Handle the forge_get_config tool.
15
18
  */
16
19
  export declare function handleGetConfigTool(): ToolResult;
20
+ /**
21
+ * Options for handleToolCall.
22
+ */
23
+ export interface HandleToolCallOptions {
24
+ /** When true, forge_write is rejected with an error. */
25
+ readOnly?: boolean;
26
+ }
17
27
  /**
18
28
  * Handle a tool call request.
29
+ *
30
+ * Routes to the appropriate handler based on tool name:
31
+ * - forge_configure / forge_get_config — stdio-only config tools
32
+ * - forge — read-only operations (list, get, help, schema)
33
+ * - forge_write — write operations (create, update, delete, deploy, etc.)
19
34
  */
20
- export declare function handleToolCall(name: string, args: Record<string, unknown>): Promise<ToolResult>;
35
+ export declare function handleToolCall(name: string, args: Record<string, unknown>, options?: HandleToolCallOptions): Promise<ToolResult>;
21
36
  //# sourceMappingURL=stdio.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAKtD,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B;;GAEG;AAEH,wBAAgB,iBAAiB,IAAI,GAAG,EAAE,CAEzC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CA+B1E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,UAAU,CAkBhD;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,UAAU,CAAC,CAwBrB"}
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAItD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B;;;;GAIG;AAEH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,GAAG,EAAE,CAElE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAmC1E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,UAAU,CAiBhD;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,wDAAwD;IACxD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,UAAU,CAAC,CA2DrB"}
package/dist/tools.d.ts CHANGED
@@ -1,22 +1,47 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
1
2
  /**
2
- * Tool type matching MCP SDK expectations.
3
+ * Read-only actions safe operations that don't modify server state.
3
4
  */
4
- interface Tool {
5
- name: string;
6
- description: string;
7
- annotations?: Record<string, unknown>;
8
- inputSchema: Record<string, unknown>;
9
- }
5
+ export declare const READ_ACTIONS: readonly ["list", "get", "help", "schema"];
6
+ /**
7
+ * Write actions — operations that modify server state.
8
+ * These are separated into the `forge_write` tool for safety.
9
+ */
10
+ export declare const WRITE_ACTIONS: readonly ["create", "update", "delete", "deploy", "reboot", "restart", "activate", "run"];
11
+ export type ReadAction = (typeof READ_ACTIONS)[number];
12
+ export type WriteAction = (typeof WRITE_ACTIONS)[number];
13
+ /**
14
+ * Check if an action is a write action.
15
+ */
16
+ export declare function isWriteAction(action: string): action is WriteAction;
17
+ /**
18
+ * Check if an action is a read action.
19
+ */
20
+ export declare function isReadAction(action: string): action is ReadAction;
10
21
  /**
11
- * Single consolidated tool for Laravel Forge MCP server.
22
+ * Core tools available in both stdio and HTTP transports.
12
23
  *
13
- * The resource/action enums and description are derived from
14
- * the shared constants in forge-core the single source of truth.
24
+ * Split into two tools for safety:
25
+ * - `forge` read-only operations (auto-approvable by MCP clients)
26
+ * - `forge_write` — write operations (always requires confirmation)
15
27
  */
16
28
  export declare const TOOLS: Tool[];
29
+ /**
30
+ * Options for filtering available tools.
31
+ */
32
+ export interface GetToolsOptions {
33
+ /** When true, only read-only tools are returned (forge_write is excluded). */
34
+ readOnly?: boolean;
35
+ }
36
+ /**
37
+ * Get the list of core tools, optionally filtered.
38
+ *
39
+ * In read-only mode, forge_write is excluded entirely — it won't appear
40
+ * in the tool listing and cannot be called.
41
+ */
42
+ export declare function getTools(options?: GetToolsOptions): Tool[];
17
43
  /**
18
44
  * Additional tools only available in stdio mode.
19
45
  */
20
46
  export declare const STDIO_ONLY_TOOLS: Tool[];
21
- export {};
22
47
  //# sourceMappingURL=tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,UAAU,IAAI;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAgBD;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,IAAI,EA2EvB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAkClC,CAAC"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAI/D;;GAEG;AACH,eAAO,MAAM,YAAY,4CAA6C,CAAC;AAEvE;;;GAGG;AACH,eAAO,MAAM,aAAa,2FAShB,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AACvD,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,WAAW,CAEnE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,UAAU,CAEjE;AA0OD;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,EAAE,IAAI,EAAwC,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,EAAE,CAK1D;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAsDlC,CAAC"}