Skip to content

Agent tools

Jaloliddin Ismailov edited this page Oct 8, 2026 · 12 revisions

The Hub ships a first-class interface for AI agents: an MCP (Model Context Protocol) service that lets an LLM read catalogues, draft content, submit opportunities and work review queues — through the same HTTP API and the same permission checks a human member uses. An agent on this system is a member with a token, nothing more. That constraint is the design.

The architecture

flowchart LR
    subgraph Your side
        Agent["An LLM agent<br/>(Claude, GPT, your own)"]
    end

    subgraph MCP["mcp-content — separate process"]
        Stdio["stdio transport<br/>scripts/agent/hub-mcp.mjs<br/>(+ session-memory tools)"]
        HTTP["HTTP service<br/>mcp-content/server.mjs · /mcp"]
        Srv["one shared server factory<br/>lib/mcpServer.mjs"]
        Stdio --> Srv
        HTTP --> Srv
    end

    subgraph Hub["The Hub — unchanged"]
        API["/api/* endpoints"]
        Access["access engine"]
        DB[(PostgreSQL)]
        API --> Access --> DB
    end

    Agent -->|JSON-RPC| Stdio
    Agent -->|JSON-RPC over HTTP| HTTP
    Srv -->|"HUB_TOKEN — member credentials"| API
Loading

Two transports, one implementation: lib/mcpServer.mjs builds the tool registry once, and both scripts/agent/hub-mcp.mjs (stdio, for agents running alongside a CLI) and mcp-content/server.mjs (HTTP, for remote agents) wrap it. The stdio entry additionally attaches the shared session-memory tools (lib/memory.mjs, when HINDSIGHT_API_KEY is set) so local agents configure a single hub server; the HTTP service serves content tools only — memory is session state for this project's maintainers, and attaching it remotely would land other consumers' recall in our bank.

The two rules that make this safe:

  1. The MCP is a client, not a component. It runs in its own process, authenticates to the Hub with HUB_TOKEN (a member account), and is never given DATABASE_URL. Everything it can do, a member holding that account could do in a browser — including being denied.
  2. Writes keep their guardrails. When an agent drafts an announcement it goes through create_content_draft → review → publish like anyone else's. The audit trail records the agent's account as the actor, so "the agent published X" is answerable with the same fidelity as "Priya published X".

The tools

The service exposes 18 tools across three jobs:

Identity & context

Tool Purpose
whoami Which Hub account this MCP is acting as
catalog_options Enumerations the tools need — scopes, kinds, categories

Opportunities & resources — the submission side of the content pipeline:

Tool Purpose
list_opportunities / list_org_opportunities Read the catalogue, own and org-scoped
create_opportunity / withdraw_opportunity Submit / retract
list_opportunity_review / review_opportunity The review queue (needs reviewer capability)
submit_resource / list_my_resource_submissions Offer material to the shared collection

Content — the full draft pipeline:

Tool Purpose
list_content / get_content Read live documents
update_content Patch — via the draft pipeline or apply-now with the capability
unpublish_content Take a document down
create_content_draft → submit_content_draft → review_content_draft → publish_content_draft The governed write path

Running it

HTTP (remote agents, the common case):

cd mcp-content
HUB_TOKEN=<a hub account token> MCP_TOKEN=<a shared secret for callers> \
  node server.mjs            # listens on :18080

Callers then hit /mcp with JSON-RPC, passing the MCP_TOKEN as a bearer or in the URL. /healthz answers for load-balancer checks. MCP_ALLOW_ANONYMOUS=1 drops the token requirement for local development only.

stdio (agents running on your machine, e.g. wired into an agent config):

{
  "hub": {
    "command": "node",
    "args": ["scripts/agent/hub-mcp.mjs"],
    "env": { "HUB_ORIGIN": "https://hub.example", "HUB_TOKEN": "…", "HINDSIGHT_API_KEY": "…" }
  }
}

A session, verbatim

// initialize → returns the server card
{"jsonrpc":"2.0","id":1,"method":"initialize",
 "params":{"protocolVersion":"2024-11-05","capabilities":{},
           "clientInfo":{"name":"example","version":"0.1"}}}

// initialized — a notification, gets an empty 202
{"jsonrpc":"2.0","method":"notifications/initialized"}

// tools/list → all 18 tools with schemas
{"jsonrpc":"2.0","id":2,"method":"tools/list"}

// tools/call → the real work
{"jsonrpc":"2.0","id":3,"method":"tools/call",
 "params":{"name":"create_content_draft",
           "arguments":{"kind":"announcement","title":"…","body":"…"}}}

CI proves this handshake on every PR — scripts/ci/mcp-smoke.mjs boots the server, runs exactly this exchange, and asserts the 18 tools are listed. If the tool surface changes, the smoke test is the contract that notices.

What agents deliberately cannot do

  • Bypass review. No apply-now without the capability a human publisher would need.
  • Touch the database. No DATABASE_URL, ever — the HTTP API is the whole surface.
  • Approve their own work. review_* needs the reviewer capability on the acting account — an agent account holding it is an operator decision, not a default.
  • Read what a member can't. Every read goes through the access engine; restricted project material needs an explicit scoped grant, same as for people.

The governing document for all of this is AGENTS.md — its invariants (no broad apply-now reuse, citations on proposal writes, independent human approval before canonical records change) are the contract any future agent capability is reviewed against.

Agents working on the Hub

Separately from the content service, the repo ships the tooling maintainers' own agents use:

Skills — .agents/skills/ carries the non-negotiables (hub-invariants first) plus domain recipes (hub-endpoints, hub-authority, hub-member-ui, hub-data, hub-mcp) and the shared neon-*, ponytail-*, and typesafe-ai sets. After cloning, restore them into your agent's skill directories:

npx skills experimental_install   # reads skills-lock.json

Session memory — the project-scoped Hindsight bank youngo-hub gives agents shared recall/retain of decisions and findings across sessions. It ships inside the hub stdio server described above — lib/memory.mjs proxies the bank's MCP tools through it — so there is one server entry, not two. It is deliberately not in global agent config: project knowledge must not leak into other projects' sessions. To wire it into your local tools:

# .env.local
HINDSIGHT_API_KEY=hsk_…          # ask a maintainer
npm run sync:mcp                  # writes .mcp.json, .zed/settings.json,
                                  # and .agents/tool-cfg/{devin,cursor,codex}

Every output path is gitignored — the script is the only place the wiring is defined, so never hand-edit its outputs; rerun it after a key rotation. Restart your agent to pick the config up, then hub exposes the content tools plus the Hindsight surface (retain, recall, reflect, …). The run needs HINDSIGHT_API_KEY, Hub credentials, or both — the server enables whichever side it has credentials for.

Troubleshooting

Symptom Cause Fix
[hub-mcp] dependencies not installed (or a raw ERR_MODULE_NOT_FOUND) Fresh clone — node_modules missing npm ci in the repo root
[hub-mcp] content credentials: missing at boot No HUB_* in .env.local Add HUB_TOKEN or HUB_EMAIL+HUB_PASSWORD, rerun npm run sync:mcp
[hub-mcp] memory tools unavailable HINDSIGHT_API_KEY unset, revoked, or the bank unreachable Check the key; rerun npm run sync:mcp
A remote MCP dies at startup with Unauthorized OAuth token expired (hosted GitHub/Supabase-style servers) Re-authenticate in the client, or prefer a personal access token — project servers use API keys precisely to avoid this

Boot diagnostics go to stderr, so look in your agent's MCP logs first — the server announces its credential status on every start.

Clone this wiki locally