@studiometa/forge-mcp 0.1.0 → 0.2.1

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/index.js CHANGED
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import { a as INSTRUCTIONS, i as executeToolWithCredentials, n as STDIO_ONLY_TOOLS, r as TOOLS, t as VERSION } from "./version-Cw8OGt4r.js";
2
+ import { t as parseReadOnlyFlag } from "./flags-LFbdErsZ.js";
3
+ import { a as INSTRUCTIONS, i as getTools, n as executeToolWithCredentials, r as STDIO_ONLY_TOOLS, t as VERSION } from "./version-BmEJceWJ.js";
3
4
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
6
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
6
7
  import { getToken, setToken } from "@studiometa/forge-api";
7
8
  /**
8
9
  * Get all available tools (including stdio-only configuration tools).
10
+ *
11
+ * @param options - Optional filtering. When `readOnly` is true, forge_write is excluded.
9
12
  */
10
- function getAvailableTools() {
11
- return [...TOOLS, ...STDIO_ONLY_TOOLS];
13
+ function getAvailableTools(options) {
14
+ return [...getTools(options), ...STDIO_ONLY_TOOLS];
12
15
  }
13
16
  /**
14
17
  * Handle the forge_configure tool.
@@ -19,46 +22,91 @@ function handleConfigureTool(args) {
19
22
  type: "text",
20
23
  text: "Error: apiToken is required and must be a non-empty string."
21
24
  }],
25
+ structuredContent: {
26
+ success: false,
27
+ error: "apiToken is required and must be a non-empty string."
28
+ },
22
29
  isError: true
23
30
  };
24
31
  setToken(args.apiToken);
25
- return { content: [{
26
- type: "text",
27
- text: JSON.stringify({
28
- success: true,
29
- message: "Laravel Forge API token configured successfully",
30
- apiToken: `***${args.apiToken.slice(-4)}`
31
- }, null, 2)
32
- }] };
32
+ const data = {
33
+ success: true,
34
+ message: "Laravel Forge API token configured successfully",
35
+ apiToken: `***${args.apiToken.slice(-4)}`
36
+ };
37
+ return {
38
+ content: [{
39
+ type: "text",
40
+ text: JSON.stringify(data, null, 2)
41
+ }],
42
+ structuredContent: data
43
+ };
33
44
  }
34
45
  /**
35
46
  * Handle the forge_get_config tool.
36
47
  */
37
48
  function handleGetConfigTool() {
38
49
  const token = getToken();
39
- return { content: [{
40
- type: "text",
41
- text: JSON.stringify({
42
- apiToken: token ? `***${token.slice(-4)}` : "not configured",
43
- configured: !!token
44
- }, null, 2)
45
- }] };
50
+ const data = {
51
+ apiToken: token ? `***${token.slice(-4)}` : "not configured",
52
+ configured: !!token
53
+ };
54
+ return {
55
+ content: [{
56
+ type: "text",
57
+ text: JSON.stringify(data, null, 2)
58
+ }],
59
+ structuredContent: data
60
+ };
46
61
  }
47
62
  /**
48
63
  * Handle a tool call request.
64
+ *
65
+ * Routes to the appropriate handler based on tool name:
66
+ * - forge_configure / forge_get_config — stdio-only config tools
67
+ * - forge — read-only operations (list, get, help, schema)
68
+ * - forge_write — write operations (create, update, delete, deploy, etc.)
49
69
  */
50
- async function handleToolCall(name, args) {
70
+ async function handleToolCall(name, args, options) {
51
71
  if (name === "forge_configure") return handleConfigureTool(args);
52
72
  if (name === "forge_get_config") return handleGetConfigTool();
53
- const apiToken = getToken();
54
- if (!apiToken) return {
73
+ if (name === "forge_write" && options?.readOnly) return {
74
+ content: [{
75
+ type: "text",
76
+ text: "Error: Server is running in read-only mode. Write operations are disabled."
77
+ }],
78
+ structuredContent: {
79
+ success: false,
80
+ error: "Server is running in read-only mode. Write operations are disabled."
81
+ },
82
+ isError: true
83
+ };
84
+ if (name === "forge" || name === "forge_write") {
85
+ const apiToken = getToken();
86
+ if (!apiToken) return {
87
+ content: [{
88
+ type: "text",
89
+ text: "Error: Forge API token not configured. Use \"forge_configure\" tool or set FORGE_API_TOKEN environment variable."
90
+ }],
91
+ structuredContent: {
92
+ success: false,
93
+ error: "Forge API token not configured. Use \"forge_configure\" tool or set FORGE_API_TOKEN environment variable."
94
+ },
95
+ isError: true
96
+ };
97
+ return executeToolWithCredentials(name, args, { apiToken });
98
+ }
99
+ return {
55
100
  content: [{
56
101
  type: "text",
57
- text: "Error: Forge API token not configured. Use \"forge_configure\" tool or set FORGE_API_TOKEN environment variable."
102
+ text: `Error: Unknown tool "${name}".`
58
103
  }],
104
+ structuredContent: {
105
+ success: false,
106
+ error: `Unknown tool "${name}".`
107
+ },
59
108
  isError: true
60
109
  };
61
- return executeToolWithCredentials(name, args, { apiToken });
62
110
  }
63
111
  /**
64
112
  * Forge MCP Server — Stdio Transport
@@ -68,12 +116,15 @@ async function handleToolCall(name, args) {
68
116
  *
69
117
  * Usage:
70
118
  * npx @studiometa/forge-mcp
119
+ * npx @studiometa/forge-mcp --read-only
120
+ * FORGE_READ_ONLY=true npx @studiometa/forge-mcp
71
121
  *
72
122
  * Or in Claude Desktop config:
73
123
  * {
74
124
  * "mcpServers": {
75
125
  * "forge": {
76
126
  * "command": "forge-mcp",
127
+ * "args": ["--read-only"],
77
128
  * "env": { "FORGE_API_TOKEN": "your-token" }
78
129
  * }
79
130
  * }
@@ -82,7 +133,8 @@ async function handleToolCall(name, args) {
82
133
  /**
83
134
  * Create and configure the MCP server.
84
135
  */
85
- function createStdioServer() {
136
+ function createStdioServer(options) {
137
+ const readOnly = options?.readOnly ?? false;
86
138
  const server = new Server({
87
139
  name: "forge-mcp",
88
140
  version: VERSION
@@ -91,18 +143,23 @@ function createStdioServer() {
91
143
  instructions: INSTRUCTIONS
92
144
  });
93
145
  server.setRequestHandler(ListToolsRequestSchema, async () => {
94
- return { tools: getAvailableTools() };
146
+ return { tools: getAvailableTools({ readOnly }) };
95
147
  });
96
148
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
97
149
  const { name, arguments: args } = request.params;
98
150
  try {
99
- return await handleToolCall(name, args ?? {});
151
+ return await handleToolCall(name, args ?? {}, { readOnly });
100
152
  } catch (error) {
153
+ const message = error instanceof Error ? error.message : String(error);
101
154
  return {
102
155
  content: [{
103
156
  type: "text",
104
- text: `Error: ${error instanceof Error ? error.message : String(error)}`
157
+ text: `Error: ${message}`
105
158
  }],
159
+ structuredContent: {
160
+ success: false,
161
+ error: message
162
+ },
106
163
  isError: true
107
164
  };
108
165
  }
@@ -112,16 +169,17 @@ function createStdioServer() {
112
169
  /**
113
170
  * Start the stdio server.
114
171
  */
115
- async function startStdioServer() {
116
- const server = createStdioServer();
172
+ async function startStdioServer(options) {
173
+ const server = createStdioServer(options);
117
174
  const transport = new StdioServerTransport();
118
175
  await server.connect(transport);
119
- console.error(`Forge MCP server v${VERSION} running on stdio`);
176
+ const mode = options?.readOnly ? " (read-only)" : "";
177
+ console.error(`Forge MCP server v${VERSION} running on stdio${mode}`);
120
178
  }
121
- if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("/forge-mcp") || process.argv[1]?.endsWith("\\forge-mcp")) startStdioServer().catch((error) => {
179
+ 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) => {
122
180
  console.error("Fatal error:", error);
123
181
  process.exit(1);
124
182
  });
125
- export { createStdioServer, startStdioServer };
183
+ export { createStdioServer, parseReadOnlyFlag, startStdioServer };
126
184
 
127
185
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.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 { STDIO_ONLY_TOOLS, TOOLS } from \"./tools.ts\";\n\nexport type { ToolResult };\n\n/**\n * Get all available tools (including stdio-only configuration tools).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function getAvailableTools(): any[] {\n return [...TOOLS, ...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 isError: true,\n };\n }\n\n setToken(args.apiToken);\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(\n {\n success: true,\n message: \"Laravel Forge API token configured successfully\",\n apiToken: `***${args.apiToken.slice(-4)}`,\n },\n null,\n 2,\n ),\n },\n ],\n };\n}\n\n/**\n * Handle the forge_get_config tool.\n */\nexport function handleGetConfigTool(): ToolResult {\n const token = getToken();\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(\n {\n apiToken: token ? `***${token.slice(-4)}` : \"not configured\",\n configured: !!token,\n },\n null,\n 2,\n ),\n },\n ],\n };\n}\n\n/**\n * Handle a tool call request.\n */\nexport async function handleToolCall(\n name: string,\n args: Record<string, unknown>,\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 // Get API token\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 isError: true,\n };\n }\n\n return executeToolWithCredentials(name, args, { apiToken });\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 *\n * Or in Claude Desktop config:\n * {\n * \"mcpServers\": {\n * \"forge\": {\n * \"command\": \"forge-mcp\",\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 * Create and configure the MCP server.\n */\nexport function createStdioServer(): Server {\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() };\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 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 isError: true,\n };\n }\n });\n\n return server;\n}\n\n/**\n * Start the stdio server.\n */\nexport async function startStdioServer(): Promise<void> {\n const server = createStdioServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error(`Forge MCP server v${VERSION} running on stdio`);\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 startStdioServer().catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;;;;AAaA,SAAgB,oBAA2B;AACzC,QAAO,CAAC,GAAG,OAAO,GAAG,iBAAiB;;;;;AAMxC,SAAgB,oBAAoB,MAAwC;AAC1E,KAAI,CAAC,KAAK,YAAY,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,MAAM,CAAC,WAAW,EACzF,QAAO;EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM;GACP,CACF;EACD,SAAS;EACV;AAGH,UAAS,KAAK,SAAS;AAEvB,QAAO,EACL,SAAS,CACP;EACE,MAAM;EACN,MAAM,KAAK,UACT;GACE,SAAS;GACT,SAAS;GACT,UAAU,MAAM,KAAK,SAAS,MAAM,GAAG;GACxC,EACD,MACA,EACD;EACF,CACF,EACF;;;;;AAMH,SAAgB,sBAAkC;CAChD,MAAM,QAAQ,UAAU;AAExB,QAAO,EACL,SAAS,CACP;EACE,MAAM;EACN,MAAM,KAAK,UACT;GACE,UAAU,QAAQ,MAAM,MAAM,MAAM,GAAG,KAAK;GAC5C,YAAY,CAAC,CAAC;GACf,EACD,MACA,EACD;EACF,CACF,EACF;;;;;AAMH,eAAsB,eACpB,MACA,MACqB;AACrB,KAAI,SAAS,kBACX,QAAO,oBAAoB,KAA6B;AAG1D,KAAI,SAAS,mBACX,QAAO,qBAAqB;CAI9B,MAAM,WAAW,UAAU;AAC3B,KAAI,CAAC,SACH,QAAO;EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM;GACP,CACF;EACD,SAAS;EACV;AAGH,QAAO,2BAA2B,MAAM,MAAM,EAAE,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;ACxE7D,SAAgB,oBAA4B;CAC1C,MAAM,SAAS,IAAI,OACjB;EACE,MAAM;EACN,SAAS;EACV,EACD;EACE,cAAc,EACZ,OAAO,EAAE,EACV;EACD,cAAc;EACf,CACF;AAED,QAAO,kBAAkB,wBAAwB,YAAY;AAC3D,SAAO,EAAE,OAAO,mBAAmB,EAAE;GACrC;AAEF,QAAO,kBAAkB,uBAAuB,OAAO,YAAY;EACjE,MAAM,EAAE,MAAM,WAAW,SAAS,QAAQ;AAE1C,MAAI;AAEF,UADe,MAAM,eAAe,MAAO,QAAoC,EAAE,CAAC;WAE3E,OAAO;AAEd,UAAO;IACL,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAF3B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;KAEN,CAAC;IAC/D,SAAS;IACV;;GAEH;AAEF,QAAO;;;;;AAMT,eAAsB,mBAAkC;CACtD,MAAM,SAAS,mBAAmB;CAClC,MAAM,YAAY,IAAI,sBAAsB;AAC5C,OAAM,OAAO,QAAQ,UAAU;AAC/B,SAAQ,MAAM,qBAAqB,QAAQ,mBAAmB;;AAShE,IAJE,OAAO,KAAK,QAAQ,UAAU,QAAQ,KAAK,QAC3C,QAAQ,KAAK,IAAI,SAAS,aAAa,IACvC,QAAQ,KAAK,IAAI,SAAS,cAAc,CAGxC,mBAAkB,CAAC,OAAO,UAAU;AAClC,SAAQ,MAAM,gBAAgB,MAAM;AACpC,SAAQ,KAAK,EAAE;EACf"}
1
+ {"version":3,"file":"index.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 { parseReadOnlyFlag } from \"./flags.ts\";\nimport { INSTRUCTIONS } from \"./instructions.ts\";\nimport { getAvailableTools, handleToolCall } from \"./stdio.ts\";\nimport { VERSION } from \"./version.ts\";\n\n// Re-export so consumers can still import from the main entry point\nexport { parseReadOnlyFlag } from \"./flags.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 * 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/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 parseReadOnlyFlag } from "./flags-LFbdErsZ.js";
3
+ import { t as VERSION } from "./version-BmEJceWJ.js";
4
+ import { a as SessionManager, n as createMcpRequestHandler, t as createHealthApp } from "./http-w0DliUHY.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 \"./flags.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"}
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"}