UNPKG

@bretterer/forge-mcp-server

Version:

Laravel Forge MCP server for managing servers, sites, and deployments

473 lines (472 loc) 20.3 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js"; class ForgeAPIClient { apiKey; baseUrl = "https://forge.laravel.com/api/v1"; constructor(apiKey) { this.apiKey = apiKey; } async makeRequest(endpoint, method = "GET", body) { const url = `${this.baseUrl}${endpoint}`; const headers = { "Authorization": `Bearer ${this.apiKey}`, "Accept": "application/json", "Content-Type": "application/json", }; const response = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined, }); if (!response.ok) { throw new McpError(ErrorCode.InternalError, `Forge API request failed: ${response.status} ${response.statusText}`); } return response.json(); } async getServers() { const response = await this.makeRequest("/servers"); return response.servers; } async getServer(serverId) { const response = await this.makeRequest(`/servers/${serverId}`); return response.server; } async getSites(serverId) { const response = await this.makeRequest(`/servers/${serverId}/sites`); return response.sites; } async getSite(serverId, siteId) { const response = await this.makeRequest(`/servers/${serverId}/sites/${siteId}`); return response.site; } async getDeployments(serverId, siteId) { const response = await this.makeRequest(`/servers/${serverId}/sites/${siteId}/deployment-history`); return response.deployments; } async deploySite(serverId, siteId) { await this.makeRequest(`/servers/${serverId}/sites/${siteId}/deployment/deploy`, "POST"); } async resetDeploymentState(serverId, siteId) { await this.makeRequest(`/servers/${serverId}/sites/${siteId}/deployment/reset`, "POST"); } async getDeploymentScript(serverId, siteId) { const response = await this.makeRequest(`/servers/${serverId}/sites/${siteId}/deployment/script`); return response.content; } async updateDeploymentScript(serverId, siteId, content) { await this.makeRequest(`/servers/${serverId}/sites/${siteId}/deployment/script`, "PUT", { content: content, }); } async enableQuickDeploy(serverId, siteId) { await this.makeRequest(`/servers/${serverId}/sites/${siteId}/deployment`, "POST"); } async disableQuickDeploy(serverId, siteId) { await this.makeRequest(`/servers/${serverId}/sites/${siteId}/deployment`, "DELETE"); } async rebootServer(serverId) { await this.makeRequest(`/servers/${serverId}/reboot`, "POST"); } async getServerLoad(serverId) { const response = await this.makeRequest(`/servers/${serverId}/load`); return response; } } class ForgeServer { server; forgeClient; constructor() { this.server = new Server({ name: "@bretterer/forge-mcp-server", version: "0.1.0", }, { capabilities: { tools: {}, }, }); const apiKey = process.env.FORGE_API_KEY; if (!apiKey) { throw new Error("FORGE_API_KEY environment variable is required"); } this.forgeClient = new ForgeAPIClient(apiKey); this.setupToolHandlers(); } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "list_servers", description: "List all Laravel Forge servers", inputSchema: { type: "object", properties: {}, }, }, { name: "get_server", description: "Get details of a specific server", inputSchema: { type: "object", properties: { serverId: { type: "number", description: "The ID of the server", }, }, required: ["serverId"], }, }, { name: "list_sites", description: "List all sites on a server", inputSchema: { type: "object", properties: { serverId: { type: "number", description: "The ID of the server", }, }, required: ["serverId"], }, }, { name: "get_site", description: "Get details of a specific site", inputSchema: { type: "object", properties: { serverId: { type: "number", description: "The ID of the server", }, siteId: { type: "number", description: "The ID of the site", }, }, required: ["serverId", "siteId"], }, }, { name: "deploy_site", description: "Deploy a site", inputSchema: { type: "object", properties: { serverId: { type: "number", description: "The ID of the server", }, siteId: { type: "number", description: "The ID of the site", }, }, required: ["serverId", "siteId"], }, }, { name: "get_deployments", description: "Get deployment history for a site", inputSchema: { type: "object", properties: { serverId: { type: "number", description: "The ID of the server", }, siteId: { type: "number", description: "The ID of the site", }, }, required: ["serverId", "siteId"], }, }, { name: "get_deployment_script", description: "Get the deployment script for a site", inputSchema: { type: "object", properties: { serverId: { type: "number", description: "The ID of the server", }, siteId: { type: "number", description: "The ID of the site", }, }, required: ["serverId", "siteId"], }, }, { name: "update_deployment_script", description: "Update the deployment script for a site", inputSchema: { type: "object", properties: { serverId: { type: "number", description: "The ID of the server", }, siteId: { type: "number", description: "The ID of the site", }, content: { type: "string", description: "The new deployment script content", }, }, required: ["serverId", "siteId", "content"], }, }, { name: "toggle_quick_deploy", description: "Enable or disable quick deploy for a site", inputSchema: { type: "object", properties: { serverId: { type: "number", description: "The ID of the server", }, siteId: { type: "number", description: "The ID of the site", }, enable: { type: "boolean", description: "Whether to enable or disable quick deploy", }, }, required: ["serverId", "siteId", "enable"], }, }, { name: "reboot_server", description: "Reboot a server", inputSchema: { type: "object", properties: { serverId: { type: "number", description: "The ID of the server", }, }, required: ["serverId"], }, }, { name: "get_server_load", description: "Get server load metrics", inputSchema: { type: "object", properties: { serverId: { type: "number", description: "The ID of the server", }, }, required: ["serverId"], }, }, { name: "reset_deployment_state", description: "Reset deployment state for a site", inputSchema: { type: "object", properties: { serverId: { type: "number", description: "The ID of the server", }, siteId: { type: "number", description: "The ID of the site", }, }, required: ["serverId", "siteId"], }, }, ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "list_servers": { const servers = await this.forgeClient.getServers(); return { content: [ { type: "text", text: JSON.stringify(servers, null, 2), }, ], }; } case "get_server": { const { serverId } = args; const server = await this.forgeClient.getServer(serverId); return { content: [ { type: "text", text: JSON.stringify(server, null, 2), }, ], }; } case "list_sites": { const { serverId } = args; const sites = await this.forgeClient.getSites(serverId); return { content: [ { type: "text", text: JSON.stringify(sites, null, 2), }, ], }; } case "get_site": { const { serverId, siteId } = args; const site = await this.forgeClient.getSite(serverId, siteId); return { content: [ { type: "text", text: JSON.stringify(site, null, 2), }, ], }; } case "deploy_site": { const { serverId, siteId } = args; await this.forgeClient.deploySite(serverId, siteId); return { content: [ { type: "text", text: `Deployment initiated for site ${siteId} on server ${serverId}`, }, ], }; } case "get_deployments": { const { serverId, siteId } = args; const deployments = await this.forgeClient.getDeployments(serverId, siteId); return { content: [ { type: "text", text: JSON.stringify(deployments, null, 2), }, ], }; } case "get_deployment_script": { const { serverId, siteId } = args; const script = await this.forgeClient.getDeploymentScript(serverId, siteId); return { content: [ { type: "text", text: script, }, ], }; } case "update_deployment_script": { const { serverId, siteId, content } = args; await this.forgeClient.updateDeploymentScript(serverId, siteId, content); return { content: [ { type: "text", text: `Deployment script updated for site ${siteId} on server ${serverId}`, }, ], }; } case "toggle_quick_deploy": { const { serverId, siteId, enable } = args; if (enable) { await this.forgeClient.enableQuickDeploy(serverId, siteId); } else { await this.forgeClient.disableQuickDeploy(serverId, siteId); } return { content: [ { type: "text", text: `Quick deploy ${enable ? "enabled" : "disabled"} for site ${siteId} on server ${serverId}`, }, ], }; } case "reboot_server": { const { serverId } = args; await this.forgeClient.rebootServer(serverId); return { content: [ { type: "text", text: `Server ${serverId} reboot initiated`, }, ], }; } case "get_server_load": { const { serverId } = args; const load = await this.forgeClient.getServerLoad(serverId); return { content: [ { type: "text", text: JSON.stringify(load, null, 2), }, ], }; } case "reset_deployment_state": { const { serverId, siteId } = args; await this.forgeClient.resetDeploymentState(serverId, siteId); return { content: [ { type: "text", text: `Deployment state reset for site ${siteId} on server ${serverId}`, }, ], }; } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InternalError, `Error executing tool ${name}: ${error}`); } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error("Laravel Forge MCP server running on stdio"); } } const server = new ForgeServer(); server.run().catch(console.error);