Skill-first API framework: every endpoint is also an MCP tool
Project description
harnessapi
Skill-first API framework — define a skill once, get both an HTTP endpoint and an MCP tool automatically.
Every skill is a folder. Drop in a handler.py and models.py and you have:
POST /skills/{name}— streaming SSE by default, JSON on request- An MCP tool at
/mcp— ready for Claude Desktop, Cursor, or any MCP client
Install
uv add harnessapi
Or clone and run locally:
git clone <repo>
cd harnessapi
uv sync
Quickstart
Create your skill folder:
my_project/
├── main.py
└── skills/
└── greet/
├── models.py
└── handler.py
skills/greet/models.py
from harnessapi import SkillInput, SkillOutput
class Input(SkillInput):
name: str
class Output(SkillOutput):
message: str
skills/greet/handler.py
"""Say hello to someone."""
from .models import Input, Output
async def handle(input: Input) -> Output:
return Output(message=f"Hello, {input.name}!")
main.py
from pathlib import Path
from harnessapi import HarnessAPI
app = HarnessAPI(skills_dir=Path(__file__).parent / "skills")
Run it exactly like a FastAPI app:
uvicorn main:app --reload
Try the factorial example
The repo ships with a streaming factorial skill that demonstrates SSE + MCP together.
# from the repo root
uv run uvicorn examples.factorial_app.main:app --reload
Call via HTTP — SSE stream (default)
curl -X POST http://localhost:8000/skills/factorial \
-H "Content-Type: application/json" \
-d '{"n": 5}'
Response (each multiplication step streamed as it's computed):
event: chunk
data: start: 1
event: chunk
data: 2: 2
event: chunk
data: 3: 6
event: chunk
data: 4: 24
event: chunk
data: 5: 120
event: done
data:
Call via HTTP — plain JSON
curl -X POST http://localhost:8000/skills/factorial \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"n": 5}'
{"chunks": ["start: 1", "2: 2", "3: 6", "4: 24", "5: 120"]}
Connect an MCP client
The MCP server is automatically available at:
http://localhost:8000/mcp
Add it to Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"harnessapi": {
"url": "http://localhost:8000/mcp"
}
}
}
Or add it to Cursor settings under MCP servers.
The factorial skill is automatically registered as an MCP tool with parameter input.n: int.
Skill folder structure
skills/
└── my_skill/
├── handler.py # REQUIRED — async def handle(input: Input) -> Output
├── models.py # REQUIRED — class Input(SkillInput), class Output(SkillOutput)
├── skill.toml # optional — metadata
├── defaults/
│ └── input.json # optional — default values shown in OpenAPI docs
└── examples/
└── 01.json # optional — {input: {...}, output: {...}} example pairs
skill.toml (all optional):
[skill]
description = "What this skill does"
is_mcp = true # expose as MCP tool (default: true)
tags = ["math"]
timeout_secs = 30
Streaming vs non-streaming
Return a value → non-streaming (single result SSE event):
async def handle(input: Input) -> Output:
return Output(...)
Use yield → streaming (multiple chunk SSE events):
async def handle(input: Input):
for item in compute_steps(input):
yield item
Decorator API (no folder needed)
from harnessapi import HarnessAPI, SkillInput, SkillOutput, skill
class TranslateInput(SkillInput):
text: str
target_lang: str = "es"
class TranslateOutput(SkillOutput):
translated: str
@skill(
name="translate",
input_model=TranslateInput,
output_model=TranslateOutput,
is_mcp=True,
)
async def translate_handler(input: TranslateInput) -> TranslateOutput:
return TranslateOutput(translated=f"[{input.target_lang}] {input.text}")
app = HarnessAPI(title="My Skills")
Runtime edit endpoint (advanced)
Enable hot-swapping a skill's handler over HTTP:
app = HarnessAPI(skills_dir="./skills", enable_edit_endpoints=True)
curl -X POST http://localhost:8000/skills/factorial/edit \
-H "Content-Type: application/json" \
-d '{
"source_code": "async def handle(input):\n yield f\"custom: {input.n}\"",
"persist": false
}'
Security note: The edit endpoint executes arbitrary Python. Always protect it with authentication middleware in production. It is disabled by default.
SSE event protocol
| Event | When |
|---|---|
chunk |
Each yielded value from a streaming handler |
result |
The final output of a non-streaming handler |
done |
Always the last event |
error |
Handler raised an exception |
OpenAPI docs
Interactive docs are available at http://localhost:8000/docs — all skills appear as documented POST endpoints with their Pydantic schemas.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file harnessapi-0.1.0.tar.gz.
File metadata
- Download URL: harnessapi-0.1.0.tar.gz
- Upload date:
- Size: 97.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9ac5b659da328ccb127b8ea8ceccafd1e40f335020832a73f85e6e8e120190a
|
|
| MD5 |
f95a704111ec3d05e815f4ecead51c32
|
|
| BLAKE2b-256 |
6c33516cf92bbb413e6e25e2813f43538ec0e63bf0b389fa92529984a4582a13
|
File details
Details for the file harnessapi-0.1.0-py3-none-any.whl.
File metadata
- Download URL: harnessapi-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5f5f4c9a71b34ef1086dfc1ba9f15e461d01651fad4b7fc586150b068a4d6ca
|
|
| MD5 |
d7f4e0f812786e07da7076b3b7e64770
|
|
| BLAKE2b-256 |
cd464ed718a568ba61d6f307851301e5ab7cfb76ac4fd4a4e06366fff380e583
|