Metadata-Version: 2.4
Name: fastapi-apcore
Version: 0.1.0
Summary: FastAPI integration for apcore AI-Perceivable Core — exposes FastAPI routes as apcore modules with auto-discovery, schema extraction from Pydantic models, and OpenAPI-based scanning.
Author-email: aipartnerup <tercel.yi@gmail.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/aipartnerup/
Project-URL: Repository, https://github.com/aipartnerup/fastapi-apcore
Keywords: fastapi,apcore,mcp,llm,ai,tools,pydantic,schema,module,cli,auto-discovery,integration
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: fastapi>=0.100
Requires-Dist: apcore>=0.13.0
Requires-Dist: apcore-toolkit>=0.2.0
Provides-Extra: mcp
Requires-Dist: apcore-mcp>=0.10.0; extra == "mcp"
Provides-Extra: cli
Requires-Dist: apcore-cli>=0.2.0; extra == "cli"
Requires-Dist: typer>=0.9; extra == "cli"
Provides-Extra: all
Requires-Dist: fastapi-apcore[cli,mcp]; extra == "all"
Provides-Extra: dev
Requires-Dist: apdev[dev]>=0.2.3; extra == "dev"
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: httpx>=0.24; extra == "dev"
Requires-Dist: ruff>=0.1; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Requires-Dist: coverage>=7.0; extra == "dev"

# fastapi-apcore

FastAPI integration for [apcore](https://github.com/aipartnerup/apcore-python) (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)
- **`@module` decorator** -- define standalone AI-callable modules with full schema enforcement
- **YAML binding** -- zero-code module definitions via external `.binding.yaml` files
- **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 serve` for 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** -- `FastAPIApcore` class provides property-based access to all components

## Requirements

- Python >= 3.11
- FastAPI >= 0.100
- apcore >= 0.13.0
- apcore-toolkit >= 0.2.0

## Installation

```bash
# 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

```python
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**:

```python
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

```bash
# 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:

```json
{
  "mcpServers": {
    "my-fastapi-app": {
      "command": "fastapi-apcore",
      "args": ["serve", "myapp.main:app"]
    }
  }
}
```

For **HTTP transport** (remote access):

```bash
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:

```bash
# 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:

```python
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`:

```yaml
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:

```python
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)

# 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:

```python
apcore.init_app(
    app,                    # FastAPI application instance
    scan=True,              # Auto-scan routes (default: True)
    scan_source="openapi",  # Scanner backend: "openapi" or "native"
    include=None,           # Regex: only register matching module IDs
    exclude=None,           # Regex: skip matching module IDs
)
```

**What `init_app()` does (in order):**

1. **Auto-discover** modules from YAML bindings and `APCORE_MODULE_PACKAGES`
2. **Scan** FastAPI routes and register them as apcore modules
3. **Enable hot-reload** if `APCORE_HOT_RELOAD=true`
4. **Start embedded MCP server** if `APCORE_EMBEDDED_SERVER` is configured

## 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

```bash
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

```bash
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

```bash
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

```bash
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 |

```python
from fastapi_apcore import get_scanner

# OpenAPI scanner (default)
scanner = get_scanner("openapi")
modules = scanner.scan(app)

# Native scanner
scanner = get_scanner("native")
modules = scanner.scan(app, include=r"users\.", exclude=r"\.delete$")
```

## Project Structure

```
fastapi_apcore/
    __init__.py      # Public API -- all user-facing exports
    client.py        # FastAPIApcore -- the main entry point
    cli.py           # Typer CLI (scan, serve, export, tasks)
    engine/          # Internal engine (not for direct import)
        config.py        # ApcoreSettings -- env-based configuration
        context.py       # FastAPIContextFactory -- Request -> Identity
        registry.py      # Singleton management (Registry, Executor)
        extensions.py    # FastAPIDiscoverer + FastAPIModuleValidator
        observability.py # Tracing/Metrics/Logging auto-setup
        tasks.py         # AsyncTaskManager singleton
        shortcuts.py     # Convenience functions
        serializers.py   # ScannedModule serialization
    scanners/        # Route scanning backends
        base.py          # BaseScanner + ScannedModule
        native.py        # Direct route inspection
        openapi.py       # OpenAPI spec-based scanning
    output/          # Output writers
        registry_writer.py  # Direct registry registration
        yaml_writer.py      # YAML binding file generation
```

Users only need to interact with two things:
- **`FastAPIApcore`** -- the unified entry point (import from `fastapi_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:

```python
from fastapi_apcore import (
    Registry, Executor, Context, Identity, Config,
    ACL, Middleware, ModuleAnnotations, FunctionModule,
    CancelToken, PreflightResult, ModuleError,
    ApprovalHandler, AutoApproveHandler, AlwaysDenyHandler,
    EventEmitter, EventSubscriber, ApCoreEvent,
    module,  # @module decorator
)
```

## License

Apache-2.0
