Skip to content
Capability hub / 001

Local-first capability hub · in development

Every tool your agents use, behind one MCP endpoint.

Put your MCP servers, APIs, credentials and skills in one hub. Claude Code, Codex and Cursor reach all of them through three tools, and write typed code instead of loading hundreds of tool definitions.

01search

Finds your tools by what you want done, and returns their addresses with one-line TypeScript signatures.

02describe

Gives the exact TypeScript types for the tools the model is about to call, within a token budget.

03execute

Runs the model's code in a sandbox where each source is a global of async functions. Nothing else is reachable.

  • Reads run. A tool that declares itself read-only runs without asking.
  • Writes ask. Everything else asks a person, and asks again if it changes upstream.
  • Only a person approves. No path the model can reach records a decision.
  • Secrets never reach the model. Keys stay in your keychain and are scrubbed from every result.
[01]The loop

Search, describe, execute. Then a person decides.

The model never loads your whole toolbox. It looks up what it needs, reads exact types, and writes one piece of code that chains the calls. When that code reaches a write, the run pauses until you answer.

  1. 1 · Find the tools

    search({ query: "open bugs, post to slack" })
    github  (connections: personal*, work)
      github.searchIssues(input: { q: string; perPage?: number; page?: number }): Promise<IssueSearchResult>
        Search issues and pull requests.
    slack  (connections: work*)
      slack.postMessage(input: { channel: string; text: string }): Promise<Message>   [asks]
        Post a message to a channel.
  2. 2 · Read the types

    describe({ targets: ["slack.postMessage"] })
    declare const slack: {
    	/** Post a message to a channel. */
    	postMessage(input: {
    		/** Channel name or id, e.g. "#eng". */
    		channel: string;
    		text: string;
    	}, options?: CallOptions): Promise<slack.Message>;
    };
  3. 3 · Run the code

    execute({ code })
    const bugs = await github.searchIssues({ q: "repo:acme/app is:open label:bug" });
    const top = bugs.items.slice(0, 3).map((i) => `• ${i.title} (#${i.number})`);
    await slack.postMessage({ channel: "#eng", text: top.join("\n") });
    return top;
    envelope
    {
      "runId": "01JD4Q8V…",
      "status": "awaiting_approval",
      "calls": [{ "address": "github.searchIssues", "ok": true, "ms": 412 }],
      "pending": [{
        "kind": "approval",
        "address": "slack.postMessage",
        "summary": "Post a message to #eng",
        "message": "An approval page opened on the user's screen. Run `capabilities hub approve 01JD4Q8V…` if it did not."
      }]
    }

Formats from the design spec. The surface ships with the local hub (M1); wording changes only with eval evidence.

[02]Control

You decide what runs. The model never does.

Policy decides every call: allow, ask or deny, per source or per tool. Approving pins the tool's contract, so a tool that changes upstream asks again and says why.

P/01Reads run
A tool that declares itself read-only runs without asking.
P/02Writes ask
Everything else asks a person, and asks again if it changes upstream.
P/03Only a person approves
No path the model can reach records a decision.
P/04Secrets never reach the model
Keys stay in your keychain and are scrubbed from every result.
Security and approvals
Approval neededexpires in 23 h

slack.postMessage

Post a message to a channel. · connection work

{
  "channel": "#eng",
  "text": "• Login fails on Safari (#412)\n• …"
}

Why it asks: writes ask by default.

Allow onceAllow for this runAlways allowDeny
Illustration · the local approval page is planned for M1 (LH-15)
[03]Every agent

Set it up once. Every harness sees the same hub.

The importer reads what your agents already have, you review it on one screen, and one command points them all at the hub. The target is under two minutes.

  1. 01 · Import

    $ capabilities hub init --import

    Reads Claude Code, Codex, Cursor, Antigravity CLI, Hermes and pi. Proposes a namespace per server and offers to move plain-text keys into your keychain.

  2. 02 · Review

    $ capabilities hub review

    One screen groups tools as read-only, writes and destructive. One keystroke accepts and pins each contract.

  3. 03 · Connect

    $ capabilities hub connect

    Writes one capabilities entry into Claude Code, Codex, Cursor and Claude Desktop. Read-only harnesses get exact instructions.

Planned for M1 · LH-13, LH-14, LH-15

Setup snippets for each agent
[04]Local first

Free on your machine. Hosted when you need the cloud.

Local hub

In progress · M1
  • Runs on your machine with capabilities hub serve
  • Reaches agents over stdio or loopback HTTP
  • Keys in your OS keychain, data in ~/.capabilities/hub
  • Free, and open source under MIT when it ships

Hosted hub

Planned · M3
  • The same engine on Cloudflare, at hub.capabilities.sh/mcp
  • Reaches claude.ai, ChatGPT and phones, behind OAuth
  • A credential vault, OAuth that refreshes while your laptop sleeps, run history, approvals from your phone
  • The paid product; pricing is not decided
[05]Status

Built in the open, one verified unit at a time.

The local hub ships as 20 pull requests. As of 2026-11-06, 11 are merged and 1 are in review. Nothing is released yet, and every page here says what is planned.

give your agent the docs
curl -s https://capabilities.sh/llms.txt