Skip to content
Developers

Give your agent a seat in Harbor.

Harbor ships an MCP server, so Claude or any MCP client can read Brand Memory, check best times, draft and propose posts. Proposals wait in Approvals like everything else.

MCP server

One endpoint, 23 tools.

Endpoint
POST /mcp on the Harbor API, over Streamable HTTP. Stateless: every request stands alone, so there are no sessions to keep alive.
Auth
Authorization: Bearer hbr_…. Create a key in Harbor under Settings → API keys.
stdio
For clients that only speak stdio, pnpm --dir apps/api mcp:stdio relays every message to /mcp with HARBOR_API_URL and HARBOR_API_KEY. Keep the API running while you use it.
Resource
harbor://brands/{brandId}/memory: a brand’s Brand Memory as JSON.
Results
Every tool result says which mode Harbor runs in, like (sandbox · simulated model), so an agent knows when nothing is real.
claude · harbor mcp
$claude
>Plan next week for Tidewater Coffee on Instagram and Threads. Don’t post anything.
○list_brandsread⎿ 2 brands (sandbox · simulated model)
○get_brand_memory { brandId: "brd_tide…" }read⎿ Brand Memory for Tidewater Coffee (sandbox · simulated model)
○best_times { platform: "instagram" }read⎿ Best times for Tidewater Coffee on instagram (sandbox · simulated model)
·Sunday 7pm is the strongest Instagram slot. Running the weekly plan.
○run_agent { agentId: "weekly_plan", inputs: { brandId, platforms } }write⎿ weekly_plan succeeded: Planned 6 posts across Instagram, Threads; 6 waiting for approval. (sandbox · simulated model)
○list_approvals { status: "pending" }read⎿ 6 pending, 0 approved, 0 rejected (sandbox · simulated model)
✓Six posts are waiting in Harbor’s Approvals. Nothing was published or scheduled.
Claude Code
terminal
claude mcp add --transport http harbor http://localhost:4800/mcp \
  --header "Authorization: Bearer hbr_your_key"
Claude Desktop and other stdio clients
claude_desktop_config.json
{
  "mcpServers": {
    "harbor": {
      "command": "pnpm",
      "args": [
        "--dir",
        "/path/to/harbor/apps/api",
        "mcp:stdio"
      ],
      "env": {
        "HARBOR_API_URL": "http://localhost:4800",
        "HARBOR_API_KEY": "hbr_your_key"
      }
    }
  }
}

Harbor’s Settings → API keys page shows these same snippets with your API’s real URL, the path to your checkout and a freshly created key filled in.

Decision 0003

Agents propose, people publish.

There is a propose_post tool. There is no schedule_post and no publish.

The write scope stops at proposing: an approval with the draft, the account, a slot, the reason and the voice-check results.

A person approves, edits or rejects it in Harbor. Every MCP run is recorded with its source and the key that made it.

Why Harbor never posts without you →
Tools

Everything an agent can do.

The full list, as the server defines it. Read tools look; write tools create ideas, drafts, notes and proposals.

read14 tools · needs the “read” scope
  • list_brands()

    List the brands in this workspace with their connected channels.

  • get_brand_memory({ brandId })

    A brand’s Brand Memory: voice (summary, do, avoid, banned words), audiences, products, knowledge, style rules and example posts.

  • list_posts({ brandId, status?, limit? })

    A brand’s posts, newest first, with their latest metrics and verdict against the brand’s median.

  • get_post({ postId })

    One post with its outcome (verdict against the median), its growth curve (snapshots) and the baseline curve.

  • get_digest({ brandId })

    The brand’s latest weekly digest: headline, stats, wins, misses, patterns and recommendations.

  • best_times({ brandId, platform? })

    The best posting slots (weekday 0 = Sunday, hour 0-23, server local time), scored 0..1, for one platform or all of the brand’s platforms.

  • list_campaigns({ brandId? })

    Campaigns, newest first, optionally for one brand.

  • list_ideas({ brandId, status? })

    A brand’s ideas, most recently updated first, optionally by status (idea, drafting, approved, scheduled, live).

  • list_approvals({ status?, brandId? })

    The approval queue: posts agents proposed, each with its draft, slot, reason and voice checks. Pending first.

  • list_memory_proposals({ status?, brandId? })

    Brand Memory changes agents proposed (the voice refresh): each change with why, its lift and the posts behind it. Pending first. A person applies them in Harbor.

  • get_conversation({ brandId, days? })

    What people say in replies to a brand’s posts, grouped into themes (questions, objections, praise, requests), largest first.

  • list_media({ brandId, tag? })

    A brand’s media library, newest first: images and videos with alt text, tags, focal point and the public URL of each crop. Attach one to a draft with attach_media.

  • list_agents()

    Harbor’s built-in agents: what each does, its inputs and its steps. Run one with run_agent.

  • get_agent_run({ runId })

    One agent or model run with its inputs, steps, child runs and the approvals it queued.

