Skip to main content
Glama

Swift Test MCP Server

by acyment
server.ts5.9 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { access, stat } from "node:fs/promises"; import { constants as fsConstants } from "node:fs"; import path from "node:path"; import process from "node:process"; const SwiftToolInputSchema = z .object({ packagePath: z .string() .min(1, "packagePath must not be empty") .describe("Absolute or relative path to the Swift package directory.") .optional(), swiftArgs: z .array(z.string()) .describe("Additional arguments forwarded to `swift test`.") .optional() }) .describe("Parameters for running `swift test`."); type SwiftToolInput = z.infer<typeof SwiftToolInputSchema>; const server = new McpServer({ name: "swift-test-mcp-server", version: "0.1.0" }); server.registerTool( "swift-test", ({ title: "Run swift test", description: "Execute `swift test` inside the specified Swift package directory." } as any), async (rawInput) => { const input = SwiftToolInputSchema.parse(rawInput ?? {}); try { const result = await runSwiftTest(input); const text = formatResult(result); return { content: [ { type: "text", text } ], isError: result.exitCode !== 0 }; } catch (error) { const message = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `swift test failed before execution: ${message}` } ], isError: true }; } } ); async function runSwiftTest(input: SwiftToolInput) { const packageRoot = await resolvePackageRoot(input.packagePath); const extraArgs = input.swiftArgs ?? []; const command = ["swift", "test", ...extraArgs]; const startedAt = process.hrtime.bigint(); const subprocess = Bun.spawn(command, { cwd: packageRoot, stdout: "pipe", stderr: "pipe" }); const stdoutPromise = subprocess.stdout ? new Response(subprocess.stdout).text() : Promise.resolve(""); const stderrPromise = subprocess.stderr ? new Response(subprocess.stderr).text() : Promise.resolve(""); const [stdout, stderr, exitCode] = await Promise.all([ stdoutPromise, stderrPromise, subprocess.exited ]); const finishedAt = process.hrtime.bigint(); const durationMs = Number(finishedAt - startedAt) / 1_000_000; return { exitCode, stdout, stderr, cwd: packageRoot, command, durationMs }; } async function resolvePackageRoot(inputPath?: string) { const candidate = path.resolve(inputPath ?? process.cwd()); await assertDirectory(candidate); await assertPackageManifest(candidate); return candidate; } async function assertDirectory(directory: string) { try { await access(directory, fsConstants.R_OK | fsConstants.X_OK); const fileStats = await stat(directory); if (!fileStats.isDirectory()) { throw new Error(); } } catch { throw new Error(`Path is not an accessible directory: ${directory}`); } } async function assertPackageManifest(directory: string) { const manifest = path.join(directory, "Package.swift"); try { await access(manifest, fsConstants.R_OK); } catch { throw new Error(`No Package.swift manifest found at ${manifest}`); } } function formatResult(result: { exitCode: number; stdout: string; stderr: string; cwd: string; command: string[]; durationMs: number; }) { const headerLines = [ `command: ${result.command.join(" ")}`, `cwd: ${result.cwd}`, `exitCode: ${result.exitCode}`, `durationMs: ${result.durationMs.toFixed(2)}` ]; const stdout = result.stdout.trim().length > 0 ? result.stdout : "(no stdout)"; const stderr = result.stderr.trim().length > 0 ? result.stderr : "(no stderr)"; return `${headerLines.join("\n")}\n\nstdout:\n${stdout}\n\nstderr:\n${stderr}`; } async function main() { const transport = new StdioServerTransport(); await server.connect(transport); // Replace the SDK's default tools/call handler to bypass its schema conversion. // We still advertise the tool via registerTool (without inputSchema) above. const CallToolRequestSchema = z.object({ method: z.literal("tools/call"), params: z .object({ name: z.string(), arguments: z.record(z.string(), z.unknown()).optional(), _meta: z.any().optional() }) .passthrough() }); try { // Remove any default handler the SDK installed for tools/call. server.server.removeRequestHandler("tools/call"); } catch {} server.server.setRequestHandler(CallToolRequestSchema as any, async (request: any) => { if (request.params?.name !== "swift-test") { // Let the SDK handle other tools if present in the future. throw new Error(`Unknown tool: ${request.params?.name ?? "(missing)"}`); } const input = SwiftToolInputSchema.parse(request.params?.arguments ?? {}); try { const result = await runSwiftTest(input); const text = formatResult(result); return { content: [ { type: "text", text } ], isError: result.exitCode !== 0 }; } catch (error) { const message = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `swift test failed before execution: ${message}` } ], isError: true }; } }); } main().catch((error) => { const message = error instanceof Error ? error.stack ?? error.message : String(error); console.error(`Fatal error while starting MCP server: ${message}`); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/acyment/swifttest-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server