feat(mcps): migrate gitea and memory MCPs to SSE transport on pm2

This commit is contained in:
2026-03-04 10:05:08 +01:00
parent ddf896e3f9
commit ee3d7714c2
8 changed files with 123 additions and 20 deletions

24
ecosystem.mcps.config.cjs Normal file
View File

@@ -0,0 +1,24 @@
module.exports = {
apps: [
{
name: 'gitea-mcp',
script: 'node',
args: 'dist/start.js',
cwd: './packages/gitea-mcp',
watch: false,
env: {
NODE_ENV: 'production'
}
},
{
name: 'memory-mcp',
script: 'node',
args: 'dist/start.js',
cwd: './packages/memory-mcp',
watch: false,
env: {
NODE_ENV: 'production'
}
}
]
};

View File

@@ -7,9 +7,11 @@
"dev": "pnpm -r dev", "dev": "pnpm -r dev",
"dev:gatekeeper": "bash -c 'trap \"COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down\" EXIT INT TERM; docker network create infra 2>/dev/null || true && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml up --build --remove-orphans'", "dev:gatekeeper": "bash -c 'trap \"COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down\" EXIT INT TERM; docker network create infra 2>/dev/null || true && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml up --build --remove-orphans'",
"dev:mcps:up": "docker-compose -f docker-compose.mcps.yml up -d", "dev:mcps:up": "docker-compose -f docker-compose.mcps.yml up -d",
"dev:mcps:down": "docker-compose -f docker-compose.mcps.yml down", "dev:mcps:down": "docker-compose -f docker-compose.mcps.yml down && pm2 delete ecosystem.mcps.config.cjs || true",
"dev:mcps:watch": "pnpm -r --filter=\"./packages/*-mcp\" exec tsc -w", "dev:mcps:watch": "pnpm -r --filter=\"./packages/*-mcp\" exec tsc -w",
"dev:mcps": "npm run dev:mcps:up && npm run dev:mcps:watch", "dev:mcps": "npm run dev:mcps:up && pm2 start ecosystem.mcps.config.cjs --watch && npm run dev:mcps:watch",
"start:mcps:run": "pm2 start ecosystem.mcps.config.cjs",
"start:mcps": "npm run dev:mcps:up && npm run start:mcps:run",
"lint": "pnpm -r --filter='./packages/**' --filter='./apps/**' lint", "lint": "pnpm -r --filter='./packages/**' --filter='./apps/**' lint",
"test": "pnpm -r test", "test": "pnpm -r test",
"changeset": "changeset", "changeset": "changeset",
@@ -40,6 +42,7 @@
"husky": "^9.1.7", "husky": "^9.1.7",
"jsdom": "^27.4.0", "jsdom": "^27.4.0",
"lint-staged": "^16.2.7", "lint-staged": "^16.2.7",
"pm2": "^6.0.14",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "^5.0.0", "typescript": "^5.0.0",
@@ -72,4 +75,4 @@
"@sentry/nextjs": "10.38.0" "@sentry/nextjs": "10.38.0"
} }
} }
} }

View File

@@ -6,15 +6,18 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"start": "node --env-file-if-exists=../../.env dist/index.js" "start": "node dist/start.js"
}, },
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.5.0", "@modelcontextprotocol/sdk": "^1.5.0",
"zod": "^3.23.8", "axios": "^1.7.2",
"axios": "^1.7.2" "dotenv": "^17.3.1",
"express": "^5.2.1",
"zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.5.3", "@types/express": "^5.0.6",
"@types/node": "^20.14.10" "@types/node": "^20.14.10",
"typescript": "^5.5.3"
} }
} }

View File

@@ -1,5 +1,6 @@
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from 'express';
import { import {
CallToolRequestSchema, CallToolRequestSchema,
ListToolsRequestSchema, ListToolsRequestSchema,
@@ -251,9 +252,27 @@ async function pollSubscriptions() {
async function run() { async function run() {
const transport = new StdioServerTransport(); const app = express();
await server.connect(transport); let transport: SSEServerTransport | null = null;
console.error("Gitea MCP Native Server running on stdio");
app.get('/sse', async (req, res) => {
console.error('New SSE connection established');
transport = new SSEServerTransport('/message', res);
await server.connect(transport);
});
app.post('/message', async (req, res) => {
if (!transport) {
res.status(400).send('No active SSE connection');
return;
}
await transport.handlePostMessage(req, res);
});
const PORT = process.env.GITEA_MCP_PORT || 3001;
app.listen(PORT, () => {
console.error(`Gitea MCP Native Server running on http://localhost:${PORT}/sse`);
});
// Start the background poller // Start the background poller
pollSubscriptions(); pollSubscriptions();

View File

@@ -0,0 +1,16 @@
import { config } from 'dotenv';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
// Try to load .env.local first (contains credentials usually)
config({ path: resolve(__dirname, '../../../.env.local') });
// Fallback to .env (contains defaults)
config({ path: resolve(__dirname, '../../../.env') });
// Now boot the compiled MCP index
import('./index.js').catch(err => {
console.error('Failed to start MCP Server:', err);
process.exit(1);
});

View File

@@ -6,7 +6,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"start": "node --env-file-if-exists=../../.env dist/index.js", "start": "node dist/start.js",
"dev": "tsx watch src/index.ts", "dev": "tsx watch src/index.ts",
"test:unit": "vitest run" "test:unit": "vitest run"
}, },
@@ -14,12 +14,15 @@
"@modelcontextprotocol/sdk": "^1.5.0", "@modelcontextprotocol/sdk": "^1.5.0",
"@qdrant/js-client-rest": "^1.12.0", "@qdrant/js-client-rest": "^1.12.0",
"@xenova/transformers": "^2.17.2", "@xenova/transformers": "^2.17.2",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.5.3", "@types/express": "^5.0.6",
"@types/node": "^20.14.10", "@types/node": "^20.14.10",
"tsx": "^4.19.1", "tsx": "^4.19.1",
"typescript": "^5.5.3",
"vitest": "^2.1.3" "vitest": "^2.1.3"
} }
} }

View File

@@ -1,5 +1,6 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express';
import { z } from 'zod'; import { z } from 'zod';
import { QdrantMemoryService } from './qdrant.js'; import { QdrantMemoryService } from './qdrant.js';
@@ -67,9 +68,27 @@ async function main() {
} }
); );
const transport = new StdioServerTransport(); const app = express();
await server.connect(transport); let transport: SSEServerTransport | null = null;
console.error('Memory MCP server is running and ready to accept connections over stdio.');
app.get('/sse', async (req, res) => {
console.error('New SSE connection established');
transport = new SSEServerTransport('/message', res);
await server.connect(transport);
});
app.post('/message', async (req, res) => {
if (!transport) {
res.status(400).send('No active SSE connection');
return;
}
await transport.handlePostMessage(req, res);
});
const PORT = process.env.MEMORY_MCP_PORT || 3002;
app.listen(PORT, () => {
console.error(`Memory MCP server is running on http://localhost:${PORT}/sse`);
});
} }
main().catch((error) => { main().catch((error) => {

View File

@@ -0,0 +1,16 @@
import { config } from 'dotenv';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
// Try to load .env.local first (contains credentials usually)
config({ path: resolve(__dirname, '../../../.env.local') });
// Fallback to .env (contains defaults)
config({ path: resolve(__dirname, '../../../.env') });
// Now boot the compiled MCP index
import('./index.js').catch(err => {
console.error('Failed to start MCP Server:', err);
process.exit(1);
});