Metadata-Version: 2.4
Name: a2api
Version: 1.0.0
Summary: Convert APIs and functions into a compact call protocol that LLMs can search, describe and invoke at any API size
Author: haesookim
License-Expression: MIT
Project-URL: Homepage, https://github.com/metahackAI/a2api
Project-URL: Repository, https://github.com/metahackAI/a2api
Project-URL: Documentation, https://github.com/metahackAI/a2api/tree/main/docs
Project-URL: Issues, https://github.com/metahackAI/a2api/issues
Keywords: llm,agents,function-calling,mcp,openapi,api,tool-use
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: jsonschema<5,>=4.19
Provides-Extra: mcp
Requires-Dist: mcp<3,>=2.3; extra == "mcp"
Provides-Extra: openapi
Requires-Dist: PyYAML<7,>=6.0.2; extra == "openapi"
Provides-Extra: source
Requires-Dist: tree-sitter<0.26,>=0.25; extra == "source"
Requires-Dist: tree-sitter-java<0.24,>=0.23; extra == "source"
Requires-Dist: tree-sitter-go<0.26,>=0.25; extra == "source"
Requires-Dist: tree-sitter-rust<0.25,>=0.24; extra == "source"
Dynamic: license-file

# a2api

**a2api turns existing APIs and functions into a small call protocol that LLM hosts can search, describe and invoke cheaply — at any API size.**

Convert what you already have — an OpenAPI document, a Java/Spring MVC, Go `net/http` or Rust Axum source tree, or plain Python functions — and call it directly, through three fixed MCP tools, or as selected function tools for a model. No review files, build directories or registry are required for the first call.

```bash
pip install a2api                 # core: convert, invoke, ToolSession
pip install "a2api[mcp]"          # MCP stdio bridge
pip install "a2api[openapi]"      # YAML OpenAPI documents
pip install "a2api[source]"       # Spring / Go / Rust source importers
```

Guides (Korean): [converting legacy APIs](https://github.com/metahackAI/a2api/blob/main/docs/36-legacy-conversion-guide.ko.md) — with OpenAPI and code, or code only (Spring, Go, Rust, Python, other languages) · [call protocol](https://github.com/metahackAI/a2api/blob/main/docs/20-call-protocol.ko.md) · [model host tools](https://github.com/metahackAI/a2api/blob/main/docs/24-selected-tools.ko.md) · [evaluation](https://github.com/metahackAI/a2api/blob/main/docs/34-completion-results.ko.md)

## Python functions

```python
from typing import TypedDict
from a2api import Application, ExecutionContext, capability

class Order(TypedDict):
    id: str
    status: str

@capability(id="orders.get", effect="read")
def get_order(id: str) -> Order:
    """Get one order by ID."""
    return {"id": id, "status": "paid"}

@capability(id="orders.cancel", effect="write")
async def cancel_order(id: str, *, context: ExecutionContext) -> Order:
    """Cancel an order. Writes are never retried automatically."""
    return {"id": id, "status": "cancelled"}

app = Application([get_order, cancel_order])
caller = ExecutionContext("alice", "tenant-a", frozenset())
print(app.invoke("orders.get", {"id": "ORD-1"}, context=caller))
# {'status': 'succeeded', 'data': {'id': 'ORD-1', 'status': 'paid'}, 'completeness': {...}}
```

Type hints and `TypedDict`s become JSON Schemas; inputs and outputs are validated on every call; `context` is host-only and never part of the model's input.

## OpenAPI or source code → HTTP calls

```python
from a2api import HTTPDeployment, convert
from a2api.importers import OpenAPIImporter, SpringSourceImporter

source = OpenAPIImporter.from_file("orders.openapi.json", namespace="shop")
# or: SpringSourceImporter.from_directory("src/main/java", namespace="shop")  (GoSourceImporter, RustSourceImporter)
app = convert(source, operations={
    "listOrders": {"effect": "read", "select": ["items[].id", "items[].status", "total", "hasMore"]},
    "cancelOrder": {"effect": "write"},
}, deployment=HTTPDeployment("https://orders.internal", credential_provider=lambda ctx: {...}, timeout_ms=5000))
```

`select` keeps only public fields (nested paths and `[]` array items supported) after the raw response is validated against the original schema. Credentials and origins stay with the host.

## Model hosts: ToolSession

```python
from a2api import ToolSession

session = ToolSession(app, caller)               # or ToolSession.from_mcp(mcp_client)
await session.auto(user_question)                # small catalog: all functions; large: host pre-search
tools = session.tools()                          # Chat Completions function tools for this request
result = await session.call(name, arguments)     # validated invoke, never retried
text = session.render(result)                    # compact model-facing result with failure hints
```

Small catalogs are exposed directly; large catalogs get one `a2api_search` tool plus the functions the host pre-selected from the question. Function parameters are the exact business input schema (HTTP `path/query/body` flattened when lossless).

## MCP

```python
import anyio
from a2api.mcp_bridge import serve_stdio
anyio.run(serve_stdio, app, caller)   # tools: a2api.discover / a2api.describe / a2api.invoke
```

The MCP tool list is always the same three tools, whatever the catalog size.

## Protocol

`a2api-call` defines discover/describe/invoke requests, a six-field capability descriptor and structured errors; the JSON Schema ships in the package (`a2api.protocol_schema()`). `a2api-tools` defines how hosts present selected capabilities to models.

## Evaluation

The repository contains a benchmark that implements the same API in Python, Spring, Go and Rust at three sizes (19 / 55 / 235 operations) and compares a2api with passing the OpenAPI specification to the model, either as one function per operation or as the whole document in the prompt. On a fixed holdout of 720 runs with `nvidia/Muse-Glimmer-30B-NVFP4`:

| | a2api | spec → functions | spec → prompt |
|---|---:|---:|---:|
| Tasks passed | 239/240 (1 provider error) | 237/240 | 240/240 |
| Median tokens per task (small / medium / large) | 4.9k / 4.8k / 5.0k | 8.1k / 19.6k / 75.2k | 12.6k / 28.2k / 103.3k |
| Median seconds per task (small / medium / large) | 10.7 / 10.6 / 10.7 | 9.4 / 9.8 / 14.8 | 10.2 / 11.3 / 18.8 |

Context stays flat as the API grows and latency drops 27–43% for the large API. Accuracy was near the ceiling for all approaches, and for small and medium APIs a2api was slower than one-function-per-operation. Details, failures and limits are in the [evaluation document](https://github.com/metahackAI/a2api/blob/main/docs/34-completion-results.ko.md). Synthetic API, one model — not a guarantee for every API or model.

Licensed under MIT.