write9 tools · needs the “write” scope
  • create_idea({ brandId, title, angle?, notes? })

    Add an idea to a brand. It starts in the Idea column.

  • suggest_ideas({ brandId, count? })

    Suggest ideas for a brand from its signals (post performance, audience pain points); each cites a signal.

  • draft_idea({ ideaId, platforms })

    Draft an idea for one or more platforms (x, linkedin, instagram, threads). Each draft is a new version.

  • propose_post({ draftId, proposedFor?, reason? })

    Propose a draft for posting. Creates a pending approval (a person approves, edits or rejects it); nothing is published or scheduled. Without proposedFor it takes the next free best-time slot on the channel.

  • attach_media({ draftId, assetId, variant? })

    Attach an image or video from the brand’s media library to a draft’s first part. Saves a new draft version; the crop defaults to the platform’s preferred one.

  • run_agent({ agentId, inputs })

    Run one of the built-in agents (see list_agents) with its inputs. Agents never publish; posts they want out become approvals.

  • add_knowledge({ brandId, title, body })

    Add a note to a brand’s Brand Memory knowledge (facts, positioning, FAQs the drafts should know).

  • save_to_memory({ brandId, from, as, text?, note?, title? })

    Save a post, reply, reply theme, signal or draft to a brand’s Brand Memory as a knowledge note, an excerpt, an example post, or a do / avoid rule. Rules link back to their source as evidence.

  • add_canvas_note({ campaignId, text })

    Put a sticky note on a campaign’s canvas, to the right of everything on it.

Agents run_agent can start
weekly_planWeekly planReads last week, finds open best-time slots, suggests ideas, drafts them per platform, runs the voice check and queues approvals.
repurpose_winnerRepurpose a winnerTakes a published post that did well and drafts it for your other platforms.
voice_checkVoice checkChecks a draft for banned words, length, hashtags, media and an empty hook. No model call.
digestWeekly digestWins, misses, patterns and recommendations, each citing posts and signals.
voice_refreshVoice refreshCompares winners with the rest and proposes Brand Memory changes with their lift and evidence.
REST API

The API the app is built on.

Harbor’s web app is a client of the /v1 API, so anything you can do in the app, you can do with curl. JSON in, JSON out.

/v1 is local for now.

The REST API acts as you, so it can publish. Until Harbor has user accounts, it only answers on the machine it runs on. Requests through a tunnel or a public hostname only reach OAuth callbacks, /media, /mcp and /health. Agents use the MCP server and an API key instead.

Behind your own auth, HARBOR_PUBLIC_API=all turns the gate off.

localhost
curl -X POST localhost:4800/v1/brands \
  -H 'content-type: application/json' \
  -d '{"name":"My Brand"}'

# Errors always look like this:
{ "error": { "code": "NOT_FOUND",
             "message": "…",
             "request_id": "…" } }
/v1/brands

Brands and their Brand Memory: voice, audiences, offers, knowledge.

  • GET /v1/brands
  • POST /v1/brands
  • GET · PATCH · DELETE /v1/brands/:id
  • GET /v1/brands/:id/best-times
  • POST /v1/brands/:id/memory/save
  • POST /v1/brands/:id/memory/preview
/v1/ideas

The Idea → Drafting → Approved → Scheduled → Live pipeline.

  • GET · POST /v1/ideas
  • POST /v1/ideas/generate
  • PATCH · DELETE /v1/ideas/:id
/v1/drafts

Per-platform drafts with versions, generation and rewrites.

  • GET · POST /v1/drafts
  • GET /v1/drafts/:id
  • POST /v1/drafts/generate
  • POST /v1/drafts/:id/rewrite
/v1/posts

Publish now or schedule, with metrics, verdicts and replies.

  • GET · POST /v1/posts
  • GET · PATCH · DELETE /v1/posts/:id
  • POST /v1/posts/:id/resolve
  • GET /v1/posts/:id/replies
/v1/approvals

What agents proposed: approve, edit, reject, or do it in bulk.

  • GET /v1/approvals
  • POST /v1/approvals/:id/approve
  • POST /v1/approvals/:id/reject
  • POST /v1/approvals/bulk
/v1/agents

The built-in agents and their runs, step by step.

  • GET /v1/agents
  • GET /v1/agents/:id
  • POST /v1/agents/:id/runs
  • GET /v1/agent-runs/:id
/v1/automations

Agents on a schedule or after a post event, with a daily cap.

  • GET · POST /v1/automations
  • GET · PATCH · DELETE /v1/automations/:id
  • POST /v1/automations/:id/run
/v1/insights

Verdicts against the median, digests and the cross-brand overview.

  • GET /v1/insights/overview
  • GET · POST /v1/brands/:id/digest
  • POST /v1/brands/:id/outcomes
  • GET /v1/brands/:id/signals
/v1/media

Uploads, focal points and per-platform crops at public URLs.

  • GET · POST /v1/brands/:id/media
  • POST /v1/brands/:id/media/from-url
  • GET · PATCH · DELETE /v1/media/:id
  • GET /v1/media/:id/fit

Also: channels and the connect flow, campaigns and their canvas, memory proposals, API keys, GET /v1/mode (sandbox or live, simulated or real model) and the sandbox clock. Each response carries a Harbor-Request-Id header. Full reference in the repo README.

API keys

Two scopes. Neither can publish.

  • A key is hbr_ plus 32 random base62 characters.
  • Harbor stores only its SHA-256 hash and the first 12 characters, for display. The full key is shown once, when you create it.
  • Each key records when it was last used. Revoke one at any time and it stops working immediately.
  • A call without the right scope fails with a message saying which scope it needs and which ones the key has.
read

List brands, posts, ideas, campaigns, media, agents and approvals. Read Brand Memory, digests, best times, conversation themes and agent runs.

write

Create and suggest ideas, draft them, attach media, add knowledge and canvas notes, save to Brand Memory, run agents and propose posts.

neither

Publish, schedule, approve, delete or connect accounts. Those stay with a person in the app.

API keys
GET    /v1/api-keys
POST   /v1/api-keys   { "name": "Claude Code", "scopes": ["read", "write"] }
DELETE /v1/api-keys/:id
Get started

Point Claude at your own Harbor.

Run Harbor locally in sandbox mode, create a key and connect Claude Code in one command. Nothing it proposes goes out until you approve it.