#!/usr/bin/env node
import axios from 'axios';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
/**
* Configuration
*/
const MAX_RESPONSE_SIZE = 50000; // Maximum response size in characters before truncating
/**
* Create an MCP server to handle HTTP requests.
*/
const server = new Server(
{
name: 'Web Request MCP Server',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
},
);
/**
* Handler for listing available tools.
* Provides a tool to execute HTTP requests.
*/
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'http_request',
description:
'Execute an HTTP request with custom method, URL, headers, and body. Supports GET, POST, PUT, DELETE, PATCH, and other HTTP methods. The AI can choose all parameters based on the task requirements.',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description:
'The URL to send the request to (e.g., "https://api.example.com/endpoint")',
},
method: {
type: 'string',
description:
'HTTP method to use (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, etc.). Defaults to GET if not specified.',
enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
},
headers: {
type: 'object',
description:
'Optional HTTP headers as key-value pairs (e.g., {"Content-Type": "application/json", "Authorization": "Bearer token"})',
additionalProperties: {
type: 'string',
},
},
body: {
type: ['string', 'object'],
description:
'Optional request body. Can be a string or an object (will be JSON stringified for objects)',
},
params: {
type: 'object',
description:
'Optional URL query parameters as key-value pairs (e.g., {"id": "123", "filter": "active"})',
additionalProperties: {
type: 'string',
},
},
},
required: ['url'],
},
},
],
};
});
/**
* Interface for HTTP request parameters
*/
interface HttpRequestParams {
url: string;
method?: string;
headers?: Record<string, string>;
body?: any;
params?: Record<string, string>;
}
/**
* Function to execute an HTTP request.
* @param {HttpRequestParams} params - Request parameters
* @returns {Promise<any>}
*/
async function executeHttpRequest(params: HttpRequestParams): Promise<any> {
try {
const { url, method = 'GET', headers = {}, body, params: queryParams } = params;
// Prepare request configuration
const config: any = {
method: method.toUpperCase(),
url: url,
headers: headers,
};
// Add query parameters if provided
if (queryParams && Object.keys(queryParams).length > 0) {
config.params = queryParams;
}
// Add body if provided (for methods that support it)
if (body !== undefined) {
if (typeof body === 'object' && !Buffer.isBuffer(body)) {
config.data = body;
// Set Content-Type if not already set
if (!config.headers['Content-Type'] && !config.headers['content-type']) {
config.headers['Content-Type'] = 'application/json';
}
} else {
config.data = body;
}
}
const response = await axios(config);
return {
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: response.data,
};
} catch (error: any) {
return {
error: true,
message: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
headers: error.response?.headers,
data: error.response?.data,
};
}
}
/**
* Function to truncate response if it's too long
*/
function truncateResponse(response: any): { response: any; truncated: boolean } {
const responseText = JSON.stringify(response, null, 2);
if (responseText.length > MAX_RESPONSE_SIZE) {
const truncatedText = responseText.substring(0, MAX_RESPONSE_SIZE);
return {
response: {
...response,
data: '[TRUNCATED - Response too large. Only first ' + MAX_RESPONSE_SIZE + ' characters shown]',
_originalSize: responseText.length,
},
truncated: true,
};
}
return {
response: response,
truncated: false,
};
}
/**
* Handler for the http_request tool.
*/
server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'http_request': {
const args = request.params.arguments;
if (!args?.url) {
throw new Error('URL is required');
}
const requestParams: HttpRequestParams = {
url: String(args.url),
method: args.method ? String(args.method) : 'GET',
headers: args.headers as Record<string, string> | undefined,
body: args.body,
params: args.params as Record<string, string> | undefined,
};
const response = await executeHttpRequest(requestParams);
// Truncate response if necessary
const { response: finalResponse, truncated } = truncateResponse(response);
let resultText = JSON.stringify(finalResponse, null, 2);
if (truncated) {
resultText +=
'\n\n⚠️ Response too large, truncated to ' +
MAX_RESPONSE_SIZE +
' characters. Consider using more specific query parameters or filtering the response.';
}
return {
content: [
{
type: 'text',
text: resultText,
},
],
};
}
default:
throw new Error('Unknown tool');
}
});
/**
* Start the server using stdio transport.
*/
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((error) => {
console.error('Server error:', error);
process.exit(1);
});