Client Intake for Claude Code: Collect Assets Without Leaving Your Agent

The problem

Claude Code can build a complete website in an afternoon. Then it hits a wall: the client has not sent the logo, hero text, or their WordPress admin credentials. The agent waits. You wait. You send a follow-up email. Two weeks pass. The client sends a blurry JPG that is 200px wide.

This is the bottleneck: not the building, but the gathering. Developers lose more time chasing assets than writing code.

The solution

BriefGate MCP. Claude Code calls define_intake() with a list of what it needs. BriefGate emails the client a branded portal link. The client uploads files, fills in text, and submits credentials — all in a clean web interface designed for non-technical people. BriefGate chases them automatically on the schedule you set.

When everything is in, Claude Code calls get_intake_results() and picks up exactly where it left off — typed data, signed file URLs, structured objects, one-time credential values — ready to use without any parsing or transformation.

Setup

Three commands:

bash
npm install -g @briefgate/mcp
claude mcp add briefgate -- briefgate-mcp --api-key bg_live_xxxxx

Get your API key at https://app.briefgate.dev/app. The MCP server connects Claude Code to BriefGate's API. After setup, tools like define_intake, get_intake_results, get_intake_status, request_revision, and send_chase are available to Claude Code in every session.

Prefer not to paste a key? claude mcp add briefgate -- npx -y @briefgate/mcp followed by npx -y @briefgate/mcp login signs you in through the dashboard instead, or connect the hosted endpoint directly with claude mcp add --transport http briefgate https://mcp.briefgate.dev/mcp (then /mcp → briefgate → Authenticate inside Claude Code) for OAuth sign-in. See Quickstart.

Adding to CLAUDE.md

Put this snippet in your project's CLAUDE.md so the agent knows when and how to use BriefGate:

## Client asset collection
Use the briefgate MCP tools when you need assets from the client:
1. Call define_intake with items[] listing what you need
2. The client will be emailed a portal link automatically
3. Check status with get_intake_status or wait for the intake.completed webhook
4. Call get_intake_results to retrieve typed data and file URLs
5. For credentials (passwords, API keys), use type: "secret" — they are encrypted

Never ask the client directly for assets in conversation — use define_intake so
everything is tracked and chased automatically.

A complete session example

Here is what a Claude Code session looks like with BriefGate in place:

Agent: "I have the site architecture ready. To continue building, I need the client's logo, hero copy, and Google Analytics ID. Let me create a BriefGate intake now."

Agent calls define_intake:

json
{
  "project_name": "Bella Cucina Restaurant Website",
  "client": {
    "email": "marco@bellacucina.cz",
    "name": "Marco",
    "language": "en"
  },
  "items": [
    {"key": "logo", "type": "image", "label": "Restaurant logo", "constraints": {"min_width": 512}},
    {"key": "hero_copy", "type": "longtext", "label": "Homepage headline and intro text"},
    {"key": "ga4_id", "type": "text", "label": "Google Analytics 4 Measurement ID", "required": false, "pattern": "^G-[A-Z0-9]+$"}
  ],
  "chase_schedule": "default",
  "branding": {"sender_name": "Radim"}
}

Agent: "Intake created. Marco will receive an email at marco@bellacucina.cz with a portal link. I will continue building the static structure and placeholder components. I will check back when assets arrive."

[Agent continues building. Chase engine sends a reminder at T+2d. Marco submits the logo and copy at T+3d. The intake.completed webhook fires.]

Agent calls get_intake_results:

json
{
  "status": "completed",
  "items": [
    {
      "key": "logo",
      "type": "image",
      "status": "submitted",
      "value": {
        "url": "https://r2.briefgate.dev/signed/...",
        "filename": "bellacucina-logo.svg",
        "width": 1024,
        "height": 512,
        "mime_type": "image/svg+xml"
      }
    },
    {
      "key": "hero_copy",
      "type": "longtext",
      "status": "submitted",
      "value": "Authentic Italian cuisine in the heart of Prague..."
    },
    {
      "key": "ga4_id",
      "type": "text",
      "status": "skipped"
    }
  ]
}

Agent: "Logo received — SVG, 1024x512. Hero copy in. GA4 ID skipped (optional). Continuing with implementation: placing logo in header, populating hero section with copy, skipping analytics snippet."

Checking status without blocking

Besides waiting for a webhook, Claude Code can check progress at any point by calling get_intake_status:

json
{
  "status": "in_progress",
  "progress": {"total": 3, "submitted": 2, "outstanding": 1},
  "items": [
    {"key": "logo", "status": "approved"},
    {"key": "hero_copy", "status": "submitted"},
    {"key": "ga4_id", "status": "pending"}
  ]
}

A due_date on define_intake shows the client a deadline in the portal and the invite email. chase_schedule: "default" reminds them at T+2d, T+5d, T+9d, then weekly, until the intake completes or you turn the schedule off — the agent is never the one sending the follow-up, so it can keep building other parts of the project in the meantime instead of waiting idle.

Handling secrets

Credentials — WordPress admin, a Stripe secret key, a Google Analytics service account — need special handling. Use type: "secret" (Solo plan or higher; a Free-plan intake rejects the item with plan_required).

json
{
  "key": "wp_admin",
  "type": "secret",
  "label": "Current WordPress admin credentials",
  "help": "Username and password for wp-admin. Encrypted before storage, shown to your developer once."
}

The client's input travels over HTTPS and is sealed on BriefGate's server with a libsodium sealed box before anything is written to the database. Only the ciphertext is stored; the plaintext is held in memory just long enough to seal it and, later, to reveal it once.

When the agent calls get_intake_results, the secret item's entry in results carries the decrypted plaintext directly — but only the first time anyone reveals it:

json
{
  "wp_admin": {
    "value": "admin:MyPassword123",
    "one_time": true,
    "first_reveal": true,
    "expires_at": "2026-08-15T10:00:00Z"
  }
}

That value is gone the moment it has been read: call get_intake_results again and the item drops out of results entirely, with meta marking it secret_unavailable because it was already revealed. The agent should move the value into a secrets manager before doing anything else with the response — it is never available again through the API. If nobody collects it within 30 days it expires the same way; ask the client to resubmit with request_revision.

Requesting revisions

When the agent reviews uploaded assets and finds quality issues, it can flag the item and ask the client to resubmit:

request_revision(
  intake_id: "int_01J3K...",
  item_key: "logo",
  note: "The uploaded logo is 200px wide. We need at least 512px for retina screens. If you have an SVG version, that would be ideal — SVG scales to any size without quality loss."
)

The client receives an email, sees the note on the flagged item in their portal, re-uploads, and submits. The item.submitted webhook fires again. The agent calls get_intake_results and finds the updated file.

This keeps the revision loop within BriefGate — no separate email thread, no risk of the original submission being overwritten without a record.

Tips

Draft before sending. Pass send: false to define_intake to create the intake without emailing the client yet. Review the items, then release it with POST /v1/intakes/:id/send when you're ready — there's no MCP tool for sending a draft later, so that step goes over REST.

Process incrementally. Set only_new: true on get_intake_results to retrieve only the items approved since your last call. Useful in a webhook handler that should act on new submissions without re-processing the whole intake each time.

Know when items auto-approve. A submitted item auto-approves after auto_approve_hours (default 72) unless you review it first. Set it to 0 on define_intake if you want every item to wait for an explicit approval before it counts as done.

Reveal secrets once, immediately. As covered above, a secret item's plaintext comes back exactly once through get_intake_results — store it in your secrets manager in the same turn, not in a later session.