Skip to main content

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

harnessapi-0.1.0.tar.gz (97.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

harnessapi-0.1.0-py3-none-any.whl (13.0 kB view details)

Uploaded Python 3

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

Hashes for harnessapi-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e9ac5b659da328ccb127b8ea8ceccafd1e40f335020832a73f85e6e8e120190a
MD5 f95a704111ec3d05e815f4ecead51c32
BLAKE2b-256 6c33516cf92bbb413e6e25e2813f43538ec0e63bf0b389fa92529984a4582a13

See more details on using hashes here.

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

Hashes for harnessapi-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d5f5f4c9a71b34ef1086dfc1ba9f15e461d01651fad4b7fc586150b068a4d6ca
MD5 d7f4e0f812786e07da7076b3b7e64770
BLAKE2b-256 cd464ed718a568ba61d6f307851301e5ab7cfb76ac4fd4a4e06366fff380e583

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