mcponce
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcponceturn my REST API at openapi.yaml into an MCP server"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcponce
Cross-platform MCP server from a single executable file powered by Hono and the official Model Context Protocol SDK.
Project Status: Alpha (v0.2.x)
mcponce is currently in active Alpha development. Core features (dual-transport stdio/HTTP bridging, background daemon sharing, caching, concurrency controls, and CLI tools) are functional and tested, but APIs and internal interfaces are subject to refinement before a v1.0.0 stable release. We welcome community testing and feedback.
Add the MCP server once in your client settings, point it to one executable file, and let that file automatically start or reuse the local server.
Claude / Cursor / Antigravity / VS Code
│
│ stdio
▼
Single executable file
│
├── starts server if needed
├── discovers existing server
├── proxies MCP messages
└── writes logs/runtime state
│
▼
Local Hono + MCP server
│
└── shared tools/resources/contextFeatures
Single Executable UX: No manual daemons to start, stop, or manage. One executable handles both server ownership and stdio bridging.
Cross-Platform: Works out of the box on macOS, Linux, and Windows using native OS application state paths.
Dynamic Port Allocation: Defaults to
port: 0so the operating system assigns an available port. No port conflicts.Robust Single-Instance: Atomic lock files with
O_CREAT | O_EXCL, HTTP/healthverification, race handling with retry backoff, and automatic stale lock/crash recovery.Shared Application Context: Initialize database, caches, or connections once for the server, shared across all simultaneous client sessions.
Smart Input Coercion & Schema Defaults: Shields handlers from LLM stringification errors (
"42"->42,"false"->false, JSON strings to objects) withcoerceInputs: trueand schema defaults.Automatic Retries with Exponential Backoff: Declarative
retry: 3with configurable exponential delays, cancellation aborts, selective predicates, and telemetry tracking.In-Memory Response Caching: LRU response caching (
cache: trueor custom TTL) with deterministic argument sorting and fine-grained invalidation.Sequential Queuing & Mutexes: Prevent race conditions with
sequential: trueor named mutex locks.Images & Binary Media: Return raw
Buffers or useimage(buffer)helpers with automatic magic byte MIME detection (PNG, JPEG, GIF, WEBP, BMP, SVG) and base64 encoding.Dynamic Resource URI Templates (RFC 6570): Expose parametric resources (
users://{userId}/profile) with parameter extraction, client autocompletion callbacks, and automatic return normalization.Resource Subscriptions & Live Push: Client subscriptions (
resources/subscribe,resources/unsubscribe) with live push updates (app.notifyResourceUpdated,app.notifyResourceListChanged).Security, Authentication & Rate Limiting: Multi-key Bearer/API Key auth, custom identity validators (
auth.validate), tool-level RBAC scopes, sliding-window rate limiting (429 Retry-After), and standard security headers.MCP Sampling & Workspace Roots: Enable autonomous sub-agents by requesting LLM completions back from the client (
context.sample) and querying open IDE workspace directories (context.listRoots).OpenAPI / Swagger Auto-Generation: Transform any REST API into type-safe MCP tools in 1 line with
app.fromOpenApi().Interactive Web Inspector & Playground: Zero-dependency browser developer UI at
/inspect(mcponce inspect/server.js inspect) with dynamic form generation, execution diagnostics, and live metrics.MCP Autocomplete Protocol: Full
completion/completesupport for prompt arguments, resource template variables, and tool parameters.Client Auto-Installer: Automatically configure servers into Claude Desktop or Cursor in 1 command:
node server.js installormcponce install <target>.Clean Protocol Communication:
stdoutis reserved strictly for MCP JSON-RPC protocol messages. Diagnostic and error logs are written safely to log files andstderr.Hono & Official MCP SDK: Native Web Standards Streamable HTTP transport integration.
Related MCP server: Enterprise MCP Server Template
Comparison & Alternatives
Different tools in the MCP ecosystem serve distinct architectural needs and maturity levels:
Dimension / Capability | Official SDK ( | FastMCP (TypeScript / Python) | Standalone Proxies (e.g., Supergateway) | mcponce |
Maturity / Stage | Official Reference (Stable) | Community Standard (Mature) | Production Utility (Stable) | Alpha ( |
Primary Focus | Specification reference & wire protocol primitives | Ergonomic syntax for quick scripts & tools | Wrapping existing stdio servers without code changes | All-in-one developer framework & daemon bridge |
HTTP Transport | Core transport primitives (SSE/HTTP) | Built-in HTTP/SSE server | Built-in HTTP proxy | Native Hono Streamable HTTP & stdio |
Multi-Client Daemon | Userland (1 process per stdio connection) | Userland (1 process per connection) | Per-process or gateway pool | Single shared background daemon via Unitup |
Concurrency & Mutex | Userland implementation | Standard async | Proxy-level buffering | Built-in FIFO queues & named mutexes ( |
Input Coercion | JSON Schema (strict validation) | First-class Zod validation | Raw pass-through | Zod + Smart coercion ( |
Response Caching | Userland implementation | Userland implementation | Not applicable | Built-in LRU cache with TTL & invalidation |
Inter-Tool Calling | Manual invocation | Manual invocation | Not applicable | Built-in |
Automatic Retries | Userland implementation | Userland implementation | Not applicable | Declarative backoff & jitter ( |
Observability & Metrics | Custom logger integration | Standard logging & events | Request logs | Built-in Prometheus ( |
CLI & Testing | Via MCP clients / inspector package | Built-in CLI & dev mode | HTTP / curl | Built-in |
Client Auto-Installer | Manual JSON config | Built-in CLI install / manual | Manual JSON config | Built-in |
Developer UI | Separate | Built-in | Not applicable | Built-in |
OpenAPI Auto-Gen | Userland scripts | Community plugins / custom | Not applicable | Built-in |
When to choose what?
Choose the Official SDK if you want the canonical, stable reference implementation from Anthropic, require minimal third-party dependencies, or are embedding MCP into existing frameworks (NestJS, Express, Fastify) that already manage their own lifecycle, caching, and queueing.
Choose FastMCP if you want an established, mature community standard with a clean, concise syntax for quickly exposing scripts and tools to AI clients without needing daemon multiplexing or Prometheus metrics.
Choose Standalone Proxies (Supergateway / mcp-proxy) if you have an existing third-party stdio MCP server binary and want to expose it over HTTP/SSE without modifying any code.
Choose
mcponceif you want an all-in-one developer experience with shared background daemon efficiency (avoiding duplicate processes for multiple IDE/Claude windows), built-in resilience primitives (mutex queues, input coercion, retries, cache), and turnkey CLI/Inspector tooling — keeping in mind that mcponce is currently in Alpha (v0.2.x) and actively stabilizing.
Installation
npm install mcponceQuick Start
import { createMcpServer } from "mcponce";
// 1-parameter shorthand or config object:
const app = createMcpServer("my-mcp");
app.tool({
name: "hello",
description: "Say hello",
inputSchema: {
name: "string"
},
async handler({ name }) {
return {
content: [{ type: "text", text: `Hello ${name}!` }]
};
}
});
app.run();Calling Tools Within One Another (Inter-Tool Invocations)
Tools can invoke other registered tools internally with full argument validation, automatic cycle detection, and unwrapped data access:
app.tool({
name: "add",
inputSchema: { a: "number", b: "number" },
handler: ({ a, b }) => a + b
});
app.tool({
name: "add_and_double",
inputSchema: { a: "number", b: "number" },
// 1. Access callTool directly from the context parameter:
async handler({ a, b }, { callTool }) {
const sumResult = await callTool("add", { a, b });
return sumResult.data * 2;
}
});You can also invoke tools programmatically directly on the server instance (great for testing):
const result = await app.callTool("add", { a: 10, b: 20 });
console.log(result.data); // 30
console.log(result.text); // "30"Images & Binary Media Support (Screenshots, Charts & Buffers)
Many tools produce visual output (e.g. Playwright browser screenshots, canvas charts, PDF previews). In mcponce, returning a raw Buffer or using image(buffer) automatically normalizes into the standard MCP ImageContent block with magic-byte MIME type detection (PNG, JPEG, GIF, WEBP, BMP, SVG):
import { image } from "mcponce";
import fs from "node:fs/promises";
// 1. Direct Buffer return (MIME type is auto-detected from magic bytes!)
app.tool({
name: "capture_screen",
handler: async () => {
return await fs.readFile("./screenshot.png");
}
});
// 2. Mixed multi-part content (Text + Image)
app.tool({
name: "render_dashboard",
handler: async () => {
const chartBuffer = await generateChart();
return [
"Here is the Q3 performance report:",
image(chartBuffer),
"Summary: Growth exceeded forecasts by 18%."
];
}
});Dynamic Resource URI Templates (RFC 6570)
Expose parametric, dynamic resources to AI clients and developers. mcponce supports RFC 6570 URI templates with automated variable extraction, client autocompletions, and return normalization:
app.resourceTemplate({
uriTemplate: "users://{userId}/profile",
name: "user_profile",
description: "User profile details",
mimeType: "application/json",
complete: {
// Autocompletions surfaced in Claude Desktop & Cursor
userId: async (prefix) => ["alice", "bob", "charlie"].filter((u) => u.startsWith(prefix))
},
handler: async (uri, { userId }) => {
// Return objects, strings, or Buffers—mcponce normalizes them automatically!
return {
userId,
role: "developer",
url: uri.href
};
}
});
// Programmatic reading (great for tests and inter-service reads)
const profile = await app.readResource("users://alice/profile");Resource Subscriptions & Live Push Notifications
Clients like Claude Desktop or Cursor can subscribe to dynamic resources via resources/subscribe. When application state changes, notify all subscribed clients in real time using app.notifyResourceUpdated(uri) or notify that the resource list changed using app.notifyResourceListChanged().
app.resource({
uri: "status://system",
name: "system_status",
mimeType: "application/json",
handler: async () => ({ status: getSystemStatus(), timestamp: Date.now() })
});
// Broadcast live push notification to all subscribed clients:
app.notifyResourceUpdated("status://system");
// Broadcast when new resources are added or removed:
app.notifyResourceListChanged();
// Hook into subscription events:
app.onResourceUpdated((uri, sessionIds) => {
console.log(`Resource ${uri} was updated for ${sessionIds.length} subscribers`);
});Security, Authentication & Rate Limiting
Secure your MCP HTTP/SSE endpoints with multi-key authentication, custom identity validators (auth.validate), tool-level RBAC scopes, sliding-window rate limiting, and standard security headers. The public /health endpoint remains open for container orchestration and uptime monitoring.
const app = createMcpServer({
name: "secure-mcp-server",
// 1. Static API keys (Bearer token or X-API-Key header)
apiKey: ["secret-token-12345", "backup-key-67890"],
// 2. Or custom identity validator with RBAC scopes
auth: {
validate: async (token, context) => {
const user = await verifyJwt(token);
return user ? { id: user.id, user: user.email, scopes: user.scopes } : false;
}
},
// 3. Sliding-window rate limiting (returns 429 & Retry-After)
rateLimit: {
max: 100,
windowMs: 60 * 1000
},
// 4. Configurable CORS & security headers
cors: {
origin: "https://app.example.com",
credentials: true
}
});
// 5. Tool-level permission scopes
app.tool({
name: "admin_delete_user",
inputSchema: { userId: "string" },
requireAuth: true,
scopes: ["admin:users", "write"],
handler: async ({ userId }, ctx, extra) => {
console.log(`Executed by: ${extra.auth?.user}`);
return `User ${userId} deleted`;
}
});Clients authenticate using standard headers or CLI flags:
Authorization Header:
Authorization: Bearer secret-token-12345Custom Header:
X-API-Key: secret-token-12345CLI Caller:
mcponce call my-server admin_delete_user --userId 42 --token secret-token-12345
MCP Sampling & Workspace Roots (Sub-Agents)
Turn your tools into autonomous sub-agents by requesting LLM completions (sampling/createMessage) back from the connected client (Claude Desktop, Cursor, AI agents). Tools can also discover open project folders via the MCP Roots protocol (roots/list).
app.tool({
name: "code_reviewer",
inputSchema: { patch: "string" },
async handler({ patch }, { sample, listRoots }) {
// 1. Discover user's project directories in their IDE
const roots = await listRoots();
// 2. Request an LLM completion from the connected client
const reply = await sample({
prompt: `Review this diff and suggest performance improvements:\n\n${patch}`,
systemPrompt: "You are a senior code reviewer. Output a concise markdown report.",
maxTokens: 500,
temperature: 0.2
});
return {
review: reply.text,
modelUsed: reply.model,
workspace: roots[0]?.name
};
}
});
// Configure offline mock fallback during testing or standalone runs:
app.onSample(async (params) => {
return "Mock review for test environment";
});OpenAPI & Swagger Tool Auto-Generation (app.fromOpenApi)
Transform entire REST APIs into type-safe MCP tools in a single line. Supports OpenAPI v3.0, v3.1, and Swagger 2.0 specs via remote URLs, local file paths, JSON/YAML strings, or JS objects:
// 1. From remote URL or local file path
await app.fromOpenApi("https://petstore.swagger.io/v2/swagger.json", {
prefix: "petstore", // e.g. "petstore_get_pet_by_id"
headers: {
Authorization: `Bearer ${process.env.API_KEY}`
}
});
// 2. Or from an inline specification with tag filtering
await app.fromOpenApi(specObject, {
baseUrl: "https://api.example.com",
tags: ["users", "billing"], // only generate tools for these tags
exclude: ["internal_*"],
transformResponse: (res) => res.data // extract inner payload
});All query, path, and header parameters as well as JSON request bodies are automatically mapped to pre-compiled Zod schemas with complete parameter type coercion.
Workspace Roots Security & Path Traversal Guards
When LLM agents operate across user files, mcponce provides path traversal defense helpers to ensure all file operations stay safely confined inside open IDE workspace roots:
import { isPathInWorkspace, resolveWorkspacePath } from "mcponce";
app.tool({
name: "read_workspace_file",
inputSchema: { relativePath: "string" },
async handler({ relativePath }, { listRoots }) {
const roots = await listRoots();
// Resolves path and throws an error if "../" attempts to escape roots
const safePath = resolveWorkspacePath(relativePath, roots);
return await fs.readFile(safePath, "utf-8");
}
});Interactive Web Inspector & Playground (/inspect)
Every mcponce server includes a zero-dependency, dark-mode developer playground accessible directly at http://localhost:<port>/inspect.
Launch it directly during development:
# Start your server and auto-open the Web Inspector
node server.js inspect
# Or open inspector for an active background server
mcponce inspect my-serverDynamic Form Generation: Form controls automatically generated from tool
inputSchemawith type validation and sample value population.Live Output Console: Syntax-highlighted JSON trees, execution duration timers in milliseconds, and LRU cache HIT/MISS indicators.
Binary Media Renderer: Renders image outputs directly in the browser.
Live Telemetry: Real-time request counts, error counts, active sessions, and direct links to
/metrics(Prometheus) and/analytics(JSON).
MCP Autocomplete Protocol (completion/complete)
Enable instant autocompletion in AI clients (Cursor, Claude Desktop, Antigravity) as users or agents type arguments:
// 1. Prompt Argument Autocompletion
app.prompt({
name: "conventional_commit",
argsSchema: { type: "string" },
complete: {
type: (query) => ["feat", "fix", "docs", "style", "refactor", "test"].filter((t) => t.startsWith(query))
},
handler: ({ type }) => ({ messages: [...] })
});
// 2. Resource Template Autocompletion
app.resourceTemplate({
uriTemplate: "repos://{owner}/{repo}",
complete: {
owner: (query) => ["litepacks", "facebook", "microsoft"].filter((o) => o.startsWith(query)),
repo: (query, context) => context?.arguments?.owner === "litepacks" ? ["mcponce", "unitup"] : []
},
handler: async (uri, params) => ...
});Strict Naming Validation (AI Provider & Cross-Platform Compatibility)
Many AI providers (OpenAI, Anthropic Claude, Google Gemini) and MCP clients (Cursor, Claude Desktop, Antigravity) enforce strict naming requirements on tools and functions. For instance, OpenAI strictly rejects tool names with spaces, dots, or symbols (^[a-zA-Z0-9_-]{1,64}$), causing runtime API errors if violated.
mcponce automatically validates names upon registration and provides clear, actionable error messages with suggested alternatives:
Tool Names (
tool.name): Must be 1–64 characters matching/^[a-zA-Z0-9_-]{1,64}$/. No spaces, dots, slashes, or unicode symbols.Parameter Names (
inputSchemakeys): Must be 1–64 characters matching/^[a-zA-Z0-9_-]{1,64}$/to ensure schema compatibility with all LLM tool calling providers.Server Names (
config.name): Must be 1–128 characters matching/^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*$/. Disallows path traversal (..,/,\), Windows reserved names (CON,PRN,NUL, etc.), and whitespace, guaranteeing cross-platform filesystem and daemon compatibility.Prompt Names (
prompt.name): Must match/^[a-zA-Z0-9_-]{1,64}$/.
import { isValidToolName, sanitizeToolName, sanitizeServerName } from "mcponce";
console.log(isValidToolName("get_user")); // true
console.log(isValidToolName("get user")); // false (spaces rejected by LLM providers)
console.log(sanitizeToolName("my tool: calculate!")); // "my_tool_calculate"Timeouts, Cancellation, and Error Resilience
mcponce provides built-in timeout, cancellation, and error handling out of the box:
Per-Tool & Global Timeouts: Set
timeoutMsper tool ortoolTimeoutMsglobally on the server (defaults to 60s, configurable viaMCP_TOOL_TIMEOUT_MS). SettimeoutMs: 0to disable timeouts for long-running batch jobs.Client Cancellation (
notifications/cancelled): When a user clicks "Stop" in Claude, Cursor, or Antigravity, the client sendsnotifications/cancelled.mcponceautomatically aborts the tool'ssignal(AbortSignal).Resilient Handler Racing: Even if a tool handler ignores the signal and hangs in a promise,
mcponceraces execution against the timeout/cancellation signal and aborts promptly without hanging the server.MCP Spec Compliant Errors: Tool exceptions and timeouts never crash the server or tear down HTTP/SSE sessions. They return structured
{ isError: true, content: [...] }so LLMs can observe the failure and self-correct.
app.tool({
name: "fetch_webpage",
description: "Fetches webpage content with cancellation & timeout",
inputSchema: { url: "string" },
timeoutMs: 5000, // Tool-specific 5-second timeout
async handler({ url }, { signal }) {
// Pass signal to fetch() or your async operations.
// If the client cancels or timeout expires, fetch aborts immediately!
const res = await fetch(url, { signal });
return await res.text();
}
});
// Programmatic tool calls with custom timeout or AbortController:
const controller = new AbortController();
const res = await app.callTool("fetch_webpage", { url: "https://example.com" }, {
signal: controller.signal,
timeoutMs: 3000,
throwOnError: false // returns { isError: true, ... } instead of throwing
});Sequential Execution & Concurrency Control (Queuing & Mutexes)
When tools interact with stateful or rate-limited resources (e.g. browser automation via Playwright/Puppeteer, hardware devices, database transactions, or single-session APIs), concurrent invocations can cause race conditions or state corruption.
mcponce provides built-in concurrency control and deterministic FIFO queuing:
Server-Level Sequential (
sequential: true): All tool calls across the entire server are queued and executed one-by-one in strict FIFO order. Can also be enabled via environment variableMCP_SEQUENTIAL=true.Server-Level Max Concurrency (
maxConcurrency: N): Limits the number of simultaneous tool executions across the entire server toN(configurable viaMCP_MAX_CONCURRENCY).Tool-Level Sequential (
sequential: true): Invocations of this specific tool run one at a time. Other independent tools continue running concurrently.Tool-Level Max Concurrency (
maxConcurrency: N): Limits simultaneous invocations of this specific tool toN.Named Mutex Groups (
sequential: "group_name"): Multiple distinct tools can share a single mutex queue. For example,browser_click,browser_type, andbrowser_navigatecan all specifysequential: "browser"so they never run concurrently with each other while allowing other tools (like calculations or file reads) to run freely.Safe Re-Entrancy: Inter-tool calls (tools calling other tools via
context.callTool) automatically detect that the parent caller already holds the queue lock, preventing deadlocks.Queue Introspection: Call
app.getQueueStats()to inspect active tasks and waiting queue lengths at runtime.
Server-Level Sequential Mode
const app = createMcpServer({
name: "single-threaded-app",
sequential: true // All tools execute serially one at a time
// or: maxConcurrency: 2
});Tool-Level Sequential & Named Mutexes
// 1. Tool-level serial execution
app.tool({
name: "write_ledger",
description: "Appends to an accounting ledger without race conditions",
sequential: true, // Only one ledger write at a time
async handler({ entry }) {
await appendToLedger(entry);
}
});
// 2. Shared named mutex across multiple browser tools
app.tool({
name: "browser_navigate",
sequential: "browser_session", // Shares queue with browser_click
async handler({ url }) {
await page.goto(url);
}
});
app.tool({
name: "browser_click",
sequential: "browser_session", // Shares queue with browser_navigate
async handler({ selector }) {
await page.click(selector);
}
});
// 3. Rate-limited tool with maxConcurrency
app.tool({
name: "scrape_page",
maxConcurrency: 3, // At most 3 simultaneous scrape jobs
async handler({ url }) {
return await scrape(url);
}
});In-Memory Tool Response Caching
LLM workflows frequently re-query read-only tools with identical arguments across turns (e.g. get_weather, search_docs, fetch_schema, get_stock_quote), incurring redundant latency, API costs, and database load.
mcponce provides built-in, zero-dependency in-memory response caching with automatic LRU eviction and deterministic argument hashing:
Quick Caching (
cache: true): Caches tool responses with a default 60-second TTL and 100-entry LRU limit.Custom TTL & Limits (
cache: { ttlMs: 30_000, maxSize: 250 }): Set custom expiration and capacity per tool.Deterministic Argument Hashing:
{ a: 1, b: 2 }and{ b: 2, a: 1 }hit the exact same cache entry via key-sorted recursive serialization.Cache Invalidation:
Inside mutating tools:
context.clearCache("other_tool").Programmatically:
app.clearCache("tool_name")(orapp.clearCache()to evict all).
Cache Bypass: Call with
{ noCache: true }in code or--no-cachevia CLI.Protocol Transparency: Seamlessly caches both programmatic calls and remote MCP client calls over HTTP / SSE.
Error Non-Caching: Only successful responses are cached. Errors and timeouts are never cached.
// 1. Tool with 30-second response caching
app.tool({
name: "fetch_stock_quote",
description: "Fetches latest stock price with 30s cache",
inputSchema: { symbol: "string" },
cache: { ttlMs: 30_000 },
async handler({ symbol }) {
return await api.getQuote(symbol);
}
});
// 2. Invalidate cache from a mutating tool
app.tool({
name: "update_stock_symbol",
inputSchema: { symbol: "string", price: "number" },
async handler({ symbol, price }, { clearCache }) {
await db.updatePrice(symbol, price);
clearCache("fetch_stock_quote"); // Evicts cached quotes
}
});
// 3. Programmatic cache bypass and cache stats
await app.callTool("fetch_stock_quote", { symbol: "AAPL" }, { noCache: true });
console.log(app.getCacheStats()); // { size: 1, hits: 14, misses: 2, evictions: 0 }Automatic Tool Retries with Exponential Backoff
Tools frequently encounter transient network hiccups, brief database reconnects, or upstream API rate limits (e.g. 503 Service Unavailable, 429 Too Many Requests). Without automated recovery, transient errors fail the entire LLM workflow.
mcponce provides built-in, zero-dependency declarative retries with exponential backoff:
Shorthand (
retry: 3): Automatically retries up to 3 times with exponential backoff (100ms, 200ms, 400ms).Detailed Configuration (
retry: { attempts, backoffMs, factor, maxBackoffMs, retryIf }): Customize initial delay, multiplier factor, maximum backoff cap, and selective error filtering.Selective Retry Predicates (
retryIf): Only retry transient errors (e.g.(err) => err.status === 429 || err.code === 'ECONNRESET') while immediately failing on permanent errors like bad credentials.Cancellation & Timeout Safe: If a client cancels or the tool timeout expires while the server is sleeping between retries, the delay timer is aborted immediately without hanging the process.
Per-Call Overrides: Override or disable retries for individual calls via
app.callTool(name, args, { retry: false })or{ retry: 5 }.Telemetry Integration: Retries are automatically tracked in
analytics.getSnapshot().summary.totalRetriesand per-toolmetric.retries, and logged at warning level (tool:retry).
// 1. Declarative shorthand: retry up to 3 times on failure
app.tool({
name: "fetch_external_weather",
inputSchema: { city: "string" },
retry: 3,
async handler({ city }) {
return await weatherApi.getForecast(city);
}
});
// 2. Fine-grained configuration with error filter
app.tool({
name: "query_upstream_database",
retry: {
attempts: 4,
backoffMs: 200, // First retry after 200ms
factor: 2, // 200ms -> 400ms -> 800ms -> 1600ms
maxBackoffMs: 5000, // Cap backoff at 5 seconds
retryIf: (err) => err.isTransient === true // Don't retry validation or auth errors
},
async handler(args) {
return await db.query(args);
}
});
// 3. Programmatic override (bypass retries)
await app.callTool("fetch_external_weather", { city: "London" }, { retry: false });Smart Input Coercion & Schema Defaults
Smaller, open-source, or cost-efficient LLMs (e.g. Llama 3, Mistral, Gemma, GPT-4o-mini, Claude Haiku) often pass stringified numbers ("42" instead of 42), stringified booleans ("false" instead of boolean false), stringified JSON objects/arrays, or omit optional fields.
mcponce provides zero-dependency Smart Input Coercion & Schema Defaults:
Boolean String Safety: In JavaScript,
Boolean("false")evaluates totrue.mcponce's coercion correctly maps"false","0","no", and"off"to booleanfalse, and"true","1","yes","on"totrue.Numeric Coercion: Automatically coerces numeric strings (
"42","3.14","-10") into JavaScript numbers.JSON Object & Array Parsing: Coerces stringified JSON (
'{"key": "val"}'or'["a", "b"]') into parsed objects and arrays, and wraps single values into[val]when an array is expected.Extended Schema Properties: Define defaults, descriptions, and coercion directly in
inputSchema:inputSchema: { page: { type: "number", default: 1, description: "Page number" }, limit: { type: "number", default: 25, description: "Items per page" }, activeOnly: { type: "boolean", default: true } }Flexible Activation:
Tool-Level:
app.tool({ name: "...", coerceInputs: true, ... }).Server-Level:
createMcpServer({ coerceInputs: true })or via envMCP_COERCE_INPUTS=true.Property-Level:
page: { type: "number", coerce: true }.Per-Call Override:
app.callTool(name, args, { coerceInputs: false })(bypass) or{ coerceInputs: true }(enable).
Clean JSON Schema Advertising: The underlying base type (
"type": "number","type": "boolean") and descriptions/defaults are preserved in JSON Schema advertised to clients overtools/list.
// 1. Tool with automatic input coercion and defaults
app.tool({
name: "search_users",
description: "Searches users with smart type coercion",
coerceInputs: true,
inputSchema: {
page: { type: "number", default: 1, description: "Page number" },
limit: { type: "number", default: 20, description: "Items per page" },
activeOnly: { type: "boolean", default: true },
tags: { type: "array", default: [] }
},
async handler({ page, limit, activeOnly, tags }) {
// Guarantees: page is number (1), limit is number (20), activeOnly is boolean (true), tags is Array
return await db.findUsers({ page, limit, activeOnly, tags });
}
});
// Calling with stringified LLM inputs works seamlessly:
await app.callTool("search_users", {
page: "2",
limit: "50",
activeOnly: "false",
tags: '["admin", "developer"]'
});Real-Time Progress Reporting (reportProgress)
Long-running tools (e.g. file downloads, batch data migrations, AI embeddings, scraping, database indexing) need to communicate ongoing progress to clients before final completion so users and LLMs aren't left waiting blindly.
mcponce provides built-in, zero-dependency Real-Time Progress Reporting:
Official MCP Protocol Compliance: When an MCP client (Claude Desktop, Cursor, Antigravity) requests progress via
_meta: { progressToken: "..." },mcponceautomatically dispatches standardnotifications/progressJSON-RPC messages over the active SSE/HTTP or stdio stream.Unified Dual-Signature API: Both
context.reportProgress(...)andextra.reportProgress(...)support positional and object formats:await context.reportProgress(current, total, message)await context.reportProgress({ progress: current, total, message })
Programmatic Live Callbacks: Pass
onProgresstoapp.callTool(name, args, { onProgress: (p) => ... })to stream live updates in Node.js scripts or unit tests.Inter-Tool Progress Forwarding: When a parent tool calls a sub-tool, it can listen to the sub-tool's progress events via
context.callTool(..., { onProgress })and aggregate or re-broadcast them.Interactive CLI Terminal Display: Running
mcponce call <tool-name>renders live progress indicators and percentages onstderr(e.g.[Progress sync_data] 45/100 (45%) - Syncing user records), keepingstdoutcompletely clean for piped JSON or output data. Supports--no-progressto suppress rendering.Safe & Non-Blocking: Progress reporting never throws or disrupts execution even if a client disconnects or an
onProgresscallback fails.
// 1. Tool reporting progress during execution
app.tool({
name: "batch_process_records",
description: "Processes records in chunks with live progress updates",
inputSchema: { totalRecords: "number" },
async handler({ totalRecords }, context) {
for (let i = 1; i <= totalRecords; i++) {
// Positional syntax: (progress, total, statusMessage)
await context.reportProgress?.(i, totalRecords, `Processing record #${i}`);
// Or object syntax:
// await context.reportProgress?.({ progress: i, total: totalRecords, message: `Record #${i}` });
}
return { processed: totalRecords, success: true };
}
});
// 2. Programmatic invocation with live onProgress listener
const result = await app.callTool("batch_process_records", { totalRecords: 100 }, {
onProgress: (report) => {
console.log(`[Live Progress] ${report.tool}: ${report.progress}/${report.total} - ${report.message}`);
}
});
// 3. Running from CLI with live terminal progress indicator:
// $ mcponce call batch_process_records --totalRecords 50
// [Progress batch_process_records] 50/50 (100%) - Processing record #50Lightweight Middleware & Interceptors (app.use)
mcponce features a zero-dependency, Koa/Hono-style Onion Middleware Subsystem that intercepts tool executions. Use middleware for authentication, authorization guards, global request logging, argument mutation, context enrichment, custom rate-limiting, error fallbacks, and response masking:
Onion Model Execution: Middlewares follow the standard
async (ctx, next) => { ... }signature. Callingawait next()passes control to downstream middleware and the final tool handler, then unwinds back up the stack.Multi-Level Scope:
Global:
app.use(handler)intercepts every tool call on the server.Pattern / Filtered:
app.use('admin_*', handler),app.use(['read', 'write'], handler), orapp.use(/^query_/, handler).Tool-Level:
app.tool({ name: 'x', middleware: [auth, rateLimiter], ... })attaches middleware exclusively to a specific tool.Server Config:
createMcpServer({ middleware: [...] })registers initial middleware at server initialization.
Full Pipeline Control:
Argument Mutation: Mutate or inject parameters in
ctx.argsbefore the tool handler runs.Context Enrichment: Attach session tokens, database clients, or authenticated user info onto
ctx.context.Early Short-Circuiting: Return cached data or mock responses without invoking downstream handlers.
Error Interception & Recovery: Wrap
await next()intry ... catchto transform errors into user-friendly responses or fallback values.Response Transformation: Inspect or redact sensitive properties from the returned tool output.
Universal Coverage: Works identically across remote MCP clients (HTTP/SSE, stdio), programmatic invocations (
app.callTool), inter-tool calls (context.callTool), and CLI calls (mcponce call).
import { createMcpServer } from "mcponce";
const app = createMcpServer({ name: "secure-api-server" });
// 1. Global Timing & Audit Logging Middleware
app.use(async (ctx, next) => {
const start = Date.now();
console.log(`[Audit] Starting execution of tool: ${ctx.tool}`);
const result = await next();
console.log(`[Audit] Completed ${ctx.tool} in ${Date.now() - start}ms`);
return result;
});
// 2. Authentication Guard on Admin Tools (Pattern-based)
app.use("admin_*", async (ctx, next) => {
if (!ctx.args.apiKey || ctx.args.apiKey !== process.env.ADMIN_API_KEY) {
throw new Error("Unauthorized: Valid admin apiKey required");
}
return next();
});
// 3. Context Enrichment & Input Normalization Middleware
app.use(async (ctx, next) => {
// Attach authenticated identity to shared context
ctx.context.user = { id: 42, role: "developer" };
// Normalize string arguments
if (typeof ctx.args.query === "string") {
ctx.args.query = ctx.args.query.trim().toLowerCase();
}
return next();
});
// 4. Tool-Specific Middleware
const confirmGuard = async (ctx, next) => {
if (!ctx.args.confirmed) {
return { status: "aborted", message: "Operation requires confirmation" };
}
return next();
};
app.tool({
name: "admin_purge_database",
middleware: [confirmGuard],
handler: async (args, context) => {
// context.user is accessible here
return { purgedBy: context.user.id };
}
});Tool Analytics & Telemetry
mcponce automatically collects in-memory telemetry with zero performance overhead:
Invocation Counts & Durations: Tracks total, successful, failed, timeout, and cancellation counts, as well as
minDurationMs,maxDurationMs,avgDurationMs, andlastDurationMsfor each tool.Inter-Tool Call Graph: Automatically maps which tools call which other tools (
compound_calc ──(x12)──> calculate).Recent Invocations Feed: Capped circular buffer of recent executions with millisecond durations, statuses, and caller origins.
Multi-Channel Access:
CLI:
node server.js analyticsrenders a formatted terminal dashboard table.HTTP Endpoints:
GET /analyticsreturns full telemetry JSON;GET /infoincludes high-level totals.Programmatic:
app.getAnalytics()andapp.resetAnalytics().MCP Clients: Any connected client can inspect the built-in
system://analyticsresource.
const analytics = app.getAnalytics();
console.log(`Total calls: ${analytics.summary.totalInvocations}`);
console.log(`Average duration: ${analytics.summary.averageExecutionTimeMs}ms`);
console.log(analytics.tools["calculate"]);Prometheus & OpenTelemetry Metrics (GET /metrics)
mcponce exposes standard Prometheus 0.0.4 text exposition format metrics out of the box for Kubernetes, Grafana, Datadog, and Prometheus scrapers:
HTTP Scrape Endpoint:
GET /metrics(Content-Type: text/plain; version=0.0.4; charset=utf-8)Programmatic Access:
app.getMetrics()returns the full Prometheus metric string.Built-in MCP Resource:
system://metricsallows connected AI clients to read live Prometheus counters.
Exposed standard metrics:
mcp_server_info{server="...",version="..."} 1mcp_server_uptime_seconds{server="..."} <seconds>mcp_active_sessions{server="..."} <gauge>mcp_sessions_total{server="..."} <counter>mcp_tool_invocations_total{server="...",status="success|error|timeout|cancelled"} <counter>mcp_tool_calls_total{server="...",tool="...",status="..."} <counter>mcp_tool_duration_seconds_total{server="...",tool="..."} <counter>mcp_tool_duration_seconds_avg{server="...",tool="..."} <gauge>mcp_tool_cache_hits_total{server="...",tool="..."} <counter>mcp_tool_retries_total{server="...",tool="..."} <counter>mcp_inter_tool_calls_total{server="...",caller="...",target="..."} <counter>mcp_cache_entries{server="..."} <gauge>mcp_cache_hits_total{server="..."} <counter>mcp_cache_misses_total{server="..."} <counter>mcp_cache_evictions_total{server="..."} <counter>
// Scrape directly from code or test:
const metricsString = app.getMetrics();
console.log(metricsString);Client Configuration
Add to your MCP client config (e.g. claude_desktop_config.json, cursor.json). You can pass --background (or -b) directly as an argument:
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/absolute/path/to/my-mcp.js", "--background"]
}
}
}Standalone Single-Executable Operation
mcponce is built first and foremost for single-executable standalone operation:
You don't need any daemon or central manager running.
Simply run your file:
node my-mcp.js(or compile it to a standalone binary withbun build --compile,pkg, orsea).Point Claude Desktop, Cursor, or Antigravity directly to the file.
The file automatically starts the background HTTP server on demand, single-instance coordinates, proxies stdio safely, and cleanly terminates when the client disconnects.
Standalone CLI Commands
You can test tools and run diagnostics directly on any individual server file without the central CLI or opening Claude/Cursor:
node my-mcp.js tools # List all registered tools, descriptions, and schemas
node my-mcp.js call <tool> [params...] # Execute a tool directly with CLI flags or JSON
node my-mcp.js call <tool> --help # View detailed parameter schema and usage example
node my-mcp.js info # Inspect running status, PID, port, active sessions
node my-mcp.js analytics # View tool execution metrics and call graph
node my-mcp.js start # Start in background (if background: true configured)
node my-mcp.js stop # Stop the running server instance
node my-mcp.js restart # Restart the server instance
node my-mcp.js logs [count] # View recent server logs
node my-mcp.js --dev # Run with diagnostic logs piped to stderr
node my-mcp.js --help # Show helpDirect Tool Invocation & Testing via CLI
You can execute any tool directly from the terminal without setting up an MCP client:
# 1. Discover registered tools and their parameter types
node my-mcp.js tools
# 2. Inspect a specific tool's parameter schema and usage example
node my-mcp.js call calculate --help
# 3. Call tool using named flags with automatic type casting
node my-mcp.js call calculate --operation add --a 10 --b 25
node my-mcp.js call hello --name Alice
# 4. Call tool with an inline JSON payload (or mix with flag overrides)
node my-mcp.js call calculate '{"operation": "multiply", "a": 6, "b": 7}'
node my-mcp.js call calculate '{"operation": "add", "a": 10}' --b 20
# 5. Output raw JSON for scripts, automation, or piping to jq
node my-mcp.js call calculate --operation add -a 10 -b 25 --json | jq .resultBackground Execution with Unitup (Optional)
mcponce supports running the shared MCP server as a detached background service using Unitup.
You can enable background mode in 3 convenient ways:
Via CLI parameter (
--background/-b) — Zero code changes needed:node server.js --backgroundOr in your MCP client config (e.g.
claude_desktop_config.json):{ "mcpServers": { "my-server": { "command": "node", "args": ["/path/to/server.js", "--background"] } } }Via Server Configuration (
background: true):const app = createMcpServer({ name: "browsertrack", background: true }); app.run();Programmatically (
app.start({ background: true })):const app = createMcpServer("browsertrack"); await app.start({ background: true }); // Starts or reuses background server await app.stop({ background: true }); // Stops background process and cleans runtime state await app.restart({ background: true }); // Restarts background process and waits for /health
Behavior & Architecture
Default (
background: falseor omitted): Uses the standard native on-demand lifecycle.Enabled (via CLI
--background,background: true, orapp.start({ background: true })):The client executable checks for an existing healthy background instance.
If none is running, it acquires the startup lock and uses Unitup to spawn the detached background server.
Waits for
GET /healthverification before connecting as a stdio bridge.When the initial client (e.g. Claude) disconnects, the background server remains alive for other clients (Cursor, Antigravity, VS Code).
Multiple distinct applications (e.g.
browsertrack,softscope,recallite) each run their own independent background singleton.
Source of Truth:
mcponceremains the sole authority for health verification, atomic locking, dynamic port discovery, and runtime metadata. Unitup is strictly an internal process-management detail.Optional Dependency: Unitup is only loaded when background mode is active. If enabled without Unitup installed, a clear error guides the user:
Background mode requires Unitup, but Unitup is not installed. Install it with: npm install unitupLogs: Unitup stdout and stderr logs are saved directly in
app.getLogDirectory()(<dataDir>/logs/unitup.stdout.log).
Central Management CLI (mcponce)
As an optional addition, mcponce comes with a global CLI that tracks all local MCP servers created on your machine in a lightweight JSON registry (~/.mcponce/servers.json or OS equivalent):
# List all registered MCP servers, statuses, PIDs, and ports
npx mcponce list
# Auto-configure server into Claude Desktop or Cursor
npx mcponce install ./server.js
npx mcponce install my-server claude
npx mcponce uninstall my-server claude
# Inspect detailed metrics and active sessions
npx mcponce status
npx mcponce status my-server
# Discover tools available on any running server
npx mcponce tools my-server
# Invoke a tool on a running server directly with CLI flags or JSON
npx mcponce call my-server calculate --operation add -a 10 -b 20
npx mcponce call my-server hello --name Alice
# View real-time tool analytics, execution stats, latencies, and call graph
npx mcponce analytics my-server
npx mcponce analytics my-server --json
# View logs of any server from anywhere
npx mcponce logs my-server 100
# Stop a running server (or all servers)
npx mcponce stop my-server
npx mcponce stop --all
# Clean up stopped servers from registry
npx mcponce cleanOpt-out / Isolated Mode
If you do not want an individual server to register in the central servers.json, you can disable it either in code or via environment variable:
const app = createMcpServer({
name: "isolated-mcp",
registerInCentral: false // Operates 100% standalone and isolated
});Or run with:
MCP_DISABLE_REGISTRY=1 node my-mcp.jsApplication Data and Logs
Runtime metadata (runtime.json, instance.lock) and logs are saved per-server in:
macOS:
~/Library/Application Support/<name>/logs/Linux:
$XDG_STATE_HOME/<name>/logs/or~/.local/state/<name>/logs/Windows:
%LOCALAPPDATA%\<name>\logs\
The central registry is stored at:
macOS/Linux:
~/.mcponce/servers.json(or$XDG_STATE_HOME/mcponce/servers.json)Windows:
%LOCALAPPDATA%\mcponce\servers.json
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
- typeshipOAuthdev.typeship
Generate a typed SDK, CLI, and MCP server from any OpenAPI or GraphQL spec, and keep them current.
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
Guarded MCP server for agent-readable business truth, provenance, readiness, and discovery.
Build multi-tenant apps over MCP. Schemas, CRUD, deploys — access control enforced server-side.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProduction-ready MCP server starter with authentication, observability, and a plugin system for building and deploying MCP servers quickly.MIT
- AlicenseNot gradedqualityBmaintenanceA production-ready, transport-independent foundation for building enterprise MCP servers in Python. Provides infrastructure, configuration, DI, logging, and tooling so downstream projects can focus on domain capabilities.MIT
- AlicenseNot gradedqualityBmaintenanceProvides a reusable MCP server foundation with explicit tool registration, operation-mode separation, scope-based permissions, structured results, and built-in health/status tools.MIT
- FlicenseNot gradedqualityBmaintenanceEnables developers to build MCP servers with registry-managed tool metadata, runtime hot-reloading, pluggable authentication and authorization, per-audience tool views, and resilient stateless operation.-