Fastapi Apcore
FastAPI integration for apcore(AI-Perceivable Core). Expose your FastAPI routes as MCP tools with auto-discovery, Pydantic schema extraction, and built-in observability.
Ask AI about Fastapi Apcore
Powered by Claude Β· Grounded in docs
I know everything about Fastapi Apcore. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
fastapi-apcore
FastAPI integration for apcore (AI-Perceivable Core). Expose your FastAPI routes as MCP tools with auto-discovery, Pydantic schema extraction, and built-in observability.
Features
- Route scanning -- auto-discover FastAPI routes and convert them to apcore modules
- Annotation inference --
GET-> readonly+cacheable,DELETE-> destructive,PUT-> idempotent - Pydantic schema extraction -- input/output schemas extracted from Pydantic models and OpenAPI spec
- Two scanner backends -- OpenAPI-based (accurate) and native route inspection (fast)
- Display overlay --
binding_path=applies a sparsebinding.yamldisplay overlay (Β§5.13) to control CLI/MCP/A2A surface aliases, descriptions, and guidance without touching code - Simplified module IDs --
simplify_ids=Trueextracts clean function names from FastAPI operationIds (deprecated β usebinding_pathwithdisplay.cli.aliasinstead) @moduledecorator -- define standalone AI-callable modules with full schema enforcement- YAML binding -- zero-code module definitions via external
.binding.yamlfiles - MCP server -- stdio, streamable-http, and SSE transports via
fastapi-apcore serve - Observability -- distributed tracing, metrics, structured logging, error history, usage tracking
- Input validation -- validate tool inputs against Pydantic schemas before execution
- CLI-first workflow --
fastapi-apcore scan+fastapi-apcore servefor zero-intrusion integration - MCP Tool Explorer -- browser UI for inspecting modules via
--explorer - JWT authentication -- protect MCP endpoints with Bearer tokens via
--jwt-secret - Approval system -- require approval for destructive operations via
--approval - AI enhancement -- enrich module metadata using local SLMs via
--ai-enhance - Async tasks -- background task submission, status tracking, and cancellation
- Unified entry point --
FastAPIApcoreclass provides property-based access to all components - CLI generation --
create_cli()turns FastAPI routes into a Click CLI that proxies to the running API - HTTP proxy modules --
HTTPProxyRegistryWriterregisters scanned routes as HTTP-forwarding modules for CLI and remote execution
Requirements
- Python >= 3.11
- FastAPI >= 0.100
- apcore >= 0.13.1
- apcore-toolkit >= 0.4.0
Installation
# Core
pip install fastapi-apcore
# With MCP server support (required for serve/export)
pip install fastapi-apcore[mcp]
# With CLI
pip install fastapi-apcore[cli]
# Everything
pip install fastapi-apcore[all]
Quick Start
1. Add FastAPIApcore to your app
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi_apcore import FastAPIApcore
apcore = FastAPIApcore()
@asynccontextmanager
async def lifespan(app: FastAPI):
apcore.init_app(app) # Auto-scan routes, discover modules, start MCP server
yield
app = FastAPI(lifespan=lifespan)
@app.get("/greet/{name}")
def greet(name: str) -> dict:
"""Greet a user by name."""
return {"message": f"Hello, {name}!"}
Or use the factory pattern:
from fastapi import FastAPI
from fastapi_apcore import FastAPIApcore
apcore = FastAPIApcore()
def create_app() -> FastAPI:
app = FastAPI()
# ... add routers ...
apcore.init_app(app)
return app
2. Scan routes and start MCP server
# Scan FastAPI routes -> register as apcore modules -> start MCP server
fastapi-apcore serve myapp.main:app --transport streamable-http --port 9090 --explorer
That's it. Your FastAPI routes are now MCP tools.
3. Connect an MCP client
For Claude Desktop, add to your config:
{
"mcpServers": {
"my-fastapi-app": {
"command": "fastapi-apcore",
"args": ["serve", "myapp.main:app"]
}
}
}
For HTTP transport (remote access):
fastapi-apcore serve myapp.main:app --transport streamable-http --host 0.0.0.0 --port 9090
Integration Paths
fastapi-apcore supports three ways to define AI-perceivable modules:
Route Scanning (zero-intrusion)
Scan existing FastAPI routes without modifying any code:
# Direct registration (in-memory)
fastapi-apcore scan myapp.main:app
# Generate YAML binding files (persistent)
fastapi-apcore scan myapp.main:app --output yaml --dir ./apcore_modules
# Preview without side effects
fastapi-apcore scan myapp.main:app --dry-run
# Filter routes by regex
fastapi-apcore scan myapp.main:app --include "users\." --exclude "\.delete$"
@module Decorator
Define standalone modules with explicit schemas:
from fastapi_apcore import FastAPIApcore
apcore = FastAPIApcore()
@apcore.module(id="math.add", tags=["math"], description="Add two numbers")
def add(a: int, b: int) -> int:
return a + b
YAML Binding (zero-code)
Define modules via external .binding.yaml files in APCORE_MODULE_DIR:
bindings:
- module_id: users.greet
target: myapp.views:greet
description: "Greet a user by name"
tags: [users]
input_schema:
properties:
name: { type: string }
required: [name]
output_schema:
properties:
message: { type: string }
Unified Entry Point
The FastAPIApcore instance provides property-based access to all components:
apcore = FastAPIApcore()
# Properties (lazy-loaded singletons)
apcore.registry # apcore Registry
apcore.executor # apcore Executor (with extensions)
apcore.settings # ApcoreSettings
apcore.metrics_collector # MetricsCollector | None
apcore.context_factory # FastAPIContextFactory
apcore.task_manager # AsyncTaskManager
apcore.extension_manager # ExtensionManager
# Module execution
result = apcore.call("math.add", {"a": 1, "b": 2})
result = await apcore.call_async("math.add", {"a": 1, "b": 2}, request=request)
# Streaming
async for chunk in apcore.stream("ai.chat", {"prompt": "hello"}, request=request):
...
# With timeout/cancellation
result = apcore.cancellable_call("slow.task", timeout=30.0)
# Module introspection
apcore.list_modules(tags=["math"])
apcore.describe("math.add")
# Background tasks
task_id = await apcore.submit_task("batch.process", {"ids": [1, 2, 3]})
status = apcore.get_task_status(task_id)
await apcore.cancel_task(task_id)
# MCP serving
apcore.serve(transport="streamable-http", port=9090, explorer=True)
tools = apcore.to_openai_tools(strict=True)
# Standalone MCP server (fresh registry, scan + serve in one call)
apcore.create_mcp_server(app, transport="streamable-http", port=9090)
# CLI generation (routes become Click commands that proxy to the running API)
cli = apcore.create_cli(app, prog_name="myapp-cli", base_url="http://localhost:8000")
cli(standalone_mode=True)
# MCP helpers (inside module execution)
await FastAPIApcore.report_progress(context, progress=50, total=100)
response = await FastAPIApcore.elicit(context, "Please confirm")
# Singleton access
apcore = FastAPIApcore.get_instance()
init_app() Reference
init_app() performs a complete initialization sequence:
apcore.init_app(
app, # FastAPI application instance
scan=True, # Auto-scan routes (default: True)
scan_source="openapi", # Scanner backend: "openapi" or "native"
simplify_ids=False, # Use simplified module IDs (function names only)
include=None, # Regex: only register matching module IDs
exclude=None, # Regex: skip matching module IDs
)
What init_app() does (in order):
- Auto-discover modules from YAML bindings and
APCORE_MODULE_PACKAGES - Scan FastAPI routes and register them as apcore modules
- Enable hot-reload if
APCORE_HOT_RELOAD=true - Start embedded MCP server if
APCORE_EMBEDDED_SERVERis configured
create_mcp_server() Reference
Create a standalone MCP server with a fresh registry (independent of the singleton):
# Full scan mode -- scan all routes and serve as MCP tools
apcore.create_mcp_server(
app,
transport="streamable-http",
port=9090,
binding_path="bindings/", # apply display overlay (Β§5.13) β sets MCP tool names/descriptions
commands_dir="commands/", # convention-scanned plain functions (Β§5.14)
explorer=True,
)
# Custom modules mode -- discover from a directory only
apcore.create_mcp_server(
extensions_dir="./mcp/modules",
scan=False,
transport="streamable-http",
port=9090,
)
binding_path accepts a single .binding.yaml file or a directory of *.binding.yaml files. When set, DisplayResolver resolves metadata["display"]["mcp"] on all scanned modules before they are registered, so MCP tool names and descriptions reflect the overlay.
commands_dir points to a directory of plain Python function files. When set, ConventionScanner from apcore-toolkit scans for public functions and registers them as additional modules alongside the scanned API routes (Β§5.14).
create_cli() Reference
Generate a Click CLI group that proxies to your running FastAPI server. Commands are auto-grouped by namespace prefix using GroupedModuleGroup (e.g., myapp-cli product list instead of myapp-cli product.list):
from fastapi_apcore import FastAPIApcore
from myapp.main import app
apcore = FastAPIApcore()
cli = apcore.create_cli(
app,
prog_name="myapp-cli",
base_url="http://localhost:8000",
binding_path="bindings/", # apply display overlay (Β§5.13) β sets CLI command names/descriptions
commands_dir="commands/", # convention-scanned plain functions (Β§5.14)
max_content_width=160, # wider help output for long command names
)
if __name__ == "__main__":
cli(standalone_mode=True)
Each scanned route becomes a CLI command named after display.cli.alias from the binding overlay (or the module_id if no overlay is set). The commands forward HTTP requests to the running API using HTTPProxyRegistryWriter. Built-in subcommands include list, describe, completion, and man.
commands_dir points to a directory of plain Python function files. When set, ConventionScanner from apcore-toolkit scans for public functions and registers them as additional modules alongside the scanned API routes (Β§5.14).
Display Overlay (binding_path)
Control how each route appears to CLI users and MCP clients without changing your FastAPI code:
# bindings/myapp.binding.yaml
bindings:
- module_id: product.get_product_product__product_id_.get
display:
alias: product-get # base alias for all surfaces
description: Get a product by its ID
cli:
alias: get-product # CLI command name: myapp-cli get-product
mcp:
alias: get_product # MCP tool name (must match [a-zA-Z_][a-zA-Z0-9_-]*)
guidance: |
Always pass product_id as a UUID string, not an integer.
Resolution chain per field: surface-specific override > display default > scanner value.
Note:
simplify_ids=Trueis deprecated. Usebinding_pathwithdisplay.cli.alias/display.mcp.aliasinbinding.yamlinstead.
Configuration
All settings are read from environment variables with the APCORE_ prefix:
Core Settings
| Variable | Default | Description |
|---|---|---|
APCORE_MODULE_DIR | apcore_modules/ | Directory for YAML binding files |
APCORE_AUTO_DISCOVER | true | Auto-discover modules on startup |
APCORE_BINDING_PATTERN | *.binding.yaml | Glob pattern for binding files |
APCORE_MODULE_PACKAGES | β | Comma-separated packages to scan for @module |
APCORE_VALIDATE_INPUTS | false | Validate inputs before module execution |
MCP Server Settings
| Variable | Default | Description |
|---|---|---|
APCORE_SERVE_TRANSPORT | stdio | MCP transport: stdio, streamable-http, sse |
APCORE_SERVE_HOST | 127.0.0.1 | Host for HTTP transport |
APCORE_SERVE_PORT | 9090 | Port for HTTP transport |
APCORE_SERVER_NAME | apcore-mcp | MCP server name |
APCORE_SERVER_VERSION | β | MCP server version string |
APCORE_EXPLORER_ENABLED | false | Enable MCP Tool Explorer UI |
APCORE_EXPLORER_PREFIX | /explorer | URL prefix for Explorer |
APCORE_EXPLORER_ALLOW_EXECUTE | false | Allow execution from Explorer |
Authentication & Security
| Variable | Default | Description |
|---|---|---|
APCORE_JWT_SECRET | β | JWT secret for MCP authentication |
APCORE_JWT_ALGORITHM | HS256 | JWT signing algorithm |
APCORE_JWT_AUDIENCE | β | Expected JWT audience claim |
APCORE_JWT_ISSUER | β | Expected JWT issuer claim |
APCORE_ACL_PATH | β | Path to ACL YAML rules file |
APCORE_MIDDLEWARES | β | Comma-separated middleware dotted paths |
Observability
| Variable | Default | Description |
|---|---|---|
APCORE_TRACING | β | Enable tracing: true or JSON config |
APCORE_METRICS | β | Enable metrics: true or JSON config |
APCORE_OBSERVABILITY_LOGGING | β | Enable structured logging: true or JSON config |
Task Management
| Variable | Default | Description |
|---|---|---|
APCORE_TASK_MAX_CONCURRENT | 10 | Max concurrent background tasks |
APCORE_TASK_MAX_TASKS | 1000 | Max total tasks in queue |
APCORE_TASK_CLEANUP_AGE | 3600 | Max age (seconds) for completed tasks |
APCORE_CANCEL_DEFAULT_TIMEOUT | β | Default cancellation timeout (seconds) |
Advanced
| Variable | Default | Description |
|---|---|---|
APCORE_EXECUTOR_CONFIG | β | JSON string for Executor configuration |
APCORE_CONTEXT_FACTORY | β | Dotted path to custom ContextFactory class |
APCORE_HOT_RELOAD | false | Watch module files for changes |
APCORE_HOT_RELOAD_PATHS | β | Comma-separated paths to watch |
APCORE_EMBEDDED_SERVER | β | Auto-start MCP server on init: true or JSON config |
APCORE_OUTPUT_FORMATTER | β | Dotted path to output formatter function |
APCORE_AI_ENHANCE | false | Enable AI metadata enhancement |
See fastapi_apcore.ApcoreSettings for the full list of 40 settings.
CLI Reference
scan -- Scan FastAPI routes
fastapi-apcore scan myapp.main:app [OPTIONS]
| Option | Description |
|---|---|
--source, -s | Scanner backend: openapi (default) or native |
--output, -o | Output format: yaml. Omit for direct registration |
--dir, -d | Output directory (default: APCORE_MODULE_DIR) |
--dry-run | Preview without writing files or registering |
--include | Regex: only include matching module IDs |
--exclude | Regex: exclude matching module IDs |
--ai-enhance | AI-enhance module metadata |
--verify | Verify written output |
serve -- Start MCP server
fastapi-apcore serve myapp.main:app [OPTIONS]
| Option | Description |
|---|---|
--transport, -t | Transport: stdio (default), streamable-http, sse |
--host | Host for HTTP transport (default: 127.0.0.1) |
--port, -p | Port for HTTP transport (default: 9090) |
--name | MCP server name |
--explorer | Enable MCP Tool Explorer UI |
--jwt-secret | JWT secret key for authentication |
--approval | Approval mode: off, elicit, auto-approve, always-deny |
--tags | Comma-separated module tag filter |
--prefix | Module ID prefix filter |
--validate-inputs | Validate inputs before execution |
--output-formatter | Dotted path to output formatter function |
--log-level | Log level: DEBUG, INFO, WARNING, ERROR |
export -- Export as OpenAI tools
fastapi-apcore export [OPTIONS]
| Option | Description |
|---|---|
--format, -f | Export format (default: openai-tools) |
--strict | Add strict: true for Structured Outputs |
--embed-annotations | Include annotation metadata in descriptions |
--tags | Comma-separated module tag filter |
--prefix | Module ID prefix filter |
tasks -- Manage async tasks
fastapi-apcore tasks list [--status STATUS]
fastapi-apcore tasks cancel TASK_ID
fastapi-apcore tasks cleanup [--max-age SECONDS]
Scanning
Two scanner backends are available:
| Backend | Method | Best for |
|---|---|---|
| OpenAPI (default) | Uses FastAPI's auto-generated OpenAPI spec | Accuracy, handles all FastAPI features |
| Native | Directly inspects app.routes | Speed, no OpenAPI generation overhead |
from fastapi_apcore import get_scanner
# OpenAPI scanner (default) -- full operationId-based IDs
scanner = get_scanner("openapi")
modules = scanner.scan(app)
# OpenAPI scanner with simplified IDs (recommended for CLI)
scanner = get_scanner("openapi", simplify_ids=True)
modules = scanner.scan(app)
# product.get_product_product__product_id_.get β product.get_product.get
# Native scanner
scanner = get_scanner("native")
modules = scanner.scan(app, include=r"users\.", exclude=r"\.delete$")
The simplify_ids option extracts the original Python function name from FastAPI's auto-generated operationId and uses only the first path segment as prefix, producing much shorter and more readable module IDs:
Default: credit_purchase.purchase.status.get_purchase_status_by_payment_intent.get (73 chars)
simplify_ids: credit_purchase.get_purchase_status_by_payment_intent.get (57 chars)
It defaults to False for backward compatibility.
Users only need to interact with two things:
FastAPIApcore-- the unified entry point (import fromfastapi_apcore)- CLI --
fastapi-apcore scan/serve/export/tasks
Everything in engine/ is internal wiring that FastAPIApcore manages automatically.
Integration with apcore Ecosystem
fastapi-apcore bridges FastAPI to the apcore protocol:
FastAPI App -> FastAPIApcore -> apcore (Registry/Executor) -> apcore-mcp (MCP Server)
All apcore types are re-exported for convenience:
from fastapi_apcore import (
Registry, Executor, Context, Identity, Config,
ACL, Middleware, ModuleAnnotations, FunctionModule,
CancelToken, PreflightResult, ModuleError,
ExecutionCancelledError, ModuleDisabledError,
ApprovalHandler, AutoApproveHandler, AlwaysDenyHandler,
EventEmitter, EventSubscriber, ApCoreEvent,
HTTPProxyRegistryWriter, # lazy import, requires apcore-toolkit
module, # @module decorator
)
License
Apache-2.0
