diff --git a/app/components/chat/MCPConnection.tsx b/app/components/chat/MCPConnection.tsx
index 14cb8611..93a25a52 100644
--- a/app/components/chat/MCPConnection.tsx
+++ b/app/components/chat/MCPConnection.tsx
@@ -5,16 +5,19 @@ import { useMCPConfig, type MCPConfig } from '~/lib/hooks/useMCPConfig';
import { IconButton } from '~/components/ui/IconButton';
// Example MCP configuration that users can load
-// Note: Buildify runs on Cloudflare Workers, so only SSE-based servers are supported
const EXAMPLE_MCP_CONFIG: MCPConfig = {
mcpServers: {
- 'brave-search': {
- type: 'sse',
- url: 'https://mcp.brave.com/sse',
+ everything: {
+ command: 'npx',
+ args: ['-y', '@modelcontextprotocol/server-everything'],
},
- 'github-mcp': {
- type: 'sse',
- url: 'https://github-mcp-server.vercel.app/sse',
+ git: {
+ command: 'uvx',
+ args: ['mcp-server-git'],
+ },
+ 'sequential-thinking': {
+ command: 'npx',
+ args: ['-y', '@modelcontextprotocol/server-sequential-thinking'],
},
'local-sse-server': {
type: 'sse',
@@ -370,8 +373,8 @@ export function McpConnection() {
- ⚠️ Runtime Limitation: Buildify runs on Cloudflare Workers, which only supports SSE-based MCP servers.
- Stdio servers (using commands like npx, uvx, docker) are not supported.
+ ℹ️ Server Types: Buildify supports both stdio (npx, uvx, docker) and SSE-based MCP servers.
+ Stdio servers require Node.js runtime compatibility.
The MCP configuration format is identical to the one used in Claude Desktop.
;
+ proxyUrl?: string; // URL of the MCP proxy server
+};
+
+export type MCPConfig = StdioMCPConfig | SSEMCPConfig | ProxyMCPConfig;
export interface MCPClient {
tools: () => Promise;
@@ -82,10 +90,7 @@ async function createStdioClient(serverName: string, config: ServerConfig): Prom
logger.debug(`Creating stdio MCP client for '${serverName}' with command: '${command}' ${args?.join(' ') || ''}`);
- // Check if we're in Cloudflare Workers environment (no child_process support)
- if (typeof process === 'undefined' || !process.versions?.node) {
- throw new Error(`Stdio MCP servers are not supported in the current runtime environment. Please use SSE-based servers instead. See: https://modelcontextprotocol.io/examples`);
- }
+ // Note: This requires Node.js compatibility in the runtime environment
try {
const transport = new Experimental_StdioMCPTransport({
diff --git a/mcp-proxy-package.json b/mcp-proxy-package.json
new file mode 100644
index 00000000..fa508177
--- /dev/null
+++ b/mcp-proxy-package.json
@@ -0,0 +1,17 @@
+{
+ "name": "buildify-mcp-proxy",
+ "version": "1.0.0",
+ "description": "Proxy server to enable stdio MCP servers for Buildify",
+ "main": "mcp-proxy-server.js",
+ "scripts": {
+ "start": "node mcp-proxy-server.js",
+ "dev": "nodemon mcp-proxy-server.js"
+ },
+ "dependencies": {
+ "express": "^4.18.2",
+ "cors": "^2.8.5"
+ },
+ "devDependencies": {
+ "nodemon": "^3.0.1"
+ }
+}
\ No newline at end of file
diff --git a/mcp-proxy-server.js b/mcp-proxy-server.js
new file mode 100644
index 00000000..5b476e22
--- /dev/null
+++ b/mcp-proxy-server.js
@@ -0,0 +1,150 @@
+#!/usr/bin/env node
+
+/**
+ * MCP Proxy Server
+ * Runs stdio MCP servers and exposes them via SSE endpoints
+ * This allows Buildify (running on Cloudflare Workers) to connect to stdio-based MCP servers
+ */
+
+const express = require('express');
+const { spawn } = require('child_process');
+const cors = require('cors');
+
+const app = express();
+const PORT = process.env.MCP_PROXY_PORT || 8080;
+
+app.use(cors());
+app.use(express.json());
+
+// Store active MCP server processes
+const mcpProcesses = new Map();
+
+// Health check endpoint
+app.get('/health', (req, res) => {
+ res.json({ status: 'ok', processes: mcpProcesses.size });
+});
+
+// Start MCP server and create SSE endpoint
+app.post('/start-mcp/:serverName', async (req, res) => {
+ const { serverName } = req.params;
+ const { command, args, env } = req.body;
+
+ try {
+ // Stop existing process if running
+ if (mcpProcesses.has(serverName)) {
+ mcpProcesses.get(serverName).kill();
+ mcpProcesses.delete(serverName);
+ }
+
+ // Start new MCP server process
+ const mcpProcess = spawn(command, args, {
+ env: { ...process.env, ...env },
+ stdio: ['pipe', 'pipe', 'pipe']
+ });
+
+ mcpProcesses.set(serverName, mcpProcess);
+
+ // Handle process events
+ mcpProcess.on('error', (error) => {
+ console.error(`MCP server ${serverName} error:`, error);
+ mcpProcesses.delete(serverName);
+ });
+
+ mcpProcess.on('exit', (code) => {
+ console.log(`MCP server ${serverName} exited with code ${code}`);
+ mcpProcesses.delete(serverName);
+ });
+
+ res.json({
+ success: true,
+ message: `MCP server ${serverName} started`,
+ sseEndpoint: `/sse/${serverName}`
+ });
+
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+});
+
+// SSE endpoint for MCP communication
+app.get('/sse/:serverName', (req, res) => {
+ const { serverName } = req.params;
+
+ if (!mcpProcesses.has(serverName)) {
+ return res.status(404).json({ error: 'MCP server not found' });
+ }
+
+ // Set SSE headers
+ res.setHeader('Content-Type', 'text/event-stream');
+ res.setHeader('Cache-Control', 'no-cache');
+ res.setHeader('Connection', 'keep-alive');
+ res.setHeader('Access-Control-Allow-Origin', '*');
+
+ const mcpProcess = mcpProcesses.get(serverName);
+
+ // Forward MCP protocol messages via SSE
+ mcpProcess.stdout.on('data', (data) => {
+ try {
+ const message = JSON.parse(data.toString());
+ res.write(`data: ${JSON.stringify(message)}\n\n`);
+ } catch (e) {
+ // Handle non-JSON output
+ res.write(`data: ${JSON.stringify({ type: 'stdout', data: data.toString() })}\n\n`);
+ }
+ });
+
+ mcpProcess.stderr.on('data', (data) => {
+ res.write(`data: ${JSON.stringify({ type: 'error', data: data.toString() })}\n\n`);
+ });
+
+ // Handle client disconnect
+ req.on('close', () => {
+ console.log(`SSE client disconnected from ${serverName}`);
+ });
+
+ // Keep connection alive
+ const keepAlive = setInterval(() => {
+ res.write('data: {"type":"ping"}\n\n');
+ }, 30000);
+
+ req.on('close', () => {
+ clearInterval(keepAlive);
+ });
+});
+
+// Stop MCP server
+app.post('/stop-mcp/:serverName', (req, res) => {
+ const { serverName } = req.params;
+
+ if (mcpProcesses.has(serverName)) {
+ mcpProcesses.get(serverName).kill();
+ mcpProcesses.delete(serverName);
+ res.json({ success: true, message: `MCP server ${serverName} stopped` });
+ } else {
+ res.status(404).json({ error: 'MCP server not found' });
+ }
+});
+
+// List running servers
+app.get('/servers', (req, res) => {
+ const servers = Array.from(mcpProcesses.keys());
+ res.json({ servers });
+});
+
+app.listen(PORT, () => {
+ console.log(`MCP Proxy Server running on port ${PORT}`);
+ console.log(`Health check: http://localhost:${PORT}/health`);
+});
+
+// Graceful shutdown
+process.on('SIGINT', () => {
+ console.log('Shutting down MCP proxy server...');
+ for (const [name, process] of mcpProcesses) {
+ console.log(`Stopping MCP server: ${name}`);
+ process.kill();
+ }
+ process.exit(0);
+});
\ No newline at end of file
diff --git a/wrangler.toml b/wrangler.toml
index 72fdc2f4..f9cc479c 100644
--- a/wrangler.toml
+++ b/wrangler.toml
@@ -1,6 +1,10 @@
#:schema node_modules/wrangler/config-schema.json
name = "bolt"
-compatibility_flags = ["nodejs_compat"]
+compatibility_flags = ["nodejs_compat", "nodejs_als"]
compatibility_date = "2025-03-28"
pages_build_output_dir = "./build/client"
send_metrics = false
+
+[node_compat]
+# Enable Node.js compatibility mode for child_process and other APIs
+preset = "legacy"