server-watch-mcp
Version:
A Model Context Protocol (MCP) server for watching and monitoring server resources
237 lines • 8.74 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_child_process_1 = require("node:child_process");
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
const express_1 = __importDefault(require("express"));
const zod_1 = require("zod");
const MAX_LOG_ENTRIES = 5000;
const logs = [];
const PORT = process.env.SERVER_WATCH_MCP_PORT
? Number.parseInt(process.env.SERVER_WATCH_MCP_PORT)
: 6280;
const sseTransports = {};
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Usage: server-watch-mcp <command> [args...]");
console.error("Example: server-watch-mcp npm run dev");
process.exit(1);
}
const command = args[0];
const commandArgs = args.slice(1);
const server = new mcp_js_1.McpServer({
name: "server-watch-mcp",
version: "0.1.3",
});
server.tool("get_logs", {
limit: zod_1.z.number().optional().default(100),
stream: zod_1.z.enum(["stdout", "stderr", "both"]).optional().default("both"),
include_timestamps: zod_1.z.boolean().optional().default(false),
}, async ({ limit, stream, include_timestamps }) => {
// Filter logs by stream if specified
const filteredLogs = stream === "both" ? logs : logs.filter((log) => log.stream === stream);
// Get the most recent logs up to the limit
const recentLogs = filteredLogs.slice(-limit);
// Format logs as simple text lines
const formattedLogs = recentLogs
.map((log) => {
let line = "";
if (include_timestamps) {
line += `[${new Date(log.timestamp).toISOString()}] `;
}
if (log.stream === "stderr") {
line += "ERR: ";
}
line += log.content;
return line;
})
.join("\n");
return {
content: [
{
type: "text",
text: formattedLogs,
},
],
};
});
server.tool("search_logs", {
query: zod_1.z.string(),
include_timestamps: zod_1.z.boolean().optional().default(false),
}, async ({ query, include_timestamps }) => {
const lowerQuery = query.toLowerCase();
// Case-insensitive substring search
const matchingLogs = logs.filter((log) => log.content.toLowerCase().includes(lowerQuery));
// Format logs as simple text lines
const formattedLogs = matchingLogs
.map((log) => {
let line = "";
if (include_timestamps) {
line += `[${new Date(log.timestamp).toISOString()}] `;
}
if (log.stream === "stderr") {
line += "ERR: ";
}
line += log.content;
return line;
})
.join("\n");
return {
content: [
{
type: "text",
text: formattedLogs,
},
],
};
});
const app = (0, express_1.default)();
app.use(express_1.default.json());
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // stateless for simplicity
});
// Modern Streamable HTTP endpoint
app.post("/mcp", async (req, res) => {
try {
await transport.handleRequest(req, res, req.body);
}
catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
// Legacy SSE endpoint for older clients
app.get("/sse", async (req, res) => {
try {
const transport = new sse_js_1.SSEServerTransport("/messages", res);
sseTransports[transport.sessionId] = transport;
res.on("close", () => {
delete sseTransports[transport.sessionId];
});
await server.connect(transport);
}
catch (error) {
console.error("Error setting up SSE transport:", error);
if (!res.headersSent) {
res.status(500).send("Failed to establish SSE connection");
}
}
});
// Legacy message endpoint for older clients
app.post("/messages", async (req, res) => {
try {
const sessionId = req.query.sessionId;
const transport = sseTransports[sessionId];
if (transport) {
await transport.handlePostMessage(req, res, req.body);
}
else {
res.status(400).send("No transport found for sessionId");
}
}
catch (error) {
console.error("Error handling SSE message:", error);
if (!res.headersSent) {
res.status(500).send("Failed to handle message");
}
}
});
// Connect MCP server to transport
await server.connect(transport);
// Start HTTP server
app.listen(PORT, () => {
console.log(`MCP server running on http://localhost:${PORT}`);
console.log(`Monitoring command: ${command} ${commandArgs.join(" ")}`);
});
// Spawn the child process with piped stdio so we can control where output goes
const child = (0, node_child_process_1.spawn)(command, commandArgs, {
stdio: ["inherit", "pipe", "pipe"], // stdin inherited, stdout/stderr piped
shell: true,
cwd: process.cwd(),
env: { ...process.env, FORCE_COLOR: "1" }, // Force color output
});
// Helper function to process and store log lines
const processOutput = (data, stream) => {
const text = data.toString();
const lines = text.split("\n");
for (const line of lines) {
if (line.trim()) {
// Store log entry
logs.push({
timestamp: Date.now(),
stream,
content: line,
});
if (logs.length > MAX_LOG_ENTRIES) {
logs.shift(); // Remove the oldest entry
}
}
}
// Write to the appropriate stream to preserve colors
if (stream === "stdout") {
process.stdout.write(data);
}
else {
process.stderr.write(data);
}
};
// Capture and forward child stdout
if (child.stdout) {
child.stdout.on("data", (data) => {
processOutput(data, "stdout");
});
}
// Capture and forward child stderr
if (child.stderr) {
child.stderr.on("data", (data) => {
processOutput(data, "stderr");
});
}
// Handle child process exit
child.on("exit", (code, signal) => {
if (signal) {
console.error(`\nChild process terminated by signal: ${signal}`);
console.log(`MCP server continues running on http://localhost:${PORT}`);
}
else {
if (code !== 0) {
console.error(`\nChild process exited with code: ${code}`);
}
else {
console.log("\nChild process exited successfully");
}
console.log(`MCP server continues running on http://localhost:${PORT}`);
}
});
child.on("error", (error) => {
const nodeError = error;
if (nodeError.code === "ENOENT") {
console.error(`\nCommand not found: ${command}`);
console.error("Make sure the command is installed and available in your PATH");
}
else if (nodeError.code === "EACCES") {
console.error(`\nPermission denied: ${command}`);
console.error("Check that you have permission to execute this command");
}
else {
console.error(`\nFailed to start command '${command}': ${error.message}`);
}
console.log(`\nMCP server continues running on http://localhost:${PORT}`);
});
}
main().catch((error) => {
console.error("Server failed to start:", error);
process.exit(1);
});
//# sourceMappingURL=index.js.map