Added chall and some stuff

This commit is contained in:
2026-02-12 22:52:30 +01:00
parent fe21c6b790
commit 19fae10e5f
5 changed files with 428 additions and 7 deletions

View File

@@ -7,6 +7,7 @@ import fs from "fs/promises";
import { createReadStream } from "fs";
import http from "http";
import path from "path";
import { spawn } from "child_process";
import { URL } from "url";
import { z } from "zod";
import { minimatch } from "minimatch";
@@ -95,6 +96,15 @@ const GetFileInfoArgsSchema = z.object({
path: z.string(),
});
const RunCommandArgsSchema = z.object({
command: z.string().min(1),
cwd: z.string().optional().describe("Working directory to run the command in. Must be within allowed directories."),
timeoutMs: z.number().int().positive().optional().default(600_000).describe("Kill the command if it runs longer than this (ms). Default: 10 minutes."),
env: z.record(z.string(), z.string()).optional().default({}).describe("Extra environment variables (string values)."),
shell: z.enum(["bash", "sh"]).optional().default("bash").describe("Shell to use for the command. Default: bash."),
maxOutputChars: z.number().int().positive().optional().default(200_000).describe("Maximum characters captured for each of stdout/stderr."),
});
// Server setup
const server = new McpServer(
{
@@ -121,6 +131,89 @@ async function readFileAsBase64Stream(filePath: string): Promise<string> {
});
}
function appendWithLimit(current: string, chunk: string, maxChars: number): { next: string; truncated: boolean } {
if (maxChars <= 0) return { next: "", truncated: true };
if (current.length >= maxChars) return { next: current, truncated: true };
const remaining = maxChars - current.length;
if (chunk.length <= remaining) return { next: current + chunk, truncated: false };
return { next: current + chunk.slice(0, remaining), truncated: true };
}
async function runShellCommand(args: z.infer<typeof RunCommandArgsSchema>): Promise<{
exitCode: number | null;
signal: string | null;
stdout: string;
stderr: string;
stdoutTruncated: boolean;
stderrTruncated: boolean;
timedOut: boolean;
effectiveCwd: string;
}> {
const defaultCwd = process.env.MCP_DEFAULT_CWD ?? "/workspace";
const requestedCwd = args.cwd ?? defaultCwd;
const effectiveCwd = await validatePath(requestedCwd);
const cwdStats = await fs.stat(effectiveCwd);
if (!cwdStats.isDirectory()) {
throw new Error(`cwd is not a directory: ${requestedCwd}`);
}
const shellPath = args.shell === "sh" ? "/bin/sh" : "/bin/bash";
const env: NodeJS.ProcessEnv = { ...process.env, ...args.env };
return await new Promise((resolve) => {
const child = spawn(shellPath, ["-lc", args.command], {
cwd: effectiveCwd,
env,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let stdoutTruncated = false;
let stderrTruncated = false;
let timedOut = false;
const killTimer = setTimeout(() => {
timedOut = true;
try {
child.kill("SIGKILL");
} catch {
// ignore
}
}, args.timeoutMs);
child.stdout?.setEncoding("utf-8");
child.stderr?.setEncoding("utf-8");
child.stdout?.on("data", (data: string) => {
const result = appendWithLimit(stdout, data, args.maxOutputChars);
stdout = result.next;
stdoutTruncated = stdoutTruncated || result.truncated;
});
child.stderr?.on("data", (data: string) => {
const result = appendWithLimit(stderr, data, args.maxOutputChars);
stderr = result.next;
stderrTruncated = stderrTruncated || result.truncated;
});
child.on("close", (exitCode, signal) => {
clearTimeout(killTimer);
resolve({
exitCode,
signal: signal ? String(signal) : null,
stdout,
stderr,
stdoutTruncated,
stderrTruncated,
timedOut,
effectiveCwd,
});
});
});
}
// Tool registrations
// read_file (deprecated) and read_text_file
@@ -616,6 +709,63 @@ server.registerTool(
}
);
async function runCommandToolHandler(rawArgs: unknown) {
const args = RunCommandArgsSchema.parse(rawArgs);
const result = await runShellCommand(args);
const textLines = [
`cwd: ${result.effectiveCwd}`,
`exitCode: ${result.exitCode === null ? "null" : String(result.exitCode)}`,
`signal: ${result.signal ?? "null"}`,
`timedOut: ${result.timedOut ? "true" : "false"}`,
`stdoutTruncated: ${result.stdoutTruncated ? "true" : "false"}`,
`stderrTruncated: ${result.stderrTruncated ? "true" : "false"}`,
"",
"stdout:",
result.stdout || "(empty)",
"",
"stderr:",
result.stderr || "(empty)",
];
const text = textLines.join("\n");
return {
content: [{ type: "text" as const, text }],
structuredContent: {
content: text,
cwd: result.effectiveCwd,
exitCode: result.exitCode,
signal: result.signal,
timedOut: result.timedOut,
stdout: result.stdout,
stderr: result.stderr,
stdoutTruncated: result.stdoutTruncated,
stderrTruncated: result.stderrTruncated,
},
};
}
const runCommandToolDefinition = {
title: "Run Command",
description:
"Execute a shell command inside this same container (same filesystem as the filesystem tools). " +
"Uses a non-interactive shell (`bash -lc` by default). Returns stdout/stderr, exit code, and timeout info.",
inputSchema: {
command: z.string(),
cwd: z.string().optional().describe("Working directory to run the command in. Must be within allowed directories."),
timeoutMs: z.number().optional().describe("Kill the command if it runs longer than this (ms). Default: 10 minutes."),
env: z.record(z.string(), z.string()).optional().describe("Extra environment variables (string values)."),
shell: z.enum(["bash", "sh"]).optional().describe("Shell to use for the command. Default: bash."),
maxOutputChars: z.number().optional().describe("Maximum characters captured for each of stdout/stderr."),
},
outputSchema: { content: z.string() },
annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true },
};
// Keep snake_case for consistency with existing filesystem tools, and also provide a camelCase alias.
server.registerTool("run_command", runCommandToolDefinition, runCommandToolHandler);
server.registerTool("runCommand", runCommandToolDefinition, runCommandToolHandler);
// SSE transport session routing (sessionId -> transport)
const sseTransportsBySessionId = new Map<string, SSEServerTransport>();