Skip to main content
Glama
Hawk-API

hawkapi-mcp

Official
by Hawk-API
README.md
# hawkapi-mcp

MCP (Model Context Protocol) server for [HawkAPI](https://github.com/Hawk-API/HawkAPI). Auto-exports every route as an agent tool — any MCP-compatible client can call your API.

## Install

```bash
pip install hawkapi-mcp
```

## Quickstart

```python
from hawkapi import HawkAPI
from hawkapi.responses import JSONResponse
from hawkapi_mcp import mount_mcp

app = HawkAPI()

@app.get("/users/{user_id:int}")
async def get_user(user_id: int) -> JSONResponse:
    return JSONResponse({"id": user_id, "name": "Alice"})

@app.post("/items")
async def create_item(body: dict) -> JSONResponse:
    return JSONResponse({"created": body})

mount_mcp(app, allow_unauthenticated=True)  # serves POST /mcp — dev only; see Auth
```

> `/mcp` MUST be protected in production — pass `dependencies=[...]` to require
> auth, or `allow_unauthenticated=True` to opt out explicitly. See [Auth](#auth).

Point any MCP-compatible client at `http://your-host/mcp`. Every HawkAPI route becomes a tool — its `operationId` is the tool name, the OpenAPI schema becomes the input schema.

## Tool naming

| Route definition | Generated tool name |
|---|---|
| `@app.get("/users/{id}", operation_id="get_user")` | `get_user` |
| `@app.get("/users/{id}")` (no `operation_id`) | `get_users_id` |

## Tool input schema

The decorator combines path / query / header parameters and the JSON request body into a single object schema. Parameter names are namespaced so they cannot collide:

| Source | Schema key |
|---|---|
| Path parameter | `path.<name>` |
| Query parameter | `query.<name>` |
| Header parameter | `header.<name>` |
| JSON body | `body` |

Cookie parameters are deliberately **not** exposed as tool inputs — exposing session cookies as data trains agents to handle credentials as ordinary fields. Forward credentials via the request to `/mcp` instead (see [Auth](#auth)).

`tools/call` example:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_user",
    "arguments": {"path.user_id": "42"}
  }
}
```

The tool result has the response body in `content[0].text` and the raw HTTP status / headers in `structuredContent`. `isError` is `true` for any 4xx/5xx response.

Credential-bearing response headers are stripped from `structuredContent` before the agent sees them: `set-cookie`, `authorization`, and anything matching `x-*-token` / `x-*-secret`. Add more via `strip_response_headers={...}`.

## Filtering tools

```python
mount_mcp(app, include_only={"get_user", "create_item"})
mount_mcp(app, exclude={"internal_admin_route"})
```

## Supported JSON-RPC methods

* `initialize` — handshake. Returns the MCP protocol version, server info, and tool capability.
* `ping` — keepalive.
* `tools/list` — return the tool catalog.
* `tools/call` — invoke a tool. Returns response body + HTTP status.
* `notifications/initialized` — accepted, no response.

The endpoint accepts both single JSON-RPC objects and batches.

## Auth

**The `/mcp` endpoint exposes every route and MUST be protected.** A single MCP
tool call can invoke any route, and the bridge synthesises an inner ASGI request
— so middleware that only guards inner routes can be bypassed. To make this
hard to get wrong, `mount_mcp()` raises `RuntimeError` at mount time unless you
either:

* pass `dependencies=[...]` to attach auth (e.g. `Depends`) to the `/mcp` route, or
* explicitly opt out with `allow_unauthenticated=True` (local dev, or auth
  enforced upstream).

```python
from hawkapi import Depends
from hawkapi_mcp import mount_mcp

mount_mcp(app, dependencies=[Depends(verify_token)])  # protected
mount_mcp(app, allow_unauthenticated=True)            # explicit opt-out
```

`hawkapi-mcp` does not define its own auth layer — wire your HawkAPI dependencies
(HTTPBearer, OAuth2, API key) on the MCP route just like any other path. The
caller's `Authorization` and `Cookie` headers on the outer `/mcp` request are
forwarded into the synthetic inner request, so inner-route `Depends(auth)` sees
the real credentials (and the real client address is propagated where available).
Header arguments forwarded by the client land in the request before middleware runs.

## Tool catalog freshness

The tool catalog is derived from `app.openapi()` and cached. By default it is
cached for the process lifetime, so routes added/removed/disabled at runtime are
**not** reflected — call `server.invalidate_tools()` to force a refresh, or pass
`cache_ttl_seconds=...` to `mount_mcp()` (or `MCPServer`) to auto-refresh after
the given interval:

```python
mount_mcp(app, dependencies=[...], cache_ttl_seconds=60)
```

## Development

```bash
git clone https://github.com/Hawk-API/hawkapi-mcp.git
cd hawkapi-mcp
uv sync --extra dev
uv run pytest -q
uv run ruff check . && uv run ruff format --check .
uv run pyright src/
```

## Specification

Implements a subset of the [Model Context Protocol](https://modelcontextprotocol.io) sufficient to advertise and invoke tools. Streamable HTTP transport only — stdio is out of scope (deploy your app behind any ASGI server and the agent connects to the `/mcp` URL).

## License

MIT.

Maintenance

ActivityInactive
ResponsivenessNo issues