apcore-mcp
Automatically converts any apcore-based project into an MCP server and OpenAI tool provider with zero code changes.
README
<div align="center"> <img src="https://raw.githubusercontent.com/aiperceivable/apcore-mcp/main/apcore-mcp-logo.svg" alt="apcore-mcp logo" width="200"/> </div>
apcore-mcp
Automatic MCP Server & OpenAI Tools Bridge for apcore.
apcore-mcp turns any apcore-based project into an MCP Server and OpenAI tool provider — with zero code changes to your existing project.
┌──────────────────┐
│ django-apcore │ ← your existing apcore project (unchanged)
│ flask-apcore │
│ ... │
└────────┬─────────┘
│ extensions directory
▼
┌──────────────────┐
│ apcore-mcp │ ← just install & point to extensions dir
└───┬──────────┬───┘
│ │
▼ ▼
MCP OpenAI
Server Tools
Design Philosophy
- Zero intrusion — your apcore project needs no code changes, no imports, no dependencies on apcore-mcp
- Zero configuration — point to an extensions directory, everything is auto-discovered
- Pure adapter — apcore-mcp reads from the apcore Registry; it never modifies your modules
- Works with any
xxx-apcoreproject — if it uses the apcore Module Registry, apcore-mcp can serve it
Documentation
For full documentation, including Quick Start guides for both Python and TypeScript, visit: https://aiperceivable.github.io/apcore-mcp/
Installation
Install apcore-mcp alongside your existing apcore project:
pip install apcore-mcp
That's it. Your existing project requires no changes.
Quick Start
Try it now
The repo includes 5 example modules (class-based + binding.yaml) you can run immediately:
pip install -e .
PYTHONPATH=./examples/binding_demo python examples/run.py
# Open http://127.0.0.1:8000/explorer/
See examples/README.md for all run modes and module details.
Zero-code approach (CLI)
If you already have an apcore-based project with an extensions directory, just run:
apcore-mcp --extensions-dir /path/to/your/extensions
All modules are auto-discovered and exposed as MCP tools. No code needed.
Programmatic approach (Python API)
The APCoreMCP class is the recommended entry point — one object, all capabilities:
from apcore_mcp import APCoreMCP
mcp = APCoreMCP("./extensions")
# Launch as MCP Server
mcp.serve()
# Or with HTTP + Explorer UI
mcp.serve(transport="streamable-http", port=8000, explorer=True)
# Or export as OpenAI tools
tools = mcp.to_openai_tools()
You can also pass an existing Registry or Executor:
from apcore import Registry
from apcore_mcp import APCoreMCP
registry = Registry(extensions_dir="./extensions")
registry.discover()
mcp = APCoreMCP(registry, name="my-server", tags=["public"])
<details> <summary>Function-based API (still supported)</summary>
from apcore import Registry
from apcore_mcp import serve, to_openai_tools
registry = Registry(extensions_dir="./extensions")
registry.discover()
serve(registry)
tools = to_openai_tools(registry)
</details>
Integration with Existing Projects
Typical apcore project structure
your-project/
├── extensions/ ← modules live here
│ ├── image_resize/
│ ├── text_translate/
│ └── ...
├── your_app.py ← your existing code (untouched)
└── ...
Adding MCP support
No changes to your project. Just run apcore-mcp alongside it:
# Install (one time)
pip install apcore-mcp
# Run
apcore-mcp --extensions-dir ./extensions
Your existing application continues to work exactly as before. apcore-mcp operates as a separate process that reads from the same extensions directory.
Adding OpenAI tools support
For OpenAI integration, a thin script is needed — but still no changes to your existing modules:
from apcore import Registry
from apcore_mcp import to_openai_tools
registry = Registry(extensions_dir="./extensions")
registry.discover()
tools = to_openai_tools(registry)
# Use with openai.chat.completions.create(tools=tools)
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": "apcore-mcp",
"args": ["--extensions-dir", "/path/to/your/extensions"]
}
}
}
Claude Code
Add to .mcp.json in your project root:
{
"mcpServers": {
"apcore": {
"command": "apcore-mcp",
"args": ["--extensions-dir", "./extensions"]
}
}
}
Cursor
Add to .cursor/mcp.json in your project root:
{
"mcpServers": {
"apcore": {
"command": "apcore-mcp",
"args": ["--extensions-dir", "./extensions"]
}
}
}
Remote HTTP access
apcore-mcp --extensions-dir ./extensions \
--transport streamable-http \
--host 0.0.0.0 \
--port 9000
Connect any MCP client to http://your-host:9000/mcp.
CLI Reference
apcore-mcp --extensions-dir PATH [OPTIONS]
| Option | Default | Description |
|---|---|---|
--extensions-dir |
(required) | Path to apcore extensions directory |
--transport |
stdio |
Transport: stdio, streamable-http, or sse |
--host |
127.0.0.1 |
Host for HTTP-based transports |
--port |
8000 |
Port for HTTP-based transports (1-65535) |
--name |
apcore-mcp |
MCP server name (max 255 chars) |
--version |
package version | MCP server version string |
--log-level |
INFO |
Logging: 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 auth (HTTP only) |
--jwt-key-file |
— | Path to PEM key file for JWT verification (e.g. RS256 public key) |
--jwt-algorithm |
HS256 |
JWT signing algorithm |
--jwt-audience |
— | Expected JWT audience claim |
--jwt-issuer |
— | Expected JWT issuer claim |
--jwt-require-auth |
on | Require valid token; use --no-jwt-require-auth for permissive mode |
--exempt-paths |
— | Comma-separated paths exempt from auth (e.g. /health,/metrics) |
--approval |
off |
Approval handler: elicit, auto-approve, always-deny, or off |
--output-format |
json |
Built-in output format: json, csv, or jsonl |
JWT key resolution priority: --jwt-key-file > --jwt-secret > APCORE_JWT_SECRET environment variable.
Exit codes: 0 normal, 1 invalid arguments, 2 startup failure.
Python API Reference
APCoreMCP (recommended)
The unified entry point — configure once, use everywhere:
from apcore_mcp import APCoreMCP
mcp = APCoreMCP(
"./extensions", # path, Registry, or Executor
name="apcore-mcp", # server name
version=None, # defaults to package version
tags=None, # filter modules by tags
prefix=None, # filter modules by ID prefix
log_level=None, # logging level ("DEBUG", "INFO", etc.)
validate_inputs=False, # validate inputs against schemas
metrics_collector=None, # MetricsExporter | bool — `True` auto-instantiates the collector
observability=False, # enable MetricsMiddleware + UsageMiddleware + /metrics + /api/usage
authenticator=None, # Authenticator for JWT/token auth (HTTP only)
require_auth=True, # False = permissive mode (no 401)
exempt_paths=None, # exact paths that bypass auth
approval_handler=None, # approval handler for runtime approval
output_formatter=None, # default: None (raw JSON); pass to_markdown to opt into apcore-toolkit Markdown
middleware=None, # list[Middleware] — user middleware applied after built-ins
acl=None, # apcore.ACL — module access control
async_tasks=True, # enable F-043 Async Task Bridge
async_max_concurrent=10, # max concurrent async tasks
async_max_tasks=1000, # max queued async tasks
)
# Note: redact_output, strategy, and trace are configurable on the
# function-based serve() / async_serve(); they are not exposed on
# APCoreMCP.serve() (see the `serve()` reference below).
# Launch as MCP server (blocking)
mcp.serve(transport="streamable-http", port=8000, explorer=True)
# Export as OpenAI tools
tools = mcp.to_openai_tools(strict=True)
# Embed into ASGI app
async with mcp.async_serve(explorer=True) as app:
...
# Inspect
mcp.tools # list of module IDs
mcp.registry # underlying Registry
mcp.executor # underlying Executor
serve() (function-based)
from apcore_mcp import serve
serve(
registry_or_executor, # Registry or Executor
transport="stdio", # "stdio" | "streamable-http" | "sse"
host="127.0.0.1", # host for HTTP transports
port=8000, # port for HTTP transports
name="apcore-mcp", # server name
version=None, # defaults to package version
on_startup=None, # callback before transport starts
on_shutdown=None, # callback after transport completes
tags=None, # filter modules by tags
prefix=None, # filter modules by ID prefix
log_level=None, # logging level ("DEBUG", "INFO", etc.)
dynamic=False, # rebuild tools on registry events
validate_inputs=False, # validate inputs against schemas
metrics_collector=None, # MetricsExporter | bool — `True` auto-instantiates apcore.observability.MetricsCollector
explorer=False, # enable browser-based Tool Explorer UI
explorer_prefix="/explorer", # URL prefix for the explorer
allow_execute=False, # allow tool execution from the explorer
explorer_title="MCP Tool Explorer",
explorer_project_name=None,
explorer_project_url=None,
authenticator=None, # Authenticator for JWT/token auth (HTTP only)
require_auth=True, # False = permissive mode (no 401)
exempt_paths=None, # exact paths that bypass auth
approval_handler=None, # approval handler for runtime approval
output_formatter=None, # default None (raw JSON); pass apcore_toolkit.to_markdown to opt in
strategy=None, # pipeline strategy preset: "standard" | "internal" | "testing" | "performance" | "minimal"
redact_output=True, # mask x-sensitive / _secret_* fields in outputs
trace=False, # enable per-call apcore pipeline trace metadata
middleware=None, # list[Middleware] — applied after built-ins
acl=None, # apcore.ACL — module access control
observability=False, # enable MetricsMiddleware + UsageMiddleware + /metrics + /api/usage
async_tasks=True, # enable F-043 Async Task Bridge
async_max_concurrent=10, # max concurrent async tasks
async_max_tasks=1000, # max queued async tasks
# Note: schema_converter / annotation_mapper / error_mapper hooks are reserved for v0.16+ (EB-2)
)
Accepts either a Registry or Executor. When a Registry is passed, an Executor is created automatically.
async_serve()
Embed the MCP server into a larger ASGI application (e.g. co-host with A2A, Django ASGI):
from apcore_mcp import async_serve
async with async_serve(registry, explorer=True) as mcp_app:
combined = Starlette(routes=[
Mount("/mcp", app=mcp_app),
Mount("/a2a", app=a2a_app),
])
config = uvicorn.Config(combined, host="0.0.0.0", port=8000)
await uvicorn.Server(config).serve()
Accepts the same parameters as serve() (except transport, host, port, on_startup, on_shutdown). Returns a Starlette app via async context manager.
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.
serve(registry, transport="streamable-http", explorer=True, allow_execute=True)
# Open http://127.0.0.1:8000/explorer/ in a browser
Endpoints:
| 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 allow_execute=True) |
- HTTP transports only (
streamable-http,sse). Silently ignored forstdio. - Execution disabled by default — set
allow_execute=Trueto enable Try-it. - Custom prefix — use
explorer_prefix="/browse"to mount at a different path.
JWT Authentication
Optional Bearer token authentication for HTTP transports. Supports symmetric (HS256) and asymmetric (RS256) algorithms.
from apcore_mcp.auth import JWTAuthenticator
auth = JWTAuthenticator(key="my-secret")
serve(
registry,
transport="streamable-http",
authenticator=auth,
explorer=True,
allow_execute=True,
)
Permissive mode — allow unauthenticated access (identity is None when no token is provided):
serve(registry, transport="streamable-http", authenticator=auth, require_auth=False)
Path exemption — bypass auth for specific paths:
serve(registry, transport="streamable-http", authenticator=auth, exempt_paths={"/health", "/metrics"})
See examples/README.md for a runnable JWT demo with a pre-generated test token.
Approval Mechanism
Optional runtime approval for tool execution. Bridges MCP elicitation to apcore's approval system.
from apcore_mcp.adapters.approval import ElicitationApprovalHandler
handler = ElicitationApprovalHandler()
serve(
registry,
transport="streamable-http",
approval_handler=handler,
explorer=True,
)
Built-in handlers:
| Handler | Description |
|---|---|
ElicitationApprovalHandler |
Prompts the MCP client for user confirmation via elicitation |
AutoApproveHandler |
Auto-approves all requests (dev/testing only) |
AlwaysDenyHandler |
Rejects all requests (enforcement) |
CLI usage:
apcore-mcp --extensions-dir ./extensions --approval elicit
Output Formatting
By default, tool execution results are serialized as JSON (json.dumps). You can customize this by passing an output_format name or a custom output_formatter callable.
Built-in formats (requires apcore-toolkit 0.7+):
# Via CLI
# apcore-mcp --extensions-dir ./extensions --output-format csv
# Via API
mcp = APCoreMCP("./extensions", output_format="csv")
Supports json, csv, and jsonl. Non-tabular data gracefully falls back to JSON.
Custom formatter:
Pass a callable that converts a dict or list result into a string.
def my_formatter(data: dict) -> str:
return "\n".join(f"{k}: {v}" for k, v in data.items())
mcp = APCoreMCP("./extensions", output_formatter=my_formatter)
The output_formatter parameter is also available on the function-based serve() API and on ExecutionRouter directly.
Extension Helpers
Modules can report progress and request user input during execution via MCP protocol callbacks. Both helpers no-op gracefully when called outside an MCP context.
from apcore_mcp import report_progress, elicit
# Inside a module's execute():
await report_progress(context, progress=50, total=100, message="Halfway done")
result = await elicit(context, "Confirm deletion?", {"type": "object", "properties": {"confirm": {"type": "boolean"}}})
if result and result["action"] == "accept":
# proceed
...
/metrics Prometheus Endpoint
When metrics_collector is provided to serve(), a /metrics HTTP endpoint is exposed that returns metrics in Prometheus text exposition format.
- Available on HTTP-based transports only (
streamable-http,sse). Not available withstdiotransport. - Returns Prometheus text format with Content-Type
text/plain; version=0.0.4; charset=utf-8. - Returns 404 when no
metrics_collectoris configured.
from apcore.observability import MetricsCollector
from apcore_mcp import serve
collector = MetricsCollector()
serve(registry, transport="streamable-http", metrics_collector=collector)
# GET http://127.0.0.1:8000/metrics -> Prometheus text format
to_openai_tools()
from apcore_mcp import to_openai_tools
tools = to_openai_tools(
registry_or_executor, # Registry or Executor
embed_annotations=False, # append annotation hints to descriptions
strict=False, # OpenAI Structured Outputs strict mode
tags=None, # filter by tags, e.g. ["image"]
prefix=None, # filter by module ID prefix, e.g. "image"
)
Returns a list of dicts directly usable with the OpenAI API:
import openai
client = openai.OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Resize the image to 512x512"}],
tools=tools,
)
Strict mode (strict=True): sets additionalProperties: false, makes all properties required (optional ones become nullable), removes defaults.
Annotation embedding (embed_annotations=True): appends [Annotations: read_only, idempotent] to descriptions.
Filtering: tags=["image"] or prefix="text" to expose a subset of modules.
Using with an Executor
If you need custom middleware, ACL, or execution configuration:
from apcore import Registry, Executor
registry = Registry(extensions_dir="./extensions")
registry.discover()
executor = Executor(registry)
serve(executor)
tools = to_openai_tools(executor)
Features
- Auto-discovery — all modules in the extensions directory are found and exposed automatically
- Display overlay —
metadata["display"]["mcp"]controls MCP tool names, descriptions, and guidance per module (§5.13); set viabinding_pathinfastapi-apcore - Markdown tool descriptions (
rich_description=True, v0.15+) — renderTool.description/ OpenAIfunction.descriptionas canonical apcore-toolkit Markdown (parameters, returns, behavior table, tags, examples) so LLMs get more decision-relevant signal per token. - Module preview meta-tool (
__apcore_module_preview, v0.15+) — lets AI orchestrators runexecutor.validate()to predict state changes WITHOUT executing the module (apcore PROTOCOL_SPEC §5.6). Returns{valid, requires_approval, predicted_changes, checks}. - Three transports — stdio (default, for desktop clients), Streamable HTTP, and SSE
- JWT authentication — optional Bearer token auth for HTTP transports with
JWTAuthenticator, permissive mode, PEM key file support, and env var fallback - Approval mechanism — runtime approval via MCP elicitation, auto-approve, or always-deny handlers
- AI guidance — error responses include
retryable,ai_guidance,user_fixable, andsuggestionfields for agent consumption - AI intent metadata — tool descriptions enriched with
x-when-to-use,x-when-not-to-use,x-common-mistakes,x-workflow-hintsfrom module metadata - Extension helpers — modules can call
report_progress()andelicit()during execution for MCP progress reporting and user input - Annotation mapping — apcore annotations (readonly, destructive, idempotent) map to MCP ToolAnnotations
- Schema conversion — JSON Schema
$ref/$defsinlining, strict mode for OpenAI Structured Outputs - Error sanitization — ACL errors and internal errors are sanitized; stack traces are never leaked
- Dynamic registration — modules registered/unregistered at runtime are reflected immediately
- Dual output — same registry powers both MCP Server and OpenAI tool definitions
- Tool Explorer — browser-based UI for browsing schemas and testing tools interactively, with Swagger-UI-style auth input
- Config Bus integration — registers an
mcpnamespace with the apcore Config Bus; configure transport, host, port, and more via unifiedapcore.yamlorAPCORE_MCP_*env vars - Error Formatter Registry — registers an MCP-specific error formatter for ecosystem-wide consistent error handling
Config Bus Integration
apcore-mcp registers an mcp namespace with the apcore Config Bus at import time. This means 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: false
Environment variable overrides use the APCORE_MCP_ prefix:
APCORE_MCP_TRANSPORT=streamable-http
APCORE_MCP_PORT=9000
APCORE_MCP_EXPLORER=true
Defaults: 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:
from apcore_mcp import MCP_NAMESPACE, MCP_ENV_PREFIX, MCP_DEFAULTS
How It Works
Mapping: apcore to MCP
| apcore | MCP |
|---|---|
metadata["display"]["mcp"]["alias"] or module_id |
Tool name |
metadata["display"]["mcp"]["description"] + guidance suffix or description |
Tool description |
input_schema |
inputSchema |
annotations.readonly |
ToolAnnotations.readOnlyHint |
annotations.destructive |
ToolAnnotations.destructiveHint |
annotations.idempotent |
ToolAnnotations.idempotentHint |
annotations.open_world |
ToolAnnotations.openWorldHint |
Mapping: apcore to OpenAI Tools
| apcore | OpenAI |
|---|---|
module_id (image.resize) |
name (image-resize) |
description |
description |
input_schema |
parameters |
Module IDs with dots are normalized to dashes for OpenAI compatibility (bijective mapping).
Architecture
Your apcore project (unchanged)
│
│ extensions directory
▼
apcore-mcp (separate process / library call)
│
├── MCP Server path
│ SchemaConverter + AnnotationMapper
│ → MCPServerFactory → ExecutionRouter → TransportManager
│
└── OpenAI Tools path
SchemaConverter + AnnotationMapper + IDNormalizer
→ OpenAIConverter → list[dict]
Development
git clone https://github.com/aiperceivable/apcore-mcp-python.git
cd apcore-mcp
pip install -e ".[dev]"
pytest # ~689 tests
pytest --cov # with coverage report
License
Apache-2.0
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。