apcore-mcp
v0.14.0
Published
Automatic MCP Server & OpenAI Tools Bridge for apcore
Maintainers
Readme
apcore-mcp
Automatic MCP Server & OpenAI Tools Bridge for apcore.
Converts apcore module registries into Model Context Protocol (MCP) tool definitions and OpenAI-compatible function calling formats — zero boilerplate required.
Features
- MCP Server — Expose apcore modules as MCP tools over stdio, Streamable HTTP, or SSE
- OpenAI Tools — Convert modules to OpenAI function calling format with strict mode support
- Schema Conversion — Inline
$defs/$reffrom Pydantic-generated JSON Schema - Annotation Mapping — Map module annotations to MCP hints and OpenAI description suffixes
- Approval Mechanism — Built-in elicitation-based approval flow for sensitive tool executions
- Error Mapping — Sanitize internal errors for safe client-facing responses
- Dynamic Registration — Listen for registry changes and update tools at runtime
- Tool Explorer — Browser-based UI for browsing schemas and testing tools interactively
- CLI — Launch an MCP server from the command line
- Config Bus integration — Registers an
mcpnamespace with the apcore Config Bus; configure via unifiedapcore.yamlorAPCORE_MCP_*env vars - Error Formatter Registry — Registers an MCP-specific error formatter for ecosystem-wide consistent error handling
Documentation
For full documentation, including Quick Start guides for both Python and TypeScript, visit: https://aiperceivable.github.io/apcore-mcp/
Requirements
- Node.js >= 18.0.0
apcore-js >= 0.19.0
Installation
npm install apcore-mcpapcore-js is included as a direct dependency — no separate install needed.
Quick Start
Programmatic API
import { serve, toOpenaiTools } from "apcore-mcp";
// Launch MCP server over stdio
await serve(executor);
// Launch over Streamable HTTP
await serve(executor, {
transport: "streamable-http",
host: "127.0.0.1",
port: 8000,
});
// Export OpenAI tool definitions
const tools = toOpenaiTools(registry, {
embedAnnotations: true,
strict: true,
});CLI
# stdio (default)
npx apcore-mcp --extensions-dir ./extensions
# Streamable HTTP
npx apcore-mcp --extensions-dir ./extensions --transport streamable-http --port 8000
# SSE
npx apcore-mcp --extensions-dir ./extensions --transport sse --port 8000CLI Arguments
| Argument | Default | Description |
|---|---|---|
| --extensions-dir | (required) | Path to apcore extensions directory |
| --transport | stdio | stdio, streamable-http, or sse |
| --host | 127.0.0.1 | Host for HTTP transports |
| --port | 8000 | Port for HTTP transports (1-65535) |
| --name | apcore-mcp | MCP server name |
| --version | package version | MCP server version |
| --log-level | INFO | DEBUG, INFO, WARNING, ERROR |
| --explorer | off | Enable the browser-based Tool Explorer UI (HTTP only) |
| --explorer-prefix | /explorer | URL prefix for the explorer UI |
| --allow-execute | off | Allow tool execution from the explorer UI |
| --jwt-secret | — | JWT secret key for Bearer token authentication |
| --jwt-key-file | — | Path to PEM key file for JWT verification (RS256/ES256) |
| --jwt-algorithm | HS256 | JWT algorithm |
| --jwt-audience | — | Expected JWT audience claim |
| --jwt-issuer | — | Expected JWT issuer claim |
| --jwt-require-auth | true | Require auth (use --jwt-permissive to override and allow unauthenticated requests) |
| --jwt-permissive | false | Permissive mode: allow unauthenticated requests (overrides --jwt-require-auth) |
| --exempt-paths | /health,/metrics,/usage | Comma-separated paths exempt from auth |
JWT key resolution priority: --jwt-key-file > --jwt-secret > APCORE_JWT_SECRET environment variable.
MCP Client Configuration
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"apcore": {
"command": "npx",
"args": ["apcore-mcp", "--extensions-dir", "/path/to/your/extensions"]
}
}
}Claude Code
Add to .mcp.json in your project root:
{
"mcpServers": {
"apcore": {
"command": "npx",
"args": ["apcore-mcp", "--extensions-dir", "./extensions"]
}
}
}Cursor
Add to .cursor/mcp.json in your project root:
{
"mcpServers": {
"apcore": {
"command": "npx",
"args": ["apcore-mcp", "--extensions-dir", "./extensions"]
}
}
}Remote HTTP access
npx apcore-mcp --extensions-dir ./extensions \
--transport streamable-http \
--host 0.0.0.0 \
--port 9000Connect any MCP client to http://your-host:9000/mcp.
API Reference
Programmatic API – APCoreMCP class
The APCoreMCP class is the recommended OOP entry point. It bundles a unified configuration object, lazy backend resolution (path / Registry / Executor), and exposes serve / asyncServe / toOpenaiTools as instance methods so you configure once and use everywhere.
import { APCoreMCP } from "apcore-mcp";
// 1. Point at an extensions directory (lazy discovery on first use)
const mcp = new APCoreMCP("./extensions", {
name: "my-server",
tags: ["public"],
observability: true,
});
// 2. Launch as MCP server (blocks until shutdown)
await mcp.serve({ transport: "streamable-http", port: 8000, explorer: true });
// 3. Or export OpenAI tool definitions
const tools = mcp.toOpenaiTools({ strict: true });
// 4. Or embed into an existing HTTP server
const app = await mcp.asyncServe({ explorer: true });
// app.handler is a Node.js request handler; call app.close() on shutdown
// 5. Or pass an existing Registry / Executor
import { Registry } from "apcore-js";
const registry = new Registry({ extensionsDir: "./extensions" });
await registry.discover();
const mcp2 = new APCoreMCP(registry, { name: "my-server", tags: ["public"] });Constructor
new APCoreMCP(
extensionsDirOrBackend: string | Registry | Executor,
options?: APCoreMCPOptions,
);The first argument is either a path to an apcore extensions directory (discovery is deferred to first use) or an existing Registry / Executor instance.
APCoreMCPOptions fields
name— MCP server name. Default:"apcore-mcp"version— MCP server version. Default: package versiontags— Filter modules by tag listprefix— Filter modules by ID prefixlogLevel— Minimum log level (DEBUG|INFO|WARNING|ERROR|CRITICAL)validateInputs— Validate inputs against schemas. Default:falsemetricsCollector—MetricsExporterortrueto auto-instantiateobservability— Enable the full metrics + usage observability stackasync—boolean | { enabled?, maxConcurrent?, maxTasks? }for the Async Task Bridge (F-043)authenticator— OptionalAuthenticator(HTTP transports only)requireAuth— Iftrue(default), reject unauthenticated requests with 401exemptPaths— Paths exempt from authenticationapprovalHandler— Optional approval handler passed to the ExecutoroutputFormatter— Custom function to format tool execution resultsmiddleware— Array of apcoreMiddlewareinstalled viaexecutor.use()acl— Optional apcoreACLinstance installed viaexecutor.setAcl()
Properties
.registry— The underlying apcoreRegistry(resolved on first access).executor— The underlying apcoreExecutor(populated afterserve()/asyncServe()).tools— List of discovered module IDs that will be exposed as tools (honourstags/prefix)
Methods
.serve(options?)— Launch an MCP server. AcceptsAPCoreMCPServeOptions:transport,host,port,onStartup,onShutdown,explorer,explorerPrefix,allowExecute,explorerTitle,explorerProjectName,explorerProjectUrl. Constructor-level options (auth, observability, middleware, acl, async, etc.) are applied automatically..asyncServe(options?)— Build an embeddable Node.js HTTP request handler. AcceptsAPCoreMCPAsyncServeOptions:explorer,explorerPrefix,allowExecute,explorerTitle,explorerProjectName,explorerProjectUrl,endpoint. Returns{ handler, close }..toOpenaiTools(options?)— Export modules as OpenAI-compatible tool definitions. AcceptsToOpenaiToolsOptions:embedAnnotations,strict.tags/prefixare inherited from the constructor.
serve(registryOrExecutor, options?)
Launch an MCP Server that exposes all apcore modules as tools.
function serve(
registryOrExecutor: Registry | Executor,
options?: {
// Transport
transport?: "stdio" | "streamable-http" | "sse";
host?: string;
port?: number;
// Identity
name?: string;
version?: string;
// Lifecycle
onStartup?: () => void | Promise<void>;
onShutdown?: () => void | Promise<void>;
// Module filtering / discovery
tags?: string[] | null;
prefix?: string | null;
dynamic?: boolean;
validateInputs?: boolean;
logLevel?: "DEBUG" | "INFO" | "WARNING" | "ERROR" | "CRITICAL";
// Async Task Bridge (F-043)
async?: boolean | { enabled?: boolean; maxConcurrent?: number; maxTasks?: number };
// Executor wiring
middleware?: unknown[];
acl?: unknown;
approvalHandler?: unknown;
strategy?: string;
// Observability (F-044)
metricsCollector?: MetricsExporter | boolean;
observability?: ObservabilityFlag;
trace?: boolean;
// Output handling
outputFormatter?: (result: Record<string, unknown>) => string;
redactOutput?: boolean;
// Auth (HTTP transports only)
authenticator?: Authenticator;
requireAuth?: boolean;
exemptPaths?: string[];
// Tool Explorer UI
explorer?: boolean;
explorerPrefix?: string;
allowExecute?: boolean;
explorerTitle?: string;
explorerProjectName?: string;
explorerProjectUrl?: string;
// Adapter overrides (advanced — Extension Bridge)
schemaConverter?: SchemaConverter;
annotationMapper?: AnnotationMapper;
errorMapper?: ErrorMapper;
}
): Promise<void>;Options reference:
Transport
transport—"stdio"(default),"streamable-http", or"sse"host— Host address for HTTP-based transports. Default:"127.0.0.1"port— Port for HTTP-based transports. Default:8000
Identity
name— MCP server name. Default:"apcore-mcp"version— MCP server version. Default: package version
Lifecycle
onStartup— Async callback invoked before the server startsonShutdown— Async callback invoked after the server stops (or on error)
Module filtering / discovery
tags— Filter modules by tag list. Default:null(no filtering)prefix— Filter modules by ID prefix. Default:null(no filtering)dynamic— Enable dynamic tool registration viaRegistryListener. Default:falsevalidateInputs— Validate inputs against schemas before dispatch. Default:falselogLevel— Minimum log level. Suppresses console methods below this level
Async Task Bridge (F-043)
async— Enable the AsyncTaskBridge and__apcore_task_*meta-tools. Passfalseto disable, or{ maxConcurrent, maxTasks }for fine-grained tuning. Default:true
Executor wiring
middleware— Array of apcoreMiddlewareinstances installed viaexecutor.use(). Appended to any middleware declared under Config Bus keymcp.middlewareacl— Optional apcoreACLinstance installed viaexecutor.setAcl(). Caller-supplied ACL takes precedence overmcp.aclConfig Bus entryapprovalHandler— Optional approval handler passed to the Executor (e.g.ElicitationApprovalHandler)strategy— Execution strategy name passed to the Executor (e.g."standard","internal")
Observability (F-044)
metricsCollector—MetricsExporterinstance, ortrueto auto-instantiate apcore-js'sMetricsCollectorand installMetricsMiddlewareobservability— Enable the full observability stack (metrics + usage middleware) and expose/metrics+/usageendpointstrace— Whentrue, enables pipeline trace viacallWithTrace(). Adds_meta.traceto non-streaming tool responses. Default:false
Output handling
outputFormatter— Custom function to format tool execution results. When undefined, results are serialized withJSON.stringify(result)redactOutput— Whentrue(default), redact sensitive fields from tool output via apcore'sredactSensitive()before formatting
Auth (HTTP transports only)
authenticator—Authenticatorinstance for request authenticationrequireAuth— Iftrue(default), unauthenticated requests are rejected with 401. Set tofalsefor permissive modeexemptPaths— Paths exempt from authentication. Default:["/health", "/metrics"]
Tool Explorer UI
explorer— Enable the browser-based Tool Explorer UI (HTTP only). Default:falseexplorerPrefix— URL prefix for the explorer. Default:"/explorer"allowExecute— Allow tool execution from the explorer UI. Default:falseexplorerTitle— Custom title for the Tool Explorer UI pageexplorerProjectName— Project name shown in the explorer UI footerexplorerProjectUrl— Project URL shown in the explorer UI footer
Adapter overrides (advanced — Extension Bridge, F-042)
schemaConverter— Override the defaultSchemaConverter(custom JSON Schema strictness/dialect)annotationMapper— Override the defaultAnnotationMapper(custom annotation wire format)errorMapper— Override the defaultErrorMapperconsumed byExecutionRouter
asyncServe(registryOrExecutor, options?)
Embed the MCP server into a larger Node.js HTTP application. Returns an HTTP request handler and a close function for lifecycle management.
import { asyncServe } from "apcore-mcp";
const { handler, close } = await asyncServe(executor, {
name: "apcore-mcp",
explorer: true,
allowExecute: true,
});
// Mount in a custom HTTP server
const server = http.createServer(handler);
server.listen(8000);
// Clean up when done
await close();Accepts the same options as serve() except transport, host, port, onStartup, and onShutdown.
Tool Explorer
When explorer: true is passed to serve(), a browser-based Tool Explorer UI is mounted on HTTP transports. It provides an interactive page for browsing tool schemas and testing tool execution.
await serve(registry, {
transport: "streamable-http",
explorer: true,
allowExecute: true,
});
// Open http://127.0.0.1:8000/explorer/ in a browserEndpoints:
| Endpoint | Description |
|----------|-------------|
| GET /explorer/ | Interactive HTML page (self-contained, no external dependencies) |
| GET /explorer/tools | JSON array of all tools with name, description, annotations |
| GET /explorer/tools/<name> | Full tool detail with inputSchema |
| POST /explorer/tools/<name>/call | Execute a tool (requires allowExecute: true) |
- HTTP transports only (
streamable-http,sse). Silently ignored forstdio. - Execution disabled by default — set
allowExecute: trueto enable Try-it. - Custom prefix — use
explorerPrefix: "/browse"to mount at a different path. - Authorization UI — Swagger-UI-style Authorization input field. Paste a Bearer token to authenticate tool execution requests. Generated cURL commands automatically include the Authorization header.
JWT Authentication
apcore-mcp supports JWT Bearer token authentication for HTTP-based transports.
Programmatic Usage
import { serve, JWTAuthenticator } from "apcore-mcp";
const authenticator = new JWTAuthenticator({
key: "your-secret-key",
algorithms: ["HS256"],
audience: "my-app",
issuer: "auth-service",
// Map custom claims to Identity fields
claimMapping: {
id: "sub",
type: "type",
roles: "roles",
attrs: ["email", "org"], // Extra claims → Identity.attrs
},
// Claims that must be present in the token (default: ["sub"])
requireClaims: ["sub", "email"],
// Set to false for permissive mode (allow unauthenticated requests)
requireAuth: true,
});
await serve(executor, {
transport: "streamable-http",
authenticator,
// Custom exempt paths (default: ["/health", "/metrics"])
exemptPaths: ["/health", "/metrics", "/status"],
});CLI Flags
| Flag | Default | Description |
|------|---------|-------------|
| --jwt-secret | — | JWT secret key for Bearer token authentication |
| --jwt-key-file | — | Path to PEM key file for JWT verification |
| --jwt-algorithm | HS256 | JWT algorithm |
| --jwt-audience | — | Expected audience claim |
| --jwt-issuer | — | Expected issuer claim |
| --jwt-require-auth | true | Require auth. Use --jwt-permissive to allow unauthenticated requests |
| --jwt-permissive | false | Overrides --jwt-require-auth and allows unauthenticated requests |
| --exempt-paths | /health,/metrics,/usage | Comma-separated paths exempt from auth |
JWT key resolution priority: --jwt-key-file > --jwt-secret > APCORE_JWT_SECRET environment variable.
curl Examples
# Authenticated request
curl -X POST http://localhost:8000/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-jwt-token>" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
# Health check (always exempt)
curl http://localhost:8000/healthtoOpenaiTools(registryOrExecutor, options?)
Export apcore modules as OpenAI-compatible tool definitions.
function toOpenaiTools(
registryOrExecutor: Registry | Executor,
options?: {
embedAnnotations?: boolean;
strict?: boolean;
tags?: string[];
prefix?: string;
}
): OpenAIToolDef[];Options:
embedAnnotations— Append annotation metadata to tool descriptions (default:false)strict— Enable OpenAI strict mode: addsadditionalProperties: false, makes all properties required, wraps optional properties with nullable (default:false)tags— Filter modules by tagsprefix— Filter modules by ID prefix
reportProgress(context, progress, total?, message?)
Report execution progress to the MCP client. No-ops silently when called outside an MCP context (no callback injected).
import { reportProgress } from "apcore-mcp";
// Inside a module's execute() method:
await reportProgress(context, 5, 10, "Processing item 5 of 10");Parameters:
context— Object with adatadict (apcore Context or BridgeContext)progress— Current progress valuetotal— Optional total for percentage calculationmessage— Optional human-readable progress message
elicit(context, message, requestedSchema?)
Ask the MCP client for user input via the elicitation protocol. Returns null when called outside an MCP context.
import { elicit } from "apcore-mcp";
import type { ElicitResult } from "apcore-mcp";
// Inside a module's execute() method:
const result: ElicitResult | null = await elicit(
context,
"Are you sure you want to proceed?",
{
type: "object",
properties: {
confirmed: { type: "boolean", description: "Confirm action" },
},
required: ["confirmed"],
},
);
if (result?.action === "accept") {
// User confirmed
}Parameters:
context— Object with adatadict (apcore Context or BridgeContext)message— Message to display to the userrequestedSchema— Optional JSON Schema describing the expected input
Returns: ElicitResult with action ("accept", "decline", or "cancel") and optional content, or null if not in an MCP context.
Config Bus Integration
apcore-mcp registers an mcp namespace with the apcore Config Bus when serve() or asyncServe() is called. MCP settings can live alongside other apcore configuration in a single apcore.yaml:
apcore:
version: "1.0.0"
mcp:
transport: streamable-http
host: 0.0.0.0
port: 9000
explorer: true
require_auth: falseEnvironment variable overrides use the APCORE_MCP_ prefix:
APCORE_MCP_TRANSPORT=streamable-http
APCORE_MCP_PORT=9000
APCORE_MCP_EXPLORER=trueDefaults: transport=stdio, host=127.0.0.1, port=8000, explorer=false, require_auth=true.
The namespace, prefix, and defaults are also available as importable constants:
import { MCP_NAMESPACE, MCP_ENV_PREFIX, MCP_DEFAULTS, registerMcpNamespace } from "apcore-mcp";Development
# Install dependencies
npm install
# Type check
npm run typecheck
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build
npm run build
# Watch mode
npm run devLicense
Apache-2.0
