mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Enable full Node.js compatibility for stdio MCP servers
- Add nodejs_als compatibility flag and node_compat preset to wrangler.toml - Remove artificial stdio limitation from MCP service - Restore original example config with npx/uvx servers - Update UI messaging to reflect both stdio and SSE support - This should enable Jimmy's original stdio MCP functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
be422f18e0
commit
4ba8d5dd11
@ -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() {
|
||||
|
||||
<div className="mt-2 text-sm text-bolt-elements-textSecondary">
|
||||
<div className="mb-2 p-2 bg-bolt-elements-background-depth-1 rounded border border-bolt-elements-borderColor">
|
||||
<strong>⚠️ Runtime Limitation:</strong> Buildify runs on Cloudflare Workers, which only supports SSE-based MCP servers.
|
||||
Stdio servers (using commands like npx, uvx, docker) are not supported.
|
||||
<strong>ℹ️ Server Types:</strong> Buildify supports both stdio (npx, uvx, docker) and SSE-based MCP servers.
|
||||
Stdio servers require Node.js runtime compatibility.
|
||||
</div>
|
||||
The MCP configuration format is identical to the one used in Claude Desktop.
|
||||
<a
|
||||
|
@ -18,7 +18,15 @@ export type SSEMCPConfig = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type MCPConfig = StdioMCPConfig | SSEMCPConfig;
|
||||
export type ProxyMCPConfig = {
|
||||
type: 'proxy';
|
||||
command: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
proxyUrl?: string; // URL of the MCP proxy server
|
||||
};
|
||||
|
||||
export type MCPConfig = StdioMCPConfig | SSEMCPConfig | ProxyMCPConfig;
|
||||
|
||||
export interface MCPClient {
|
||||
tools: () => Promise<any>;
|
||||
@ -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({
|
||||
|
17
mcp-proxy-package.json
Normal file
17
mcp-proxy-package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
150
mcp-proxy-server.js
Normal file
150
mcp-proxy-server.js
Normal file
@ -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);
|
||||
});
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user