cURL MCP Server
cURL MCP Server
A security-hardened MCP server that gives LLMs the ability to make HTTP requests via cURL. Use it as a standalone server, extend it programmatically, or define APIs declaratively with YAML.
Key features:
Security-first — SSRF protection, DNS rebinding prevention, rate limiting, input validation
Extensible —
McpCurlServerclass with hooks, custom tools, and configurationYAML-driven — Define API endpoints declaratively and generate MCP tools automatically
Two tools —
curl_executefor HTTP requests,jq_queryfor querying saved JSON files
Quick Start: MCP Server
Claude Code
claude mcp add curl -- npx -y github:sixees/mcp-curlOr add to .mcp.json:
{
"mcpServers": {
"curl": {
"command": "npx",
"args": [
"-y",
"github:sixees/mcp-curl"
]
}
}
}Claude Desktop
Add to your config file:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"curl": {
"command": "npx",
"args": [
"-y",
"github:sixees/mcp-curl"
]
}
}
}Quick Start: Standalone
# Stdio transport (default)
npx -y github:sixees/mcp-curl
# HTTP transport
TRANSPORT=http PORT=3000 npx -y github:sixees/mcp-curl
# HTTP with authentication
TRANSPORT=http PORT=3000 MCP_AUTH_TOKEN=your-secret npx -y github:sixees/mcp-curlOr clone and build locally:
git clone https://github.com/sixees/mcp-curl.git
cd mcp-curl && npm install && npm run build
npm startTools
curl_execute
Execute HTTP requests with structured parameters. Supports all common HTTP methods, authentication (basic, bearer), headers, form data, redirects, and timeouts.
{
"url": "https://api.github.com/users/octocat",
"bearer_token": "ghp_xxx",
"jq_filter": ".name,.email,.location"
}Responses exceeding max_result_size (default 500KB) are automatically saved to file. Use jq_filter to extract
specific data before the size limit is checked.
jq_query
Query saved JSON files without making new HTTP requests:
{
"filepath": "/path/to/saved_response.txt",
"jq_filter": ".users[0:5]"
}Files must be in the temp directory, MCP_CURL_OUTPUT_DIR, or current working directory.
jq_filter syntax
Syntax | Example | Description |
|
| Object property |
|
| Array index (non-negative only) |
|
| Array slice |
|
| Bracket notation |
|
| Multiple paths (returns array) |
Programmatic API
Install as a library and build custom MCP servers:
npm install mcp-curlimport { McpCurlServer } from "mcp-curl";
const server = new McpCurlServer()
.configure({
baseUrl: "https://api.example.com",
defaultHeaders: {"Authorization": `Bearer ${process.env.API_TOKEN}`},
defaultTimeout: 60,
})
.beforeRequest((ctx) => {
console.log(`${ctx.tool}: ${ctx.params.url}`);
})
.afterResponse((ctx) => {
console.log(`Response: ${ctx.response.length} bytes`);
});
await server.start("stdio");See the library documentation for the full API reference, including hooks, custom tools, instance utilities, and lifecycle management.
Sanitizing externally-sourced field descriptions
registerCustomTool() sanitizes title, description, and every .describe() string
inside inputSchema automatically — recursing through nested z.object(), z.array(),
z.union() / z.discriminatedUnion(), z.tuple(), z.record() / z.map(), z.set(),
z.intersection(), z.lazy(), .transform() / .pipe(), and through
z.optional() / z.default() / z.nullable() / .readonly() / .catch() / z.promise()
wrappers. The walker mutates z.globalRegistry in place, preserving every Zod runtime
invariant (.refine() / .check() chains, .strict() / .passthrough() modes, array
length constraints, factory defaults, ZodDiscriminatedUnion discriminators). You don't
need to call sanitizeDescription() manually.
The example below applies sanitizeDescription() defensively at the call site as well — it's
optional: registration-time sanitisation already covers every depth. Use it as belt-and-suspenders
when wiring untrusted strings (database rows, remote APIs, user-authored YAML) into Zod descriptions.
import { z } from "zod";
import { sanitizeDescription } from "mcp-curl";
// `server` is the McpCurlServer instance from the example above.
const fieldMeta = await fetchFieldDescriptionsFromDb();
server.registerCustomTool(
"search_records",
{
title: "Search records", // sanitized internally
description: "Search the catalog.", // sanitized internally
inputSchema: z.object({
// sanitizeDescription() here is defensive — registerCustomTool() also
// sanitises every .describe() string at every depth at registration time.
q: z.string().describe(sanitizeDescription(fieldMeta.q)),
limit: z.number().int().min(1).max(100)
.describe(sanitizeDescription(fieldMeta.limit)),
}),
},
async (params) => { /* handler logic */ }
);For trusted internal strings, no sanitization is required. See docs/custom-tools.md for the full discussion.
YAML Schema
Define API endpoints declaratively and generate MCP tools:
import { createApiServer } from "mcp-curl";
const server = await createApiServer({
definitionPath: "./my-api.yaml",
});
await server.start("stdio");apiVersion: "1.0"
api:
name: my-api
baseUrl: https://api.example.com
endpoints:
- id: list_items
path: /items
method: GET
title: List Items
description: Get all items
parameters:
- name: page
in: query
type: integer
required: falseSee YAML Schema Reference for the full specification including authentication, defaults, response filtering, and parameter types.
Fork Workflow
If you fork this repo to build an API-specific server, use the configs/ directory for your definitions:
# 1. Fork and clone
git clone https://github.com/your-org/mcp-curl.git
cd mcp-curl && npm install && npm run build
# 2. Copy the template
cp configs/example.yaml.template configs/my-api.yaml
# 3. Edit your API definition
# See docs/api-schema.md for the full YAML specification
# 4. Create your entry point (configs/*.ts is gitignored)
# See configs/README.md for a full TypeScript template
# 5. Run your server (using tsx to run the TS file directly)
npx tsx configs/my-api.tsFiles in configs/ matching *.yaml, *.yml, *.ts, *.js are gitignored, so pulling upstream changes
(git pull upstream main) won't conflict with your application-specific configuration.
Alternatively, install mcp-curl as an npm dependency in a separate project — see
Getting Started.
Security Highlights
SSRF protection — blocks private IPs, cloud metadata endpoints, DNS rebinding services, internal TLDs
DNS rebinding prevention — DNS resolved before validation, cURL pinned to validated IP via
--resolveProtocol whitelist — only
http://andhttps://allowed;file://,ftp://,data:,javascript:, etc. blocked at the schema layer (createHttpOnlyUrlSchema), the SSRF layer, and via cURL--protoRate limiting — 60 req/min per host, 300 req/min per client
Input validation — Zod schemas, CRLF injection prevention,
--data-raw/--form-stringto block@file readsNo shell execution — commands spawned via
spawn()without shell; allowlist permits onlycurlFile access control —
jq_queryrestricted to temp dir,MCP_CURL_OUTPUT_DIR, and cwd; symlinks resolvedResource limits — 10MB response cap, 100MB global memory, 100ms jq parse timeout, 30s default request timeout, 256 KB HTML/markdown strip cap
Secure file permissions — temp dirs 0o700, files 0o600 (owner-only)
Localhost port restrictions — when
MCP_CURL_ALLOW_LOCALHOST=true, only ports80,443, and any port> 1024are reachable; ports1–1024(other than80/443) stay blocked even with the flag set, to prevent the LLM from reaching SSH (22), SMTP (25), DNS (53), etc.Auth-token validation —
MCP_AUTH_TOKENrejected at HTTP startup if not printable ASCII (0x20–0x7E) or longer than 4096 chars; the rejected token is never echoed in error messages; bearer comparison is timing-safe (crypto.timingSafeEqualover length-padded buffers); theBearerscheme is matched case-insensitively per RFC 6750 §2.1YAML pre-sanitisation invariant —
loadApiSchema(),loadApiSchemaFromString(),validateApiSchema(), AND the directly-re-exportedApiSchemaValidator.parse()all run a singlez.preprocess()step that sanitises every user-facing string field before Zod validates structure, so attacker-controlled bidi/zero-width bytes never reach the LLM and never appear in Zod error messagesCustom-tool input-schema sanitisation —
registerCustomTool()walksinputSchemaand sanitises every.describe()string at every depth in place via Zod'sglobalRegistry(recurses throughZodObject,ZodArray,ZodUnion/ZodDiscriminatedUnion,ZodTuple,ZodRecord/ZodMap,ZodSet,ZodIntersection,ZodPipe,ZodLazy, and throughZodOptional/ZodDefault/ZodNullable/ZodReadonly/ZodCatch/ZodPromisewrappers); all Zod runtime invariants preservedDefence-in-depth response wrap — every tool result (
curl_execute,jq_query, YAML endpoints, custom tools, hook short-circuit returns) routes through a single internal post-processor that runsdetect-on-original → sanitise → optional spotlighton each text part; idempotent via a module-private (non-Symbol.for) tag; fail-open with throttled[wrap-error]logResponse sanitisation — Unicode attack chars (bidi overrides, zero-width family, "Sneaky Bits" Variation Selector Supplement, Braille blank, Hangul fillers, Mongolian invisibles, Arabic Letter Mark, …) stripped from text responses; visual-space-padding runs (50+ tabs / NBSP / em-spaces / IDEOGRAPHIC SPACE) and newline runs (20+, with single inline-whitespace interrupters tolerated) collapsed before reaching the LLM; idempotence loop (≤4 iterations) defeats
(49 spaces + ZWSP) × NinterleavingHTML / Markdown content stripping —
<script>and<style>blocks removed from HTML/XHTML/SVG/*+xmlresponses (ReDoS-hardened with bounded fixed-point stripping, numeric-entity decode inside the loop, 256 KB cap); body-shape sniffer catches markup served under tampered Content-Type; Markdown image beacons and external-URL links replaced with[image removed]/[link removed]; dangerous-scheme URLs (javascript:,vbscript:,file:,data:) blocked in Markdown links and images, including the[](javascript:...)nesting caseInjection-detection signal —
[injection-defense] [hostname] InjectionDetectedlogged to stderr (throttled 60s per hostname) when a NFKC-normalised regex matches the original response text; observability only — never refuses, redacts, or alters content
Environment Variables
Variable | Description |
| Transport mode: |
| HTTP transport port (default: 3000) |
| Bearer token for HTTP transport auth (printable ASCII, ≤4096) |
| Default directory for saved responses |
| Set |
| HTTP transport bind address (default: |
| Comma-separated origins for HTTP |
| Default User-Agent for every request (empty string disables) |
| Default Referer for every request (empty string disables) |
Documentation
Guide | Description |
| |
Step-by-step setup guide | |
All configuration options | |
Request/response interception | |
Creating custom MCP tools | |
API definition format |
Examples
Working example projects in examples/:
basic/— Minimal custom serverwith-hooks/— Authentication and logging hooksfrom-yaml/— Server from YAML API definition
MCP Resources & Prompts
Resource:
curl://docs/api— Built-in API documentationPrompts:
api-test(test an endpoint),api-discovery(explore a REST API)
License
MIT
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Tools
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/sixees/mcp-curl'
If you have feedback or need assistance with the MCP directory API, please join our Discord server