Repository navigation
Agent tools
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.
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
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:
-
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 givenDATABASE_URL. Everything it can do, a member holding that account could do in a browser — including being denied. -
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 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 |
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 :18080Callers 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": "…" }
}
}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.
- 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.
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.jsonSession 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 maintainernpm 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.
| 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.