feat(mcps): unify SSE/Stdio transport and fix handshake timeouts
This commit is contained in:
@@ -15,10 +15,10 @@ import { z } from "zod";
|
||||
import axios from "axios";
|
||||
|
||||
const GITEA_HOST = process.env.GITEA_HOST || "https://git.infra.mintel.me";
|
||||
const GITEA_ACCESS_TOKEN = process.env.GITEA_ACCESS_TOKEN;
|
||||
const GITEA_ACCESS_TOKEN = process.env.GITEA_ACCESS_TOKEN || process.env.GITEA_TOKEN;
|
||||
|
||||
if (!GITEA_ACCESS_TOKEN) {
|
||||
console.error("Warning: GITEA_ACCESS_TOKEN environment variable is missing. Pipeline tools will return unauthorized errors.");
|
||||
console.error("Warning: Neither GITEA_ACCESS_TOKEN nor GITEA_TOKEN environment variable is set. Pipeline tools will return unauthorized errors.");
|
||||
}
|
||||
|
||||
const giteaClient = axios.create({
|
||||
@@ -829,32 +829,42 @@ async function pollSubscriptions() {
|
||||
|
||||
|
||||
async function run() {
|
||||
const app = express();
|
||||
let transport: SSEServerTransport | null = null;
|
||||
const isStdio = process.argv.includes('--stdio');
|
||||
|
||||
app.get('/sse', async (req, res) => {
|
||||
console.error('New SSE connection established');
|
||||
transport = new SSEServerTransport('/message', res);
|
||||
if (isStdio) {
|
||||
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
});
|
||||
console.error('Gitea MCP server is running on stdio');
|
||||
} else {
|
||||
const app = express();
|
||||
let transport: SSEServerTransport | null = null;
|
||||
|
||||
app.post('/message', async (req, res) => {
|
||||
if (!transport) {
|
||||
res.status(400).send('No active SSE connection');
|
||||
return;
|
||||
}
|
||||
await transport.handlePostMessage(req, res);
|
||||
});
|
||||
app.get('/sse', async (req, res) => {
|
||||
console.error('New SSE connection established');
|
||||
transport = new SSEServerTransport('/message', res);
|
||||
await server.connect(transport);
|
||||
});
|
||||
|
||||
const PORT = process.env.GITEA_MCP_PORT || 3001;
|
||||
app.listen(PORT, () => {
|
||||
console.error(`Gitea MCP Native Server running on http://localhost:${PORT}/sse`);
|
||||
});
|
||||
app.post('/message', async (req, res) => {
|
||||
if (!transport) {
|
||||
res.status(400).send('No active SSE connection');
|
||||
return;
|
||||
}
|
||||
await transport.handlePostMessage(req, res);
|
||||
});
|
||||
|
||||
// Start the background poller
|
||||
pollSubscriptions();
|
||||
const PORT = process.env.GITEA_MCP_PORT || 3001;
|
||||
app.listen(PORT, () => {
|
||||
console.error(`Gitea MCP server running on http://localhost:${PORT}/sse`);
|
||||
});
|
||||
|
||||
// Start the background poller only in SSE mode or if specifically desired
|
||||
pollSubscriptions();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
run().catch((error) => {
|
||||
console.error("Fatal error:", error);
|
||||
process.exit(1);
|
||||
|
||||
@@ -132,25 +132,39 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
});
|
||||
|
||||
async function run() {
|
||||
const app = express();
|
||||
let transport: SSEServerTransport | null = null;
|
||||
const isStdio = process.argv.includes('--stdio');
|
||||
|
||||
app.get('/sse', async (req, res) => {
|
||||
transport = new SSEServerTransport('/message', res);
|
||||
if (isStdio) {
|
||||
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
});
|
||||
console.error('GlitchTip MCP server is running on stdio');
|
||||
} else {
|
||||
const app = express();
|
||||
let transport: SSEServerTransport | null = null;
|
||||
|
||||
app.post('/message', async (req, res) => {
|
||||
if (!transport) { res.status(400).send('No active SSE connection'); return; }
|
||||
await transport.handlePostMessage(req, res);
|
||||
});
|
||||
app.get('/sse', async (req, res) => {
|
||||
console.error('New SSE connection established');
|
||||
transport = new SSEServerTransport('/message', res);
|
||||
await server.connect(transport);
|
||||
});
|
||||
|
||||
const PORT = process.env.GLITCHTIP_MCP_PORT || 3005;
|
||||
app.listen(PORT, () => {
|
||||
console.error(`GlitchTip MCP server running on http://localhost:${PORT}/sse`);
|
||||
});
|
||||
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.GLITCHTIP_MCP_PORT || 3005;
|
||||
app.listen(PORT, () => {
|
||||
console.error(`GlitchTip MCP server running on http://localhost:${PORT}/sse`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
run().catch((err) => {
|
||||
console.error("Fatal error:", err);
|
||||
process.exit(1);
|
||||
|
||||
@@ -12,14 +12,6 @@ async function main() {
|
||||
|
||||
const qdrantService = new QdrantMemoryService(process.env.QDRANT_URL || 'http://localhost:6333');
|
||||
|
||||
// Initialize embedding model and Qdrant connection
|
||||
try {
|
||||
await qdrantService.initialize();
|
||||
} catch (e) {
|
||||
console.error('Failed to initialize local dependencies. Exiting.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
server.tool(
|
||||
'store_memory',
|
||||
'Store a new piece of knowledge/memory into the vector database. Use this to remember architectural decisions, preferences, aliases, etc.',
|
||||
@@ -71,10 +63,18 @@ async function main() {
|
||||
const isStdio = process.argv.includes('--stdio');
|
||||
|
||||
if (isStdio) {
|
||||
// Connect Stdio FIRST to avoid handshake timeouts while loading model
|
||||
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error('Memory MCP server is running on stdio');
|
||||
|
||||
// Initialize dependency after connection
|
||||
try {
|
||||
await qdrantService.initialize();
|
||||
} catch (e) {
|
||||
console.error('Failed to initialize local dependencies:', e);
|
||||
}
|
||||
} else {
|
||||
const app = express();
|
||||
let transport: SSEServerTransport | null = null;
|
||||
@@ -94,13 +94,19 @@ async function main() {
|
||||
});
|
||||
|
||||
const PORT = process.env.MEMORY_MCP_PORT || 3002;
|
||||
app.listen(PORT, () => {
|
||||
console.error(`Memory MCP server is running on http://localhost:${PORT}/sse`);
|
||||
app.listen(PORT, async () => {
|
||||
console.error(`Memory MCP server running on http://localhost:${PORT}/sse`);
|
||||
// Initialize dependencies in SSE mode on startup
|
||||
try {
|
||||
await qdrantService.initialize();
|
||||
} catch (e) {
|
||||
console.error('Failed to initialize local dependencies:', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Fatal error in main():', error);
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Tool,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import axios from "axios";
|
||||
import https from "https";
|
||||
|
||||
const SERPBEAR_BASE_URL = process.env.SERPBEAR_BASE_URL || "https://serpbear.infra.mintel.me";
|
||||
const SERPBEAR_API_KEY = process.env.SERPBEAR_API_KEY;
|
||||
@@ -18,8 +19,12 @@ if (!SERPBEAR_API_KEY) {
|
||||
const serpbearClient = axios.create({
|
||||
baseURL: `${SERPBEAR_BASE_URL}/api`,
|
||||
headers: { apiKey: SERPBEAR_API_KEY },
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
// --- Tool Definitions ---
|
||||
const LIST_DOMAINS_TOOL: Tool = {
|
||||
name: "serpbear_list_domains",
|
||||
@@ -199,26 +204,39 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
|
||||
// --- Express / SSE Server ---
|
||||
async function run() {
|
||||
const app = express();
|
||||
let transport: SSEServerTransport | null = null;
|
||||
const isStdio = process.argv.includes('--stdio');
|
||||
|
||||
app.get('/sse', async (req, res) => {
|
||||
console.error('New SSE connection established');
|
||||
transport = new SSEServerTransport('/message', res);
|
||||
if (isStdio) {
|
||||
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
});
|
||||
console.error('SerpBear MCP server is running on stdio');
|
||||
} else {
|
||||
const app = express();
|
||||
let transport: SSEServerTransport | null = null;
|
||||
|
||||
app.post('/message', async (req, res) => {
|
||||
if (!transport) { res.status(400).send('No active SSE connection'); return; }
|
||||
await transport.handlePostMessage(req, res);
|
||||
});
|
||||
app.get('/sse', async (req, res) => {
|
||||
console.error('New SSE connection established');
|
||||
transport = new SSEServerTransport('/message', res);
|
||||
await server.connect(transport);
|
||||
});
|
||||
|
||||
const PORT = process.env.SERPBEAR_MCP_PORT || 3004;
|
||||
app.listen(PORT, () => {
|
||||
console.error(`SerpBear MCP server running on http://localhost:${PORT}/sse`);
|
||||
});
|
||||
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.SERPBEAR_MCP_PORT || 3004;
|
||||
app.listen(PORT, () => {
|
||||
console.error(`SerpBear MCP server running on http://localhost:${PORT}/sse`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
run().catch((err) => {
|
||||
console.error("Fatal error:", err);
|
||||
process.exit(1);
|
||||
|
||||
@@ -7,12 +7,17 @@ import {
|
||||
Tool,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import axios from "axios";
|
||||
import https from "https";
|
||||
|
||||
const UMAMI_BASE_URL = process.env.UMAMI_BASE_URL || "https://umami.infra.mintel.me";
|
||||
const UMAMI_USERNAME = process.env.UMAMI_USERNAME;
|
||||
const UMAMI_PASSWORD = process.env.UMAMI_PASSWORD;
|
||||
const UMAMI_API_KEY = process.env.UMAMI_API_KEY; // optional if using API key auth
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
if (!UMAMI_USERNAME && !UMAMI_API_KEY) {
|
||||
console.error("Warning: Neither UMAMI_USERNAME/PASSWORD nor UMAMI_API_KEY is set.");
|
||||
}
|
||||
@@ -28,12 +33,13 @@ async function getAuthHeaders(): Promise<Record<string, string>> {
|
||||
const res = await axios.post(`${UMAMI_BASE_URL}/api/auth/login`, {
|
||||
username: UMAMI_USERNAME,
|
||||
password: UMAMI_PASSWORD,
|
||||
});
|
||||
}, { httpsAgent });
|
||||
cachedToken = res.data.token;
|
||||
}
|
||||
return { Authorization: `Bearer ${cachedToken}` };
|
||||
}
|
||||
|
||||
|
||||
// --- Tool Definitions ---
|
||||
const LIST_WEBSITES_TOOL: Tool = {
|
||||
name: "umami_list_websites",
|
||||
@@ -147,7 +153,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const headers = await getAuthHeaders();
|
||||
const api = axios.create({ baseURL: `${UMAMI_BASE_URL}/api`, headers });
|
||||
const api = axios.create({ baseURL: `${UMAMI_BASE_URL}/api`, headers, httpsAgent });
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
@@ -236,24 +242,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
|
||||
// --- Express / SSE Server ---
|
||||
async function run() {
|
||||
const app = express();
|
||||
let transport: SSEServerTransport | null = null;
|
||||
const isStdio = process.argv.includes('--stdio');
|
||||
|
||||
app.get('/sse', async (req, res) => {
|
||||
console.error('New SSE connection established');
|
||||
transport = new SSEServerTransport('/message', res);
|
||||
if (isStdio) {
|
||||
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
});
|
||||
console.error('Umami MCP server is running on stdio');
|
||||
} else {
|
||||
const app = express();
|
||||
let transport: SSEServerTransport | null = null;
|
||||
|
||||
app.post('/message', async (req, res) => {
|
||||
if (!transport) { res.status(400).send('No active SSE connection'); return; }
|
||||
await transport.handlePostMessage(req, res);
|
||||
});
|
||||
app.get('/sse', async (req, res) => {
|
||||
console.error('New SSE connection established');
|
||||
transport = new SSEServerTransport('/message', res);
|
||||
await server.connect(transport);
|
||||
});
|
||||
|
||||
const PORT = process.env.UMAMI_MCP_PORT || 3003;
|
||||
app.listen(PORT, () => {
|
||||
console.error(`Umami MCP server running on http://localhost:${PORT}/sse`);
|
||||
});
|
||||
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.UMAMI_MCP_PORT || 3003;
|
||||
app.listen(PORT, () => {
|
||||
console.error(`Umami MCP server running on http://localhost:${PORT}/sse`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
|
||||
Reference in New Issue
Block a user