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:
npm install -g @briefgate/mcp
claude mcp add briefgate -- briefgate-mcp --api-key bg_live_xxxxxGet 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:
{
"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:
{
"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:
{
"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).
{
"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:
{
"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.