Core Concepts

Handles

Return a handle immediately and poll job status across stateless requests.

bitmcp is stateless. There is no Mcp-Session-Id and no sticky server memory.

Mint a handle in execute

ingest/tool.ts
import { memoryHandleStore, type HandleStore } from "bitmcp";

type Job = { status: string; progress: number; source: string };
export const jobs: HandleStore<Job> = memoryHandleStore();

async execute({ jobId, source }, ctx) {
  const id = jobId ?? ctx.handle();
  const existing = await jobs.get(id);
  if (!existing) {
    await jobs.set(id, { status: "running", progress: 0, source });
  }
  const job = (await jobs.get(id)) ?? { status: "running", progress: 0, source };
  return { jobId: id, ...job };
}

Poll from the View

Return a handle immediately. Let the View poll status via a backing action. Each poll is a new stateless tools/call.

Verify

  1. First call returns a jobId without waiting for completion
  2. Subsequent calls with the same jobId return updated progress
  3. With a persistent store, the same jobId survives restarts
Start ingest from warehouse
Reply to Claude…

Reference

ctx.handle()

Mints an opaque id (UUID). Store state with a HandleStore:

  • memoryHandleStore() in development
  • KV, Redis, or D1 in production

The model and View pass jobId back as a normal tool argument.

HandleStore

type HandleStore<T> = {
  get: (id: string) => Promise<T | undefined>;
  set: (id: string, value: T) => Promise<void>;
};

Every HTTP request builds a fresh MCP server. Handles make cross-call state safe across replicas.

For the optional MCP tasks extension (resultType: "task"), see Package exports.