MCP Tools Reference
BriefGate ships as an MCP server so coding agents can orchestrate client intake without leaving their context. All 13 tools map 1:1 to REST endpoints under https://api.briefgate.dev/v1/. Connect it without pasting a key — hosted OAuth or local device login — or with an API key for CI and scripts; see quickstart.md for all four options. login and logout are two additional local-only helpers available when you run the package via npx — see below.
When should an agent use BriefGate
Triggers. Reach for these tools when a task is blocked on something only a human outside the current conversation can provide: files (logo, product photos, documents), copy, brand assets, structured answers, or credentials/logins — and that person may take hours or days to respond, so something needs to chase them on a schedule instead of you polling by hand.
When not to. Skip it when the information is already available — in the repo, earlier in the conversation, or in an earlier BriefGate intake — or when the person you're talking to right now can just answer directly. BriefGate's portal and reminders are built for people who are not in this conversation.
Flow.
- Call
define_intakewith the typed items you need (see the item types table linked from each tool below). - Poll
get_intake_status. An item stayingpending, or the intake stayingsentorin_progress, is the normal state while BriefGate chases the client on the configured schedule — it is not an error or a sign the call failed. Usefollow_upon thedefine_intakeresponse, or a registered webhook, instead of tight polling. - Once items are submitted, call
get_intake_resultsto get typed values back. Itsmissingarray names anything still outstanding — check it before assuming the intake is done. - If a submitted value is wrong or incomplete, call
request_revisionwith a note explaining what to fix, instead of asking the client again yourself outside BriefGate. secretitems (passwords, API keys) come back throughget_intake_resultsonce: the plaintextvalueis present only on the first successful read (first_reveal: true) and absent on every call after. Store it immediately. Secrets are encrypted server-side on arrival (libsodium sealed box) — not end-to-end, not zero-knowledge — see security.md.
define_intake
Creates a new intake and immediately sends the client an email with their portal link.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
project_name |
string | Yes | Human-readable project title shown in the portal and all emails. |
client.email |
string | Yes | Client's email address. |
client.name |
string | Yes | Client's display name. Every invite and reminder email opens by addressing the client by name. |
client.language |
string | No | Portal and email language: cs, sk, pl, de, es, en. Omit it and the account's default language applies, falling back to English. |
client.timezone |
string | No | IANA timezone (e.g. Europe/Prague) used for quiet-hours scheduling. |
client.phone |
string | No | E.164 phone number (e.g. +420601123456). |
client.also_notify |
array | No | Up to 4 extra { email, name } people who get the same portal link and reminders as the primary client (e.g. two directors of one company). Each gets their own email; nobody sees the rest. |
email_copy |
object | No | Your own invite_subject, invite_intro, reminder_subject, reminder_intro, overriding the built-in translation for this intake. Placeholders: {sender}, {project}, {client}, {count}, {minutes}, {due} — an unknown one is rejected, not rendered literally. |
items |
array | Yes | Ordered list of item definitions. See item-types.md. |
items[].assignee |
string | No | client (default) or owner. An owner item is your own to-do — invisible to the client, never chased, and it never holds the intake open. See item-types.md. |
branding |
object | No | Per-intake override of account branding: logo_url, accent_color (hex, e.g. #1B2A4A), sender_name, reply_to. |
template |
string | No | Template slug to pre-populate items (e.g. "restaurant-website"). |
chase_schedule |
string | No | One of default, gentle, aggressive, custom, off (default: default). default=T+2d,T+5d,T+9d,weekly. gentle=T+3d,T+8d,biweekly. aggressive=T+1d,T+3d,T+5d,every-other-day. custom=every chase_interval chase_interval_unit. off=no auto reminders. |
chase_interval |
integer | No | Reminder interval for chase_schedule: "custom". Omit it and custom reminds every 3 days. Rejected with any other schedule. |
chase_interval_unit |
string | No | minutes, hours or days (default days). Minimum 5 minutes, maximum 90 days. |
respect_quiet_hours |
boolean | No | Keep reminders inside the client's 08:00–19:00 window (default true). |
max_reminders |
integer | string | No | Reminders before the intake stalls and hands back to you (1–1000, default 3, or "unlimited" to remove the cap). |
chase_at_time |
string | No | Local time of day for the reminder, HH:MM. Requires chase_schedule: "custom" with a whole-day interval, and outranks quiet hours. |
due_date |
string | No | ISO 8601 date. Shown in the portal and email subject. |
auto_approve_hours |
integer | No | Hours after submission before an item is auto-approved without your review (default 72). Set to 0 to require explicit approval. |
send |
boolean | No | Set to false to create a draft without emailing the client. Call POST /v1/intakes/:id/send to send later. Default: true. |
retention |
object | No | { mode: "days"|"on_delivery", days?: number, anonymize?: boolean }. Default: purge 90 days after intake.completed. Use mode: "on_delivery" for intakes containing credentials — contents are removed ~24 h after you call get_intake_results. |
folder_id |
string | No | Put this intake in an existing folder from list_folders instead of leaving it unfiled. Folders group intakes by client or project — reuse one for a returning client rather than creating a duplicate with create_folder. |
Example — restaurant website project
{
"project_name": "Bella Napoli — Website",
"client": {
"email": "owner@bellanapoli.com",
"name": "Marco Esposito",
"language": "en"
},
"items": [
{
"key": "logo",
"label": "Restaurant logo",
"help": "SVG or PNG with transparent background, minimum 512px.",
"type": "image",
"required": true,
"constraints": {
"formats": ["svg", "png"],
"min_width": 512,
"transparent_background": true
}
},
{
"key": "brand_colors",
"label": "Brand colors",
"help": "Pick the primary and secondary brand colors.",
"type": "color_list",
"required": true
},
{
"key": "hero_copy",
"label": "Hero tagline",
"type": "longtext",
"required": true,
"constraints": { "max_chars": 400 }
},
{
"key": "has_existing_site",
"label": "Do you have an existing website?",
"type": "boolean",
"required": true
},
{
"key": "existing_site_url",
"label": "Existing website URL",
"help": "Only required if you answered Yes above.",
"type": "url",
"required": false
},
{
"key": "platform",
"label": "Preferred platform",
"type": "select",
"required": true,
"options": [
{ "value": "wordpress", "label": "WordPress" },
{ "value": "webflow", "label": "Webflow" },
{ "value": "custom", "label": "Custom / I'm not sure" }
]
},
{
"key": "cms_password",
"label": "Admin credentials",
"help": "Existing CMS admin login (if migrating). Stored encrypted, one-time reveal.",
"type": "secret",
"required": false
},
{
"key": "opening_hours",
"label": "Opening hours",
"type": "structured",
"required": true,
"schema": {
"type": "object",
"required": ["mon_fri", "sat", "sun"],
"properties": {
"mon_fri": { "type": "string" },
"sat": { "type": "string" },
"sun": { "type": "string" }
}
}
}
],
"chase_schedule": "default"
}Response
{
"intake_id": "in_8f3kQmR2",
"portal_url": "https://p.briefgate.dev/8f3kqmr2",
"status": "sent",
"items": [
{ "key": "logo", "status": "pending" },
{ "key": "brand_colors","status": "pending" }
],
"follow_up": {
"recommended": "schedule",
"reason": "No active webhook endpoints are registered on this account.",
"webhook": {
"active_endpoints": 0,
"events": ["item.submitted", "intake.completed"],
"register_with": "manage_webhook"
},
"schedule": {
"check_with": "get_intake_status",
"every_hours": 24,
"until": "2024-03-29T10:22:00Z"
}
}
}follow_up tells you how to learn about progress on this intake without polling blindly. recommended is "webhook" when you already have an active webhook endpoint covering the relevant events, or "schedule" — as above — when you don't. On "schedule", poll get_intake_status every follow_up.schedule.every_hours hours until follow_up.schedule.until; on "webhook", register one with manage_webhook (or rely on the endpoints already listed under follow_up.webhook) instead of polling.
get_intake_status
Returns a lightweight snapshot of the intake's current state: which items are complete, which are pending, and the full chase history. Prefer this over get_intake_results when you only need to check progress.
progress.total counts what the intake is waiting on: an item the owner has waived, and an optional item the client has not touched, both leave the denominator. That keeps percent at 100 exactly when status turns completed.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
intake_id |
string | Yes | The intake_id returned by define_intake. |
Response
{
"status": "in_progress",
"due_date": "2024-04-01",
"client_last_seen": "2024-03-16T14:05:33Z",
"progress": {
"submitted": 5,
"total": 8,
"outstanding": 3,
"percent": 62
},
"items": [
{ "key": "logo", "status": "approved", "submitted_at": "2024-03-16T10:00:00Z", "label": "Restaurant logo" },
{ "key": "brand_colors", "status": "approved", "submitted_at": "2024-03-16T10:05:00Z", "label": "Brand colors" },
{ "key": "hero_copy", "status": "needs_revision", "submitted_at": "2024-03-16T10:10:00Z", "label": "Hero tagline" },
{ "key": "has_existing_site","status": "approved", "submitted_at": "2024-03-16T10:15:00Z", "label": "Do you have an existing website?" },
{ "key": "existing_site_url","status": "approved", "submitted_at": "2024-03-16T10:15:00Z", "label": "Existing website URL" },
{ "key": "platform", "status": "approved", "submitted_at": "2024-03-16T10:20:00Z", "label": "Preferred platform" },
{ "key": "cms_password", "status": "pending", "submitted_at": null, "label": "Admin credentials" },
{ "key": "opening_hours", "status": "pending", "submitted_at": null, "label": "Opening hours" }
],
"chases": [
{ "channel": "email", "sent_at": "2024-03-17T09:00:00Z", "status": "sent", "attempt_no": 1 },
{ "channel": "email", "sent_at": "2024-03-20T09:00:00Z", "status": "sent", "attempt_no": 2 },
{ "channel": "email", "sent_at": null, "status": "scheduled", "attempt_no": 3 }
]
}Item statuses
| Status | Meaning |
|---|---|
pending |
Not yet submitted by client. |
submitted |
Uploaded or filled in; awaiting agent review or auto-approval. |
needs_revision |
Agent requested a revision via request_revision. |
approved |
Accepted (explicitly or via 72-hour auto-approval). |
waived |
The owner waived the item — it will never be submitted and does not count toward progress.total. |
get_intake_results
Returns the typed content of all approved (or all submitted) items. Use this once the intake is complete, or incrementally as items are approved using only_new: true.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
intake_id |
string | Yes | The intake to retrieve results from. |
only_new |
boolean | No | If true, returns only items approved since the last call. Useful for streaming incremental processing. |
include_pending |
boolean | No | If true, also returns items that are submitted but not yet approved. |
Response
{
"intake_id": "in_8f3kQmR2",
"status": "completed",
"progress": { "submitted": 8, "total": 8, "outstanding": 0, "percent": 100 },
"missing": [],
"results": {
"logo": {
"url": "https://files.briefgate.dev/in_8f3kQmR2/logo.png?token=sig_abc&expires=1710615600",
"filename": "bella-napoli-logo.png",
"mime": "image/png",
"width": 1024,
"height": 512,
"size": 48210,
"checksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8"
},
"brand_colors": ["#1B2A4A", "#E8E2D9", "#C8382E"],
"hero_copy": "Since 1987, handcrafted Neapolitan pizza in the heart of the city. Come hungry, leave happy.",
"has_existing_site": true,
"existing_site_url": "https://old.bellanapoli.com",
"platform": "wordpress",
"cms_password": {
"value": "admin:s3cr3tP@ssw0rd",
"one_time": true,
"first_reveal": true,
"expires_at": "2024-04-15T10:22:00Z"
},
"opening_hours": {
"mon_fri": "12:00-22:00",
"sat": "12:00-23:00",
"sun": "13:00-21:00"
}
},
"meta": {
"logo": { "type": "image", "status": "approved", "submitted_at": "2024-03-16T10:00:00Z" },
"cms_password": { "type": "secret", "status": "approved", "submitted_at": "2024-03-16T10:15:00Z" }
}
}missing lists the keys of required client items still pending or needs_revision — useful to check before assuming the intake is complete. meta carries one entry per item returned in results: its type, status, submitted_at, and (for a decision item) decided_by.
Notes on specific types:
- Images and files —
urlis a signed URL with 24-hour validity. Fetch it promptly or re-request results to get a fresh URL. - Secrets — the
valuefield contains the decrypted plaintext on the first call only (first_reveal: true). On all subsequent callsvalueis absent. Store the secret immediately. Auto-expire after 30 days. Requires thesecrets:readoradminscope on the API key.
request_revision
Marks an item as needs_revision and sends the client an email notification explaining what needs to be fixed. The item returns to pending on the client's side, and the cycle restarts.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
intake_id |
string | Yes | The intake containing the item. |
item_key |
string | Yes | The key of the item to revise (e.g. "logo"). |
note |
string | Yes | Human-readable explanation sent to the client. Be specific. |
Example
{
"intake_id": "in_8f3kQmR2",
"item_key": "logo",
"note": "The logo appears to be exported at 320px. Please re-export at a minimum of 512px on the shortest side, ideally as an SVG vector file."
}Response
{
"intake_id": "in_8f3kQmR2",
"item_key": "logo",
"status": "needs_revision",
"client_notified": true
}The client receives an email with your note. Their portal highlights the flagged item. When they resubmit, the item transitions back to submitted and the 72-hour auto-approval timer resets.
send_chase
Manually triggers a chase message outside the automatic schedule. Use this to send a personal nudge before a deadline.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
intake_id |
string | Yes | The intake to chase. |
channel |
string | No | "email". Defaults to "email". |
Example
{
"intake_id": "in_8f3kQmR2",
"channel": "email"
}Response
{
"intake_id": "in_8f3kQmR2",
"channel": "email",
"sent_at": "2024-03-22T08:15:00Z"
}When to use this tool
- The deadline is tomorrow and the client has not responded to the automatic emails.
- You want to send a personal nudge outside the automatic schedule.
list_intakes
Returns a paginated list of intakes, optionally filtered by status or client email. Useful for building dashboards or checking which projects need attention.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
status |
string | No | Filter by intake status: draft, sent, in_progress, completed, archived. |
client_email |
string | No | Filter to intakes belonging to a specific client. |
folder_id |
string | No | Filter by folder, using an id from list_folders. Pass the literal string "none" to see only intakes that are not in any folder. |
q |
string | No | Free-text search: matches a substring of project name, client name, or client email. |
limit |
integer | No | Number of results per page (default 20, max 100). |
offset |
integer | No | Pagination offset (default 0). |
Response
{
"intakes": [
{
"intake_id": "in_8f3kQmR2",
"project_name": "Bella Napoli — Website",
"status": "in_progress",
"client": {
"email": "owner@bellanapoli.com",
"name": "Marco Esposito"
},
"portal_url": "https://p.briefgate.dev/8f3kqmr2",
"created_at": "2024-03-15T10:22:00Z",
"progress": { "submitted": 3, "total": 7 }
},
{
"intake_id": "in_4p2nWxY7",
"project_name": "Green Leaf Cafe — Rebrand",
"status": "completed",
"client": {
"email": "ops@greenleafcafe.com",
"name": "Priya Nair"
},
"portal_url": "https://p.briefgate.dev/4p2nwxy7",
"created_at": "2024-03-10T09:00:00Z",
"progress": { "submitted": 5, "total": 5 }
}
],
"total": 47,
"limit": 20,
"offset": 0
}Each intake in the array uses the same snake_case field shape as the single-intake object described elsewhere in these docs, plus a per-intake progress summary showing how many items have been submitted out of the total.
add_items
Appends new items to an existing intake that has already been sent. The client receives a notification that new items have been added. Use this when scope expands after the initial intake is live.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
intake_id |
string | Yes | The intake to extend. |
items |
array | Yes | Array of item definitions in the same format used in define_intake. |
Example — adding a favicon request after the initial intake was sent
{
"intake_id": "in_8f3kQmR2",
"items": [
{
"key": "favicon",
"label": "Favicon",
"help": "A square icon at least 32x32px, ideally 512x512px. Used in browser tabs and bookmarks.",
"type": "image",
"required": true,
"constraints": {
"formats": ["png", "svg"],
"min_width": 32,
"min_height": 32
}
}
]
}Response
{
"intake_id": "in_8f3kQmR2",
"items_added": [
{ "key": "favicon", "status": "pending" }
]
}The client's portal immediately shows the new item.
update_item
Changes the definition of an existing item on an intake — for example, tightening a constraint, editing the label or help text, or making an item required after the fact. The item's key cannot be changed; if you need a different key, use add_items to add a new item and leave the old one in place or discard it.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
intake_id |
string | Yes | The intake containing the item. |
item_key |
string | Yes | The key of the item to update. |
type |
string | No | New item type. See item-types.md. |
label |
string | No | New label shown to the client. |
help |
string | No | New help text. |
required |
boolean | No | Whether the item is required. |
constraints |
object | No | New constraints object, replacing the previous one. |
options |
array | No | New {value, label} options array, for select/multiselect items. |
pattern |
string | No | New regular expression, for text items. |
discard_submitted_value |
boolean | No | See "Conflicts with an existing answer" below. Default false. |
At least one field besides intake_id and item_key must be given. Neither schema nor owner_note can be set through this tool — the call is rejected if you pass either. assignee also cannot be changed by this tool — remove and re-add the item with add_items if it needs to move between client and owner.
Example — widening an image constraint after the client complained
{
"intake_id": "in_8f3kQmR2",
"item_key": "logo",
"constraints": {
"formats": ["svg", "png", "jpg"],
"min_width": 256
}
}Response
{
"item": {
"key": "logo",
"type": "image",
"assignee": "client",
"label": "Restaurant logo",
"help": "SVG or PNG with transparent background, minimum 512px.",
"required": true,
"status": "pending",
"value": null,
"constraints": {
"formats": ["svg", "png", "jpg"],
"min_width": 256
},
"options": null,
"pattern": null,
"submitted_at": null,
"approved_at": null,
"revision_note": null,
"revision_count": 0,
"waiver": null,
"client_note": null,
"owner_note": null
}
}Conflicts with an existing answer
If the client has already submitted a value for the item, and your change would invalidate that value under the new definition (e.g. narrowing max_chars below the submitted text's length, or removing an option the client already selected), the call fails with 409 and error code item_answer_would_be_discarded instead of silently dropping the client's answer:
{
"error": {
"code": "item_answer_would_be_discarded",
"message": "Changing constraints on \"logo\" would invalidate the client's submitted value."
}
}Pass discard_submitted_value: true to proceed anyway. In that case the item resets to pending and the response includes "discarded_submitted_value": true alongside item.
update_intake
Edits an intake that is already sent — the alternative used to be deleting it and calling define_intake again, which re-sends the invitation and hands the client a second link. Works on an intake in any status except archived; changing the chase cadence on a sent or in_progress intake re-plans the schedule from now, cancelling whatever reminder was still pending under the old settings. The client's e-mail address cannot be changed with this tool — it is what the portal link and magic token are bound to. Use manage_recipients to add, remove, or reinstate an address instead.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
intake_id |
string | Yes | The intake to edit. |
project_name |
string | No | |
due_date |
string | null | No | YYYY-MM-DD. null clears it. |
chase_schedule |
string | No | default, gentle, aggressive, custom, off. |
chase_interval |
integer | No | Only meaningful with chase_schedule: "custom". Pair with chase_interval_unit. |
chase_interval_unit |
string | No | minutes, hours, or days (default days). |
chase_at_time |
string | null | No | HH:MM local time. Needs an interval measured in whole days. null clears it. |
max_reminders |
integer | "unlimited" |
No | Raising this past the number of reminders already sent un-stalls an intake that had run out and resumes chasing. |
respect_quiet_hours |
boolean | No | |
client.name |
string | No | |
client.phone |
string | null | No | E.164, e.g. +420601123456. null clears it. |
client.language |
string | No | cs, sk, pl, de, es, en. |
client.timezone |
string | No | IANA timezone, e.g. Europe/Prague. Feeds respect_quiet_hours and chase_at_time — changing it also re-plans the schedule. |
folder_id |
string | null | No | Move this intake to a different folder, using an id from list_folders. null removes it from any folder. It never touches the chase schedule. |
No client.email — see above. At least one field besides intake_id is required. Omitting a field leaves it untouched; null is only accepted where the table above says so.
Example — switching a stalled intake to a faster, uncapped cadence
{
"intake_id": "in_8f3kQmR2",
"chase_schedule": "custom",
"chase_interval": 6,
"chase_interval_unit": "hours",
"max_reminders": "unlimited"
}Example — correcting the client's name and timezone
{
"intake_id": "in_8f3kQmR2",
"client": {
"name": "Marco Esposito",
"timezone": "Europe/Rome"
}
}Response is { "intake": { ... } }, the same object get_intake_status's sibling read tools return.
Errors
| Code | Meaning |
|---|---|
intake_archived (409) |
The intake is archived and cannot be edited. |
| 400 | No field besides intake_id was given. |
| 422 | A field's value is invalid — e.g. chase_at_time against an interval that isn't a whole number of days. |
manage_recipients
Adds, removes, or reinstates one of the people an intake is addressed to. An intake can have up to five recipients sharing one portal link — a primary client plus four others (two directors of the same company, for instance). Wraps POST/DELETE /v1/intakes/:id/recipients and the reinstate endpoint; see rest-api.md for the underlying REST contract.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
intake_id |
string | Yes | |
action |
string | Yes | add, remove, or reinstate. |
email |
string | Yes | The address to add, remove, or reinstate. |
name |
string | No | Only used with action: "add". |
add — includes the address in the same portal link and future reminders as the primary client. Sends nothing by itself; use send_chase to reach the new person immediately.
remove — drops an additional address. The primary client cannot be removed this way — use update_intake to change other fields, or define_intake to start over.
reinstate — for a bounce that was wrong: the person did get the e-mail, or the mailbox has since been fixed. Clears the bounce flag on an address that a delivery failure had silenced. If nobody was left to chase before this call, automatic reminders resume.
Example
{
"intake_id": "in_8f3kQmR2",
"action": "reinstate",
"email": "owner@bellanapoli.com"
}Response
{
"email": "owner@bellanapoli.com",
"bounced_at": null,
"still_chasing": true
}(add and remove return the shapes documented for their underlying REST endpoints instead — also_notify for add, an empty body for remove.)
Errors
| Code | Meaning |
|---|---|
recipient_exists (409) |
add: that address is already on the intake. |
400 (recipient_limit) |
add: the intake already has five recipients. |
recipient_is_primary (400) |
remove: that address is the primary client. |
| 404 | remove/reinstate: no such recipient on the intake. |
recipient_not_bounced (409) |
reinstate: that address has never bounced. |
manage_webhook
Creates, lists, or deletes webhook endpoints for your account. Webhooks let you react to intake events (an item submitted, an intake completed, a reminder bouncing, and so on) without polling. See webhooks.md for delivery, signature verification, and retry behavior.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
action |
string | Yes | "create", "list", or "delete". |
url |
string | For create |
HTTPS endpoint that receives the webhook POSTs. |
events |
array | For create |
Subset of item.submitted, intake.completed, client.viewed, chase.bounced, intake.stalled, intake.overdue. |
format |
string | No | "raw" (default, signed BriefGate envelope), "slack", or "discord". |
webhook_id |
string | For delete |
The endpoint to remove. |
url and events are both required for action: "create".
Example — registering an endpoint
{
"action": "create",
"url": "https://yourapp.com/webhooks/briefgate",
"events": ["item.submitted", "intake.completed", "intake.stalled"]
}Response (create)
{
"id": "whe_a1b2c3d4e5f6",
"url": "https://yourapp.com/webhooks/briefgate",
"events": ["item.submitted", "intake.completed", "intake.stalled"],
"format": "raw",
"active": true,
"created_at": "2024-03-22T08:15:00Z",
"secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}secret is shown exactly once, at creation — store it immediately, since you need it to verify signatures and it is never returned again (including by action: "list").
Response (list)
{
"webhooks": [
{
"id": "whe_a1b2c3d4e5f6",
"url": "https://yourapp.com/webhooks/briefgate",
"events": ["item.submitted", "intake.completed", "intake.stalled"],
"format": "raw",
"active": true,
"created_at": "2024-03-22T08:15:00Z"
}
]
}url is masked in the list response for slack/discord format endpoints, and shown in full for raw format.
Response (delete)
action: "delete" returns no body (204 No Content at the REST layer).
list_folders
Lists the folders in your account, used to group intakes by client or project. Call this before create_folder or before setting folder_id on define_intake, update_intake, or list_intakes — reuse an existing folder for a returning client instead of creating a duplicate.
Parameters
None.
Response
{
"folders": [
{
"id": "fld_a1b2c3d4",
"name": "Bella Napoli",
"sort_order": 0,
"intake_count": 3,
"created_at": "2024-03-10T09:00:00Z"
}
]
}create_folder
Creates a new folder to group intakes, e.g. one per client. Call list_folders first and reuse a matching folder — only create one when none of the existing folders fits.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Folder name, e.g. the client's or project's name. Must be unique in your account. |
Response
{
"id": "fld_a1b2c3d4",
"name": "Bella Napoli",
"sort_order": 0,
"intake_count": 0,
"created_at": "2024-03-22T08:15:00Z"
}Errors
| Code | Meaning |
|---|---|
folder_exists (409) |
A folder with this name already exists — use list_folders to find it instead. |
login
Signs in without pasting an API key — the local-package equivalent of Option 1 in quickstart.md. Not available when connected through the hosted endpoint (mcp.briefgate.dev); there, connecting the client already triggers OAuth. Takes no arguments.
This is a two-phase tool because approval can take minutes, longer than a single tool call should block for:
- The first call starts a device-authorization flow and returns immediately with a short code and a URL. Tell the client's user to open the URL and approve the code; a browser is also opened automatically where possible.
- Call
loginagain — any time, or once the user says they've approved it — to check progress. While it's still waiting, the response says so; once approved, that same call reports success and the key is saved to~/.briefgate/credentials.json. No restart needed — the very next tool call is signed in.
Has no effect, and says so instead of running the flow, if an API key is already configured via --api-key or BRIEFGATE_API_KEY — those always take priority over a locally stored one.
Parameters: none.
logout
Removes the API key login stored locally for this BriefGate server, and best-effort revokes it on the server too (a DELETE /v1/keys/current call using that same key). Not available when connected through the hosted endpoint. Takes no arguments.
If the revoke call fails (no network, API unreachable), the local copy is still removed; the response says so and points at the BriefGate dashboard to revoke it there instead.
Parameters: none.
Idempotency
Network errors can cause define_intake to be called twice, which would send the client two emails and create two intakes. The MCP package automatically derives a stable idempotency key from the project_name, client.email, and a hash of the items array — retrying with identical arguments returns the original response without creating a second intake.
When calling the REST API directly, pass an Idempotency-Key header:
curl -X POST https://api.briefgate.dev/v1/intakes \
-H "Authorization: Bearer bg_live_xxxxx" \
-H "Idempotency-Key: bella-napoli-2024-01" \
-H "Content-Type: application/json" \
-d '{ ... }'If the key was seen before, BriefGate returns the original response unchanged (HTTP 200, not 201). Keys expire after 24 hours.
Trying it without emailing a client
Pass "send": false to define_intake to create the intake as a draft without emailing the client — see the send parameter above. Nothing goes out until you call POST /v1/intakes/:id/send over REST; there is no MCP tool for sending a draft later. While it's still a draft, GET /v1/intakes/preview shows the exact subject line and sender name the client will get, using the account's current branding — it's session-only, so check it from the dashboard rather than from the agent.
Tool call example in Claude Code
When Claude Code invokes a BriefGate tool, the underlying MCP call looks like this:
{
"type": "tool_use",
"id": "toolu_01XyzAbc",
"name": "mcp__briefgate__define_intake",
"input": {
"project_name": "Bella Napoli — Website",
"client": {
"email": "owner@bellanapoli.com",
"name": "Marco Esposito",
"language": "en"
},
"items": [
{
"key": "logo",
"label": "Restaurant logo",
"type": "image",
"required": true,
"constraints": {
"formats": ["svg", "png"],
"min_width": 512
}
}
],
"chase_schedule": "default"
}
}The tool name is prefixed with mcp__ and the MCP server name (mcp__briefgate__). Claude Code handles the JSON serialization and transport automatically — you just describe what you need in natural language and the agent constructs the call.