handinloop-mcp-server (POC)
MCP server that lets AI agents — including Claude's scheduler/routines — submit document
extraction jobs to HandInLoop's human-in-the-loop platform and get back human-validated
results with per-field confidence and a full audit trail.
Async by design: submit returns a job_id; the agent polls status and fetches the result
when complete. This matches real human turnaround (minutes–hours) and Claude's scheduled
routines (submit on one run, collect on the next).
Tools
handinloop_list_task_types | Discover supported document types and their output fields |
handinloop_propose_fields | Given a document, propose the fields worth extracting — feed them straight into a custom extraction |
handinloop_save_task_type | Save a field set as a reusable task type — submit against its id later instead of re-sending fields |
handinloop_submit_extraction_job | Submit a document (URL or base64) for a built-in task_type or a custom fields set you describe — extract anything, human-verified |
handinloop_get_job_status | Poll: queued → in_review → completed / rejected |
handinloop_get_job_result | Fetch validated fields + line items + audit trail |
Install
Once published to npm, run it without a local checkout:
npx handinloop-mcp-server
Or add it to an MCP client (Claude Desktop / Claude Code):
{
"mcpServers": {
"handinloop": {
"command": "npx",
"args": ["-y", "handinloop-mcp-server"],
"env": { "HANDINLOOP_CLIENT": "mock" }
}
}
}
Set HANDINLOOP_CLIENT=http with HANDINLOOP_API_URL / HANDINLOOP_API_KEY to talk to the live API (see Backend wiring).
Quick start from source (mock mode — no backend needed)
npm install
npm run build
node scripts/smoke-test.mjs
Mock mode simulates the human lifecycle (5s queue + HANDINLOOP_MOCK_TURNAROUND_SECONDS,
default 20s) and returns realistic invoice/bank-statement results. Jobs are in-memory and
lost on restart.
Claude Desktop / Claude Code config
{
"mcpServers": {
"handinloop": {
"command": "node",
"args": ["/absolute/path/to/handinloop-mcp-server/dist/index.js"],
"env": { "HANDINLOOP_CLIENT": "mock" }
}
}
}
Demo prompt to try: "List the HandInLoop task types, submit https://example.com/invoice.pdf
as an invoice job, wait for it to complete, and give me the total with its audit trail."
Backend wiring (the real work)
Set HANDINLOOP_CLIENT=http plus HANDINLOOP_API_URL (base URL includes /v1) and
HANDINLOOP_API_KEY. src/httpClient.ts is wired to the live contract in
contract/openapi.yaml:
GET /task-types | { task_types: TaskType[] } |
POST /jobs | JobSummary (accepts SubmitJobRequest) |
GET /jobs/:id | JobSummary |
GET /jobs/:id/result | JobResult |
Auth is Authorization: Bearer <key> with a portal-issued API key — the portal's Google
OAuth session flow doesn't apply to machine clients. Issuing developer API keys is tracked
in #16; until it lands, use HANDINLOOP_CLIENT=mock (the default) for demos.
Field shapes live in src/types.ts; the MCP tool layer is transport-agnostic and stays
unchanged across mock/http.
Publishing (maintainers)
.github/workflows/publish-mcp.yml publishes on a GitHub Release, to two places:
- npm — via the
NPM_TOKEN repo secret (an npm "Automation" token; no local npm login).
- The official MCP registry (
io.github.lesofi/handinloop-mcp-server) — via GitHub
OIDC, which proves ownership of the io.github.lesofi/* namespace with no secret.
The registry links to the npm package via matching identifiers: package.json's mcpName
must equal server.json's name, and the version must match across package.json,
server.json, and the release tag (the workflow enforces this).
To release:
- Bump
version in both package.json and server.json (e.g. 0.1.1 → 0.1.2) and merge.
- Create a GitHub Release tagged
v<version> (e.g. v0.1.2).
- The workflow builds,
npm publishes, then publishes server.json to the MCP registry.
POC success criteria
- A scheduled Claude routine submits a real invoice, a human processes it in the crowd
portal, validated JSON comes back. Measure end-to-end turnaround.
- Five conversations with agent builders using the working demo. The demo exists to make
those conversations concrete — not to scale.
Out of scope (resist): billing, SLAs, webhooks, multi-tenant auth, remote HTTP
transport, the generic task UI. Any of these gets built only after demand shows up.