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
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
- First call returns a
jobIdwithout waiting for completion - Subsequent calls with the same
jobIdreturn updated progress - With a persistent store, the same
jobIdsurvives restarts
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.