Python framework to build streaming APIs and MCP tools from skill folders
Project description
Python Skill Framework for MCP Tools and Streaming APIs
Write a skill. Get an API. Get an MCP tool. Ship.
harnessapi is a Python framework that turns a skill folder into a streaming HTTP API and a Model Context Protocol (MCP) tool simultaneously — no routes, no decorators, no separate MCP server to maintain.
skills/summarize/
├── models.py ← define input & output
├── handler.py ← write your logic
└── skill.toml ← name, description, tags, timeout
Drop the folder. Run the server. Your skill is live as an HTTP endpoint, an MCP tool, and in Swagger docs.
Contents
- Why harnessapi
- Quick start
- Try it instantly with uvx
- Streaming
- Every skill is an MCP tool
- Works with
- Scaffold in one command
- Example: streaming factorial
- Skill folder reference
- Hot-swap handlers at runtime
- Multi-tenancy & per-user skill variants
- Features
- Philosophy
- See also
Why harnessapi
Use harnessapi when you are:
- Building tools for Claude Desktop, Cursor, Copilot, or any MCP client
- Exposing Python functions as streaming API endpoints (Server-Sent Events)
- Converting an agentskills.io skill folder into a production API
- Shipping an LLM-powered microservice without FastAPI boilerplate
- Wrapping any Python function as an MCP tool in under a minute
Quick start
uv add harnessapi
skills/summarize/models.py
from harnessapi import SkillInput, SkillOutput
class Input(SkillInput):
text: str
max_length: int = 200
class Output(SkillOutput):
summary: str
skills/summarize/handler.py
from .models import Input, Output
async def handle(input: Input) -> Output:
return Output(summary=input.text[:input.max_length])
skills/summarize/skill.toml
[skill]
description = "Summarize text to a target length"
is_mcp = true
tags = ["text"]
timeout_secs = 30
main.py
from pathlib import Path
from harnessapi import HarnessAPI
app = HarnessAPI(skills_dir=Path(__file__).parent / "skills")
harnessapi run
Your skill is live at three places simultaneously:
| Endpoint | Details |
|---|---|
POST /skills/summarize |
HTTP endpoint — SSE streaming by default |
GET /docs |
Interactive OpenAPI / Swagger UI |
http://localhost:8000/mcp |
MCP server — ready for Claude, Cursor, Copilot |
Try it instantly with uvx
No install needed. uvx runs harnessapi in an isolated environment:
# Scaffold a new project
uvx harnessapi init my-project
# Enter and run it
cd my-project
uvx harnessapi run
Then call your skill:
# Streaming (SSE — default)
curl -X POST http://localhost:8000/skills/greet \
-H "Content-Type: application/json" \
-d '{"name": "world"}'
# Plain JSON
curl -X POST http://localhost:8000/skills/greet \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"name": "world"}'
{"message": "Hello, world! Welcome to harnessapi.", "length": 36}
Streaming — just use yield
Return a value for a single response. Use yield to stream chunks as they're produced. Same endpoint, same URL, no extra config.
# Non-streaming — return a value
async def handle(input: Input) -> Output:
return Output(result=compute(input))
# Streaming — yield chunks
async def handle(input: Input):
async for token in llm.stream(input.prompt):
yield token
Clients receive standard Server-Sent Events (SSE):
event: chunk
data: The answer is
event: chunk
data: 42.
event: done
data:
Need plain JSON? Add Accept: application/json — harnessapi collects all chunks and returns them together. Same handler, zero changes.
Every skill is an MCP tool
Every skill folder is automatically registered as a Model Context Protocol (MCP) tool. No extra code required.
{
"mcpServers": {
"my-skills": {
"url": "http://localhost:8000/mcp"
}
}
}
Add a skill folder → restart the server → it appears as an MCP tool. No registration. No schema maintenance.
Works with
| Client | How to connect |
|---|---|
| Claude Desktop | Add http://localhost:8000/mcp as an MCP server in settings |
| Cursor | Add under MCP Servers in Cursor settings |
| Copilot / VS Code | Any MCP-compatible client works |
| agentskills.io | Drop-in compatible — existing skill folders work as-is |
| Any HTTP client | POST /skills/{name} — curl, httpx, fetch |
Scaffold in one command
# New project with a sample greet skill
harnessapi init my-project
# Add API + MCP layer to an existing agentskills.io skill folder
harnessapi init --skill .agents/skills/summarize
# Convert an entire skills directory at once
harnessapi init --skills-dir .agents/skills
# Wrap a plain Python function as a skill
harnessapi init --function utils/compute.py --output skills
harnessapi is a compatible superset of the agentskills.io standard — existing skill folders with a SKILL.md are detected automatically.
Example: streaming factorial (SSE + MCP)
git clone https://github.com/edwinjosechittilappilly/harnessapi
cd harnessapi
uv sync
uv run uvicorn examples.factorial_app.main:app --reload
curl -X POST http://localhost:8000/skills/factorial \
-H "Content-Type: application/json" \
-d '{"n": 5}'
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:
Or collect everything as 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"]}
Skill folder reference
skills/
└── my_skill/
├── handler.py ← required: your logic
├── models.py ← required: Pydantic Input + Output
├── SKILL.md ← optional: agentskills.io compatible metadata
├── skill.toml ← optional: name, description, tags, timeout
├── defaults/
│ └── input.json ← optional: default values shown in /docs
└── examples/
└── 01.json ← optional: {input, output} pairs for docs
skill.toml
[skill]
description = "What this skill does"
is_mcp = true # set false to hide from MCP
tags = ["nlp"]
timeout_secs = 30
Hot-swap handlers at runtime
Patch a running skill handler without restarting the server:
app = HarnessAPI(skills_dir="./skills", enable_edit_endpoints=True)
curl -X POST http://localhost:8000/skills/summarize/edit \
-H "Content-Type: application/json" \
-d '{"source_code": "async def handle(input):
return Output(summary=input.text.upper())", "persist": true}'
Disabled by default. Add auth middleware before enabling in production.
Multi-tenancy & per-user skill variants
Add one parameter to HarnessAPI(...) and every skill gets per-user variants — same schema, different handler implementation, isolated routing. No restarts, no per-tenant route tables.
from harnessapi import HarnessAPI
from harnessapi.multitenancy import TenantBackend, SQLiteStorageBackend
backend = TenantBackend(
tenant_extractor=lambda req: req.headers.get("X-Tenant-ID"),
storage=SQLiteStorageBackend(path="./variants.db"),
)
app = HarnessAPI(skills_dir="./skills", tenant_backend=backend)
All existing endpoints continue to work. A /tenants/* management API is added automatically.
Agent workflow — clone → customize → test → promote:
# 1. Clone base skill source as starting point
curl -X POST /tenants/user-a/skills/greet/clone
# 2. Submit customized handler (validated before stored)
curl -X POST /tenants/user-a/skills/greet/customize \
-d '{"source_code": "async def handle(input):\n return Output(message=f\"Howdy, {input.name}!\")"}'
# 3. Test variant in isolation
curl -X POST /tenants/user-a/skills/greet/variants/{id}/run -d '{"name": "Alice"}'
# → {"message": "Howdy, Alice!"}
# 3b. Optional: set as preview — routes real tenant traffic without hard-promoting
curl -X POST /tenants/user-a/skills/greet/variants/{id}/preview
# 4. Promote — variant becomes the permanent active handler
curl -X POST /tenants/user-a/skills/greet/variants/{id}/promote
# 5. Route to variant automatically
curl -X POST /skills/greet -H "X-Tenant-ID: user-a" -d '{"name": "Alice"}'
# → {"message": "Howdy, Alice!"} ← variant handler
curl -X POST /skills/greet -H "X-Tenant-ID: user-b" -d '{"name": "Bob"}'
# → {"message": "Hello, Bob!"} ← base handler
Optional: per-user sandboxes for full process isolation:
from harnessapi.multitenancy import SandboxRegistry
backend = TenantBackend(
...,
sandbox_registry=SandboxRegistry(),
sandbox_provider="local_subprocess", # or "docker" / "kubernetes"
)
Optional: admin MCP server so agents manage variants as MCP tools:
app = HarnessAPI(
skills_dir="./skills",
tenant_backend=backend,
enable_admin_mcp=True,
admin_mcp_auth=require_api_key, # protect /admin-mcp in production
)
# Claude Desktop / Claude Code: add http://localhost:8000/admin-mcp as an MCP server
# Tools: clone_skill, customize_skill, preview_variant, promote_variant, run_variant, provision_sandbox, ...
See the multi-tenancy guide for the full API reference, sandbox providers, storage backends, and TenantBackend configuration.
Features
| Feature | Details |
|---|---|
| HTTP endpoint | POST /skills/{name} for every skill, automatically |
| Streaming | SSE by default · JSON via Accept: application/json |
| MCP server | /mcp · all skills auto-registered as MCP tools |
| OpenAPI docs | /docs · full Swagger UI, zero config |
| Pydantic validation | Invalid input rejected before your handler runs |
| Timeouts | Per-skill timeout_secs in skill.toml |
| Hot-swap | Runtime handler replacement via opt-in edit endpoint |
| Multi-tenancy | Per-user skill variants · promote/demote · SQLite or custom storage |
| Preview status | Sandbox-tested variants route real tenant traffic before full promotion |
| Per-user sandboxes | Local subprocess, Docker, or Kubernetes — pluggable SandboxProvider |
| Admin MCP server | /admin-mcp · manage variants as MCP tools from Claude Desktop / Claude Code |
| LocalFileStorageBackend | File-per-variant JSON storage, no DB deps |
| agentskills.io | Drop-in compatible — existing skill folders just work |
| CLI scaffold | uvx harnessapi init · --skill · --skills-dir · --function |
Philosophy
Most frameworks start with routes. Most agent frameworks start with tools. harnessapi starts with skills — the capability itself. The HTTP API and the MCP tool are consequences, not configuration.
Write the thing. Everything else follows.
See also
- FastMCP — MCP server framework harnessapi builds on
- FastAPI — the HTTP layer underneath
- agentskills.io — skill folder standard harnessapi is compatible with
- Model Context Protocol — the open protocol for agent tools
- Pydantic — data validation for skill inputs and outputs
Project details
Release history Release notifications | RSS feed
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.2.1.tar.gz.
File metadata
- Download URL: harnessapi-0.2.1.tar.gz
- Upload date:
- Size: 941.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","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 |
ec87a489fca233b3a1abe752f66ade1ec7fb35e73aa5452328d601c594d1329c
|
|
| MD5 |
394704ce8735b0b6fce5cbf1a8c26a73
|
|
| BLAKE2b-256 |
0f00a69a9d3eab0a19e124345650ec4e6a8bf797f91474fcfade0bc39752f7af
|
File details
Details for the file harnessapi-0.2.1-py3-none-any.whl.
File metadata
- Download URL: harnessapi-0.2.1-py3-none-any.whl
- Upload date:
- Size: 301.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","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 |
9eb7b99f4a94d96130dbf3370ec3fa54325d5c4f4731bffcaff76563b62a79e3
|
|
| MD5 |
6e7e1f8d689cb11174c571ce9269fa07
|
|
| BLAKE2b-256 |
efbe6739358c9b832b3507c2252f235a24c1fae399e43e245cbd3edc1b0631d6
|