Skip to main content

Skill-first API framework: every endpoint is also an MCP tool

Project description

harnessapi

Write a skill. Get an API. Get an MCP tool. Ship.

PyPI version Python 3.11+ License: MIT


You write one Python function. harnessapi gives you:

  • A streaming HTTP endpoint at POST /skills/{name} — Server-Sent Events out of the box
  • An MCP tool at /mcp — plug straight into Claude Desktop, Cursor, Copilot, or any agent

No routers. No decorators scattered across files. No separate MCP server to maintain. Just a folder with two files.


Install

uv add harnessapi

60-second start

my_project/
├── main.py
└── skills/
    └── summarize/
        ├── models.py
        └── handler.py

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

"""Summarize text to a target length."""
from .models import Input, Output

async def handle(input: Input) -> Output:
    return Output(summary=input.text[:input.max_length])

main.py

from pathlib import Path
from harnessapi import HarnessAPI

app = HarnessAPI(skills_dir=Path(__file__).parent / "skills")
uvicorn main:app --reload

That's it. Your skill is live at POST /skills/summarize and available as an MCP tool at http://localhost:8000/mcp.


Streaming is the default

Return a value → one clean JSON response. Use yield → stream chunks to the client as they're produced.

# Non-streaming
async def handle(input: Input) -> Output:
    return Output(result=compute(input))

# Streaming — just yield
async def handle(input: Input):
    async for token in llm.stream(input.prompt):
        yield token

Clients get standard Server-Sent Events:

event: chunk
data: The answer is

event: chunk
data: 42.

event: done
data:

Need plain JSON instead? Add Accept: application/json to your request. Same endpoint, no configuration.


Every skill is also an MCP tool

Add harnessapi to Claude Desktop in 10 seconds:

{
  "mcpServers": {
    "my-skills": {
      "url": "http://localhost:8000/mcp"
    }
  }
}

Every skill you define automatically appears as a tool your agent can call. Add a skill folder, restart the server — it's there.


Try it: streaming factorial

Clone the repo and run the built-in example:

git clone https://github.com/edwinjosechittilappilly/harnessapi
cd harnessapi
uv sync
uv run uvicorn examples.factorial_app.main:app --reload

Watch 5! computed step-by-step over SSE:

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 get it all at once:

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 lives here
    ├── models.py         # required — Pydantic Input + Output
    ├── skill.toml        # optional — name, description, tags, timeout
    ├── defaults/
    │   └── input.json    # optional — shown as example in /docs
    └── examples/
        └── 01.json       # optional — {input, output} pairs for docs

skill.toml

[skill]
description  = "What this skill does"
is_mcp       = true      # default: true — set false to hide from MCP
tags         = ["nlp"]
timeout_secs = 30

Prefer decorators? That works too.

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)
async def translate(input: TranslateInput) -> TranslateOutput:
    # call your translation API here
    return TranslateOutput(translated=f"[{input.target_lang}] {input.text}")

app = HarnessAPI(title="My Skills")

Hot-swap handlers at runtime

Need to tweak a skill without restarting? Enable the edit endpoint and push new code over HTTP:

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):\n    return Output(summary=input.text.upper())", "persist": true}'

Disabled by default. Protect with auth middleware before exposing in production.


What you get out of the box

Feature Details
HTTP endpoint POST /skills/{name} for every skill
Streaming SSE by default, JSON fallback via Accept header
MCP server /mcp — all skills auto-registered as tools
OpenAPI docs /docs — full interactive Swagger UI
Pydantic validation Input validated before your handler is called
Timeouts Per-skill timeout_secs in skill.toml
Hot-swap Opt-in edit endpoint for runtime handler replacement

Philosophy

Most API frameworks start with routes. Most agent frameworks start with tools. harnessapi starts with skills — self-contained units of capability that are both, automatically.

Drop a folder. Define input and output. Everything else is handled.


Built on FastAPI · FastMCP · Pydantic · uv

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

harnessapi-0.1.1.tar.gz (112.4 kB view details)

Uploaded Source

File details

Details for the file harnessapi-0.1.1.tar.gz.

File metadata

  • Download URL: harnessapi-0.1.1.tar.gz
  • Upload date:
  • Size: 112.4 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

Hashes for harnessapi-0.1.1.tar.gz
Algorithm Hash digest
SHA256 307f0750041887607a164901192facb2a272f1d9965987b328eeac82db69cfdf
MD5 04d50d1ea79b149fe5fe4c494e6eab41
BLAKE2b-256 88bd99a604b6a978b0e705f7780ef06d8328dfcc86a913306821504fa6fbfcda

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page