Skip to main content
Glama
server.ts12.3 kB
import 'dotenv/config'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import { spawn } from 'node:child_process'; import { basename, extname, resolve, dirname } from 'node:path'; import { existsSync } from 'node:fs'; // Utility: run a command and capture output function runCommand(cmd: string, args: string[], options: { cwd?: string, env?: NodeJS.ProcessEnv } = {}) { return new Promise<{ code: number | null, stdout: string, stderr: string }>((resolvePromise) => { const child = spawn(cmd, args, { cwd: options.cwd, env: { ...process.env, ...options.env }, shell: true, // allow .bat/.cmd wrappers on Windows windowsHide: true }); let stdout = ''; let stderr = ''; child.stdout.on('data', (d) => { stdout += d.toString(); }); child.stderr.on('data', (d) => { stderr += d.toString(); }); child.on('close', (code) => resolvePromise({ code, stdout, stderr })); }); } // Tool input schema shapes (for registerTool) const BuildInput = { project: z.string().describe('Path to a Delphi .dproj or .groupproj file'), configuration: z.string().optional().default(process.env.DELPHI_CONFIG || 'Release'), platform: z.string().optional().default(process.env.DELPHI_PLATFORM || 'Win32'), msbuildPath: z.string().optional().describe('Optional path to msbuild.exe to use'), rsvarsPath: z.string().optional().describe('Optional path to rsvars.bat to initialize RAD Studio env') }; const CleanInput = { project: z.string().describe('Path to a Delphi .dproj or .groupproj file'), configuration: z.string().optional(), platform: z.string().optional().default(process.env.DELPHI_PLATFORM || 'Win32'), msbuildPath: z.string().optional(), rsvarsPath: z.string().optional() }; // FPC build inputs const FpcBuildInput = { source: z.string().describe('Path to a Pascal program (.lpr/.pas) or unit to compile with FPC'), output: z.string().optional().describe('Optional output binary path/name'), defines: z.array(z.string()).optional().describe('Conditional defines, e.g. FOO=1'), unitPaths: z.array(z.string()).optional().describe('Additional unit search paths (-Fu)'), includePaths: z.array(z.string()).optional().describe('Additional include search paths (-Fi)'), cpu: z.string().optional().describe('Target CPU, e.g. x86_64, i386, aarch64'), os: z.string().optional().describe('Target OS, e.g. win64, win32, linux'), fpcPath: z.string().optional().describe('Path to fpc compiler (defaults to "fpc")') }; // Lazarus build/clean inputs const LazarusBuildInput = { project: z.string().describe('Path to a Lazarus project file (.lpi)'), buildMode: z.string().optional().describe('Lazarus build mode name (maps to --bm)'), cpu: z.string().optional().describe('Target CPU for lazbuild (e.g. x86_64, i386, aarch64)'), os: z.string().optional().describe('Target OS for lazbuild (e.g. win64, win32, linux)'), lazbuildPath: z.string().optional().describe('Path to lazbuild (defaults to "lazbuild")') }; const LazarusCleanInput = { project: z.string().describe('Path to a Lazarus project file (.lpi)'), lazbuildPath: z.string().optional().describe('Path to lazbuild (defaults to "lazbuild")') }; function resolveDefaults() { const rsvars = process.env.RSVARS_BAT || process.env.RSVARS_PATH; const msbuild = process.env.MSBUILD_PATH; return { rsvars, msbuild }; } function isDelphiProject(file: string) { const ext = extname(file).toLowerCase(); return ext === '.dproj' || ext === '.groupproj'; } async function buildWithMSBuild({ project, configuration, platform, msbuildPath, rsvarsPath }: { project: string; configuration?: string; platform?: string; msbuildPath?: string; rsvarsPath?: string; }) { const projPath = resolve(project); if (!existsSync(projPath)) { throw new Error(`Project not found: ${projPath}`); } if (!isDelphiProject(projPath)) { throw new Error('Unsupported project type. Provide a .dproj or .groupproj file'); } const { rsvars, msbuild } = resolveDefaults(); const rsvarsFinal = rsvarsPath || rsvars; const msbuildFinal = msbuildPath || msbuild || 'msbuild'; // Prepare MSBuild arguments const args = [ '"' + projPath + '"', '/t:Build', configuration ? `/p:Config=${configuration}` : '', platform ? `/p:Platform=${platform}` : '' ].filter(Boolean); // If rsvars is available, run in a single shell using cmd and call if (rsvarsFinal && existsSync(rsvarsFinal)) { const cmd = 'cmd'; const composite = [ '/s', '/c', `"@echo off && call \"${rsvarsFinal}\" && ${msbuildFinal} ${args.join(' ')}"` ]; return await runCommand(cmd, composite); } // Otherwise, rely on msbuild directly (if in PATH or explicit) return await runCommand(msbuildFinal, args); } async function cleanWithMSBuild({ project, configuration, platform, msbuildPath, rsvarsPath }: { project: string; configuration?: string; platform?: string; msbuildPath?: string; rsvarsPath?: string; }) { const projPath = resolve(project); if (!existsSync(projPath)) { throw new Error(`Project not found: ${projPath}`); } if (!isDelphiProject(projPath)) { throw new Error('Unsupported project type. Provide a .dproj or .groupproj file'); } const { rsvars, msbuild } = resolveDefaults(); const rsvarsFinal = rsvarsPath || rsvars; const msbuildFinal = msbuildPath || msbuild || 'msbuild'; const args = [ '"' + projPath + '"', '/t:Clean', configuration ? `/p:Config=${configuration}` : '', platform ? `/p:Platform=${platform}` : '' ].filter(Boolean); if (rsvarsFinal && existsSync(rsvarsFinal)) { const cmd = 'cmd'; const composite = [ '/s', '/c', `"@echo off && call \"${rsvarsFinal}\" && ${msbuildFinal} ${args.join(' ')}"` ]; return await runCommand(cmd, composite); } return await runCommand(msbuildFinal, args); } // -------------------- FPC helpers -------------------- function isFpcSource(file: string) { const ext = extname(file).toLowerCase(); return ext === '.pas' || ext === '.pp' || ext === '.p' || ext === '.lpr'; } async function buildWithFpc({ source, output, defines, unitPaths, includePaths, cpu, os, fpcPath }: { source: string; output?: string; defines?: string[]; unitPaths?: string[]; includePaths?: string[]; cpu?: string; os?: string; fpcPath?: string; }) { const srcPath = resolve(source); if (!existsSync(srcPath)) { throw new Error(`Source not found: ${srcPath}`); } if (!isFpcSource(srcPath)) { throw new Error('Unsupported source type. Provide a .lpr/.pas/.pp'); } const args: string[] = []; if (cpu) args.push(`-P${cpu}`); if (os) args.push(`-T${os}`); if (output) args.push(`-o${resolve(output)}`); (defines || []).forEach(d => args.push(`-d${d}`)); (unitPaths || []).forEach(p => args.push(`-Fu${resolve(p)}`)); (includePaths || []).forEach(p => args.push(`-Fi${resolve(p)}`)); args.push('"' + srcPath + '"'); const compiler = fpcPath || 'fpc'; // Use the source directory as CWD so relative includes work return await runCommand(compiler, args, { cwd: dirname(srcPath) }); } // -------------------- Lazarus helpers -------------------- function isLazarusProject(file: string) { return extname(file).toLowerCase() === '.lpi'; } async function lazarusBuild({ project, buildMode, cpu, os, lazbuildPath }: { project: string; buildMode?: string; cpu?: string; os?: string; lazbuildPath?: string; }) { const projPath = resolve(project); if (!existsSync(projPath)) { throw new Error(`Project not found: ${projPath}`); } if (!isLazarusProject(projPath)) { throw new Error('Unsupported project type. Provide a .lpi file'); } const args: string[] = ['--build-mode=']; if (buildMode) args[0] = `--build-mode=${buildMode}`; else args.pop(); if (cpu) args.push(`--cpu=${cpu}`); if (os) args.push(`--os=${os}`); args.push('"' + projPath + '"'); const lazbuild = lazbuildPath || 'lazbuild'; return await runCommand(lazbuild, args, { cwd: dirname(projPath) }); } async function lazarusClean({ project, lazbuildPath }: { project: string; lazbuildPath?: string; }) { const projPath = resolve(project); if (!existsSync(projPath)) { throw new Error(`Project not found: ${projPath}`); } if (!isLazarusProject(projPath)) { throw new Error('Unsupported project type. Provide a .lpi file'); } const args: string[] = ['--clean', '"' + projPath + '"']; const lazbuild = lazbuildPath || 'lazbuild'; return await runCommand(lazbuild, args, { cwd: dirname(projPath) }); } async function main() { const mcpServer = new McpServer({ name: 'mcp-delphi-build', version: '1.1.0', }); // Register tools mcpServer.registerTool('delphi.build', { description: 'Build a Delphi .dproj or .groupproj using MSBuild with RAD Studio environment', inputSchema: BuildInput, }, async (req: any) => { const { code, stdout, stderr } = await buildWithMSBuild(req); const ok = code === 0; return { content: [ { type: 'text', text: ok ? `Build succeeded for ${basename(req.project)}` : `Build failed for ${basename(req.project)}` }, { type: 'text', text: `Exit code: ${code}` }, { type: 'text', text: '--- STDOUT ---\n' + stdout }, { type: 'text', text: '--- STDERR ---\n' + stderr } ], isError: !ok }; }); mcpServer.registerTool('delphi.clean', { description: 'Clean a Delphi .dproj or .groupproj using MSBuild with RAD Studio environment', inputSchema: CleanInput, }, async (req: any) => { const { code, stdout, stderr } = await cleanWithMSBuild(req); const ok = code === 0; return { content: [ { type: 'text', text: ok ? `Clean succeeded for ${basename(req.project)}` : `Clean failed for ${basename(req.project)}` }, { type: 'text', text: `Exit code: ${code}` }, { type: 'text', text: '--- STDOUT ---\n' + stdout }, { type: 'text', text: '--- STDERR ---\n' + stderr } ], isError: !ok }; }); // FPC tool mcpServer.registerTool('fpc.build', { description: 'Build with Free Pascal Compiler (fpc) for a Pascal program or project file', inputSchema: FpcBuildInput, }, async (req: any) => { const { code, stdout, stderr } = await buildWithFpc(req); const ok = code === 0; return { content: [ { type: 'text', text: ok ? `FPC build succeeded for ${basename(req.source)}` : `FPC build failed for ${basename(req.source)}` }, { type: 'text', text: `Exit code: ${code}` }, { type: 'text', text: '--- STDOUT ---\n' + stdout }, { type: 'text', text: '--- STDERR ---\n' + stderr } ], isError: !ok }; }); // Lazarus tools mcpServer.registerTool('lazarus.build', { description: 'Build a Lazarus (.lpi) project using lazbuild', inputSchema: LazarusBuildInput, }, async (req: any) => { const { code, stdout, stderr } = await lazarusBuild(req); const ok = code === 0; return { content: [ { type: 'text', text: ok ? `Lazarus build succeeded for ${basename(req.project)}` : `Lazarus build failed for ${basename(req.project)}` }, { type: 'text', text: `Exit code: ${code}` }, { type: 'text', text: '--- STDOUT ---\n' + stdout }, { type: 'text', text: '--- STDERR ---\n' + stderr } ], isError: !ok }; }); mcpServer.registerTool('lazarus.clean', { description: 'Clean Lazarus build artifacts using lazbuild --clean', inputSchema: LazarusCleanInput, }, async (req: any) => { const { code, stdout, stderr } = await lazarusClean(req); const ok = code === 0; return { content: [ { type: 'text', text: ok ? `Lazarus clean succeeded for ${basename(req.project)}` : `Lazarus clean failed for ${basename(req.project)}` }, { type: 'text', text: `Exit code: ${code}` }, { type: 'text', text: '--- STDOUT ---\n' + stdout }, { type: 'text', text: '--- STDERR ---\n' + stderr } ], isError: !ok }; }); const transport = new StdioServerTransport(); await mcpServer.connect(transport); } main().catch((err) => { console.error('Server error:', err); process.exit(1); });

Latest Blog Posts

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/flydev-fr/mcp-delphi'

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