Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Dockerfile.sse
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
## Stage 1: Builder
FROM node:lts-alpine AS builder

# Set working directory
WORKDIR /app

# Copy all files into the container
COPY . .

# Install dependencies without running scripts
RUN npm install --ignore-scripts

# Build the TypeScript source code
RUN npm run build

## Stage 2: Runtime
FROM node:lts-alpine

WORKDIR /app

# Install Python and other programming languages
RUN apk add --no-cache \
python3 \
go \
php \
ruby

# Copy only the necessary files from the builder stage
COPY --from=builder /app/dist ./dist
COPY package*.json ./

# Install only production dependencies
RUN npm install --production --ignore-scripts

# Use a non-root user for security (optional)
RUN adduser -D mcpuser
USER mcpuser

# Set the entrypoint command
CMD ["node", "./dist/cli.js", "--transport", "sse"]
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
"scripts": {
"build": "tsc && shx chmod +x dist/index.js dist/cli.js",
"watch": "tsc --watch",
"start": "node ./dist/cli.js"
"start": "node ./dist/cli.js",
"start-http": "node ./dist/cli.js --transport=http",
"start-sse": "node ./dist/cli.js --transport=sse"
},
"files": [
"dist"
Expand Down
4 changes: 2 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ program
.name('mcp-server-code-runner')
.description(pkg.description)
.version(pkg.version)
.option('-t, --transport <transport>', 'Transport (stdio or http)', 'stdio')
.option('-p, --port <port>', 'Port number for HTTP server')
.option('-t, --transport <transport>', 'Transport (stdio, http, or sse)', 'stdio')
.option('-p, --port <port>', 'Port number for HTTP/SSE server')
.action(async (options) => {
try {
await startMcpServer(options.transport, { port: parseInt(options.port) });
Expand Down
7 changes: 5 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { startStdioMcpServer } from "./stdio.js";
import { startStreamableHttpMcpServer, McpServerEndpoint } from "./streamableHttp.js";
import { startSSEMcpServer } from "./sseServer.js";

export type Transport = 'stdio' | 'http';
export type Transport = 'stdio' | 'http' | 'sse';

export interface HttpServerOptions {
port?: number;
Expand All @@ -12,7 +13,9 @@ export async function startMcpServer(transport: Transport, options?: HttpServerO
return startStdioMcpServer();
} else if (transport === 'http') {
return startStreamableHttpMcpServer(options?.port);
} else if (transport === 'sse') {
return startSSEMcpServer(options?.port);
} else {
throw new Error('Invalid transport. Must be either "stdio" or "http"');
throw new Error('Invalid transport. Must be either "stdio", "http", or "sse"');
}
}
125 changes: 125 additions & 0 deletions src/sseServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import express, { Request, Response } from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { createServer } from "./server.js";

export interface McpServerEndpoint {
url: string;
port: number;
}

export async function startSSEMcpServer(port?: number): Promise<McpServerEndpoint> {
const app = express();
app.use(express.json());

// Store transports by session ID
const transports: { [sessionId: string]: SSEServerTransport } = {};

// SSE endpoint for establishing connection
app.get('/sse', async (req: Request, res: Response) => {
console.log('Received SSE connection request');
try {
const server: McpServer = createServer();
const transport: SSEServerTransport = new SSEServerTransport('/messages', res);

// Store the transport by session ID
transports[transport.sessionId] = transport;

// Clean up transport when connection closes
transport.onclose = () => {
delete transports[transport.sessionId];
console.log(`SSE session ${transport.sessionId} closed`);
};

// Connect the server to the transport
await server.connect(transport);

console.log(`SSE session ${transport.sessionId} established`);
} catch (error) {
console.error('Error establishing SSE connection:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error',
},
id: null,
});
}
}
});

// Messages endpoint for receiving client messages
app.post('/messages', async (req: Request, res: Response) => {
const sessionId = req.query.sessionId as string;
console.log(`Received message for session: ${sessionId}`);

if (!sessionId || !transports[sessionId]) {
console.error(`No transport found for sessionId: ${sessionId}`);
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Invalid or missing session ID',
},
id: null,
});
return;
}

try {
const transport = transports[sessionId];
await transport.handlePostMessage(req, res, req.body);
} catch (error) {
console.error('Error handling SSE message:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error',
},
id: null,
});
}
}
});

// Health check endpoint
app.get('/health', (req: Request, res: Response) => {
res.json({
status: 'ok',
transport: 'sse',
activeSessions: Object.keys(transports).length
});
});

// Start the server
const PORT = Number(port || process.env.PORT || 3088);

return new Promise((resolve, reject) => {
const appServer = app.listen(PORT, (error) => {
if (error) {
console.error('Failed to start server:', error);
reject(error);
return;
}
const endpoint: McpServerEndpoint = {
url: `http://localhost:${PORT}/sse`,
port: PORT
};
console.log(`Code Runner SSE MCP Server listening at ${endpoint.url}`);
console.log(`Messages endpoint: http://localhost:${PORT}/messages`);
console.log(`Health check: http://localhost:${PORT}/health`);
resolve(endpoint);
});

// Handle server errors
appServer.on('error', (error) => {
console.error('Server error:', error);
reject(error);
});
});
}