Skip to main content
Glama

Curl MCP Server

index.ts15 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { spawn } from 'child_process'; class CurlMCPServer { private server: Server; private readonly MAX_OUTPUT_SIZE = 1024 * 1024; // 1MB constructor() { this.server = new Server({ name: 'curl-mcp-server', version: '1.0.0', capabilities: { tools: {}, }, }); this.setupToolHandlers(); this.setupErrorHandling(); } private setupToolHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'curl_get', description: 'Execute a GET request using curl', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'The URL to request', }, headers: { type: 'array', items: { type: 'string' }, description: 'Optional headers in format "Key: Value"', }, timeout: { type: 'number', description: 'Timeout in seconds (max 30)', maximum: 30, default: 10, }, follow_redirects: { type: 'boolean', description: 'Follow HTTP redirects', default: true, }, }, required: ['url'], }, }, { name: 'curl_post', description: 'Execute a POST request using curl', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'The URL to request', }, data: { type: 'string', description: 'POST data to send', }, content_type: { type: 'string', description: 'Content-Type header', default: 'application/json', }, headers: { type: 'array', items: { type: 'string' }, description: 'Optional headers in format "Key: Value"', }, timeout: { type: 'number', description: 'Timeout in seconds (max 30)', maximum: 30, default: 10, }, }, required: ['url', 'data'], }, }, { name: 'curl_custom', description: 'Execute a custom curl command with full control', inputSchema: { type: 'object', properties: { args: { type: 'array', items: { type: 'string' }, description: 'Curl arguments (without the curl command itself)', }, timeout: { type: 'number', description: 'Timeout in seconds (max 30)', maximum: 30, default: 10, }, }, required: ['args'], }, }, { name: 'curl_download', description: 'Download a file using curl and return metadata', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'The URL to download from', }, output_file: { type: 'string', description: 'Output file path (optional, uses URL filename if not provided)', }, timeout: { type: 'number', description: 'Timeout in seconds (max 30)', maximum: 30, default: 30, }, }, required: ['url'], }, }, ], }; }); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'curl_get': return await this.handleGetRequest(args); case 'curl_post': return await this.handlePostRequest(args); case 'curl_custom': return await this.handleCustomRequest(args); case 'curl_download': return await this.handleDownload(args); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${name}` ); } } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}` ); } }); } private async handleGetRequest(args: any) { const { url, headers = [], timeout = 10, follow_redirects = true } = args; this.validateUrl(url); const curlArgs = [ '-s', // Silent mode '-i', // Include headers in output '--max-time', timeout.toString(), ]; if (follow_redirects) { curlArgs.push('-L'); } // Add headers headers.forEach((header: string) => { curlArgs.push('-H', header); }); curlArgs.push(url); const result = await this.executeCurl(curlArgs, timeout * 1000); return this.formatResponse(result, 'GET', url); } private async handlePostRequest(args: any) { const { url, data, content_type = 'application/json', headers = [], timeout = 10 } = args; this.validateUrl(url); const curlArgs = [ '-s', // Silent mode '-i', // Include headers in output '-X', 'POST', '--max-time', timeout.toString(), '-H', `Content-Type: ${content_type}`, '-d', data, ]; // Add additional headers headers.forEach((header: string) => { curlArgs.push('-H', header); }); curlArgs.push(url); const result = await this.executeCurl(curlArgs, timeout * 1000); return this.formatResponse(result, 'POST', url); } private async handleCustomRequest(args: any) { const { args: curlArgs, timeout = 10 } = args; // Basic security check - prevent dangerous operations this.validateCurlArgs(curlArgs); const finalArgs = ['-s', '-i', '--max-time', timeout.toString(), ...curlArgs]; const result = await this.executeCurl(finalArgs, timeout * 1000); return this.formatResponse(result, 'CUSTOM', curlArgs.join(' ')); } private async handleDownload(args: any) { const { url, output_file, timeout = 30 } = args; this.validateUrl(url); const curlArgs = [ '-s', // Silent mode '-I', // Head request to get metadata '--max-time', timeout.toString(), url, ]; // First, get file metadata const metadataResult = await this.executeCurl(curlArgs, timeout * 1000); // Then download if output_file is specified let downloadResult = null; if (output_file) { const downloadArgs = [ '-s', '--max-time', timeout.toString(), '-o', output_file, url, ]; downloadResult = await this.executeCurl(downloadArgs, timeout * 1000); } return { content: [ { type: 'text', text: `Download operation completed for: ${url}\n\n` + `Metadata:\n${metadataResult.stdout}\n\n` + (downloadResult ? `Download saved to: ${output_file}\n` : 'Metadata only (no file downloaded)\n') + (metadataResult.stderr ? `Errors: ${metadataResult.stderr}` : ''), }, ], }; } private async executeCurl(args: string[], timeoutMs: number): Promise<{ stdout: string, stderr: string, exitCode: number }> { return new Promise((resolve, reject) => { const child = spawn('curl', args, { stdio: ['pipe', 'pipe', 'pipe'], }); let stdout = ''; let stderr = ''; let outputSize = 0; const timeout = setTimeout(() => { child.kill('SIGTERM'); reject(new McpError(ErrorCode.InternalError, 'Curl command timed out')); }, timeoutMs); child.stdout.on('data', (data) => { outputSize += data.length; if (outputSize > this.MAX_OUTPUT_SIZE) { child.kill('SIGTERM'); reject(new McpError(ErrorCode.InternalError, 'Output size exceeded maximum limit')); return; } stdout += data.toString(); }); child.stderr.on('data', (data) => { stderr += data.toString(); }); child.on('close', (code) => { clearTimeout(timeout); resolve({ stdout, stderr, exitCode: code || 0, }); }); child.on('error', (error) => { clearTimeout(timeout); reject(new McpError(ErrorCode.InternalError, `Failed to execute curl: ${error.message}`)); }); }); } private validateUrl(url: string) { try { const parsed = new URL(url); if (!['http:', 'https:'].includes(parsed.protocol)) { throw new McpError(ErrorCode.InvalidParams, 'Only HTTP and HTTPS URLs are allowed'); } } catch (error) { throw new McpError(ErrorCode.InvalidParams, 'Invalid URL format'); } } private validateCurlArgs(args: string[]) { const dangerousFlags = [ '--config', '-K', // Config file '--output', '-o', // Output to file (controlled separately) '--remote-name', '-O', // Save with remote name '--upload-file', '-T', // Upload file '--form', '-F', // Form upload '--data-binary', // Binary data '--trace', '--trace-ascii', // Trace to file ]; for (const arg of args) { for (const dangerous of dangerousFlags) { if (arg.startsWith(dangerous)) { throw new McpError( ErrorCode.InvalidParams, `Dangerous curl flag not allowed: ${dangerous}` ); } } } } private formatResponse(result: { stdout: string, stderr: string, exitCode: number }, method: string, url: string) { const { stdout, stderr, exitCode } = result; let responseText = `Curl ${method} Request: ${url}\n`; responseText += `Exit Code: ${exitCode}\n\n`; if (stdout) { // Try to separate headers and body const parts = stdout.split('\r\n\r\n'); if (parts.length >= 2) { responseText += `Headers:\n${parts[0]}\n\n`; responseText += `Body:\n${parts.slice(1).join('\r\n\r\n')}`; } else { responseText += `Response:\n${stdout}`; } } if (stderr) { responseText += `\n\nErrors/Warnings:\n${stderr}`; } return { content: [ { type: 'text', text: responseText, }, ], }; } private setupErrorHandling() { this.server.onerror = (error) => { console.error('[MCP Error]', error); }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Curl MCP Server running on stdio'); } } // Start the server const server = new CurlMCPServer(); server.run().catch(console.error);

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/neeraj15022001/curl-mcp-server'

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