feat(mcps): unify SSE/Stdio transport and fix handshake timeouts

This commit is contained in:
2026-03-05 12:04:19 +01:00
parent 29423123b3
commit daa2750f89
5 changed files with 141 additions and 75 deletions

View File

@@ -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) => {