Adding Configure to your agent
How to add Configure to an AI agent or agent product: which tools to give the model, how to route and answer their calls, and the habits that make an agent use memory well. Every Configure call your agent makes executes securely on the Configure server, gated by the permissions the user approved, and the user can revoke your agent's access at any time. This page is for the developer (or coding agent) wiring Configure into an agent; the full model-loop code lives in Handling Tool Calls.
All docs pages, indexed for agents: https://docs.configure.dev/llms.txt (add .md to any page URL for plain markdown).
The integration in one loop
Spread the Configure tools next to your own, route by name prefix, and feed results back. That is the whole integration:
ts
const profile = configure.profile({ token });
const tools = [...yourTools, ...profile.tools()];
// inside your model loop, for each tool call the model makes:
const result = call.name.startsWith("configure_")
? await profile.executeTool(call)
: await executeYourTool(call);profile.tools() returns Anthropic-shaped { name, description, input_schema } definitions; wrap them with toOpenAIFunctions() for OpenAI. profile.executeTool() accepts { name, input } or { name, arguments }, so pass the call through in whichever shape your provider produced. The user token stays server-side: the model sees tool schemas and tool results, never the token.
If your runtime speaks MCP instead, connect it to https://mcp.configure.dev and the same configure_* tools appear natively, with the same names and the same refusal behavior. Everything on this page transfers.
Support MCP in your product
Expose your product's own capabilities over MCP too. The loop above assumes your app owns the model, but users increasingly bring their own agent (Claude, ChatGPT, Cursor, a coding agent) and connect products to it. A product that speaks MCP meets that agent where it is: the user pastes one URL, and your product's tools sit next to Configure's in the same client. Neither side integrates the other. Configure ships this pattern itself: https://mcp.configure.dev is the live example, documented tool by tool in Configure MCP. Treat the MCP surface as first-class alongside your web UI.
The six default tools
With no options, profile.tools() returns exactly six tools:
configure_profile_read: the grounding read; identity, summary, top facts, connections, and a boxes index.configure_profile_search: a point lookup into concrete memories.configure_profile_remember: save one durable fact.configure_profile_forget: delete a memory this agent wrote.configure_profile_import: bulk-save a chunk of context as one distilled note.configure_connect: mint a sign-in or connect-an-app link. It only returns a URL; it never writes memory.
The model sees all six in every session: every profile.tools() result includes them regardless of options, and profile.executeTool() accepts them before any tools() call is made. Signed in or not, connected or not, the six default tools are in the model's tool list. configure_profile_commit is the one profile tool held back by default; enable it with advanced: { commit: true }.
Advertise everything, never pre-filter
Do not remove connector or action tools from the model's list because the user has not connected the app yet. Listing is not permission: what actually runs is enforced live, per call, by profile.executeTool() (linked state, connector state, permissions, scopes, approval state). A call against an unconnected app fails closed with an error that carries the recovery, so hiding the tool only removes the moment where the user learns they can connect it.
Enable the connector and action tools your product supports, once, at tools() time:
ts
const tools = profile.tools({
connectors: ["gmail", "calendar", "drive", "notion", "sheets"],
actions: ["email.send", "calendar.create_event"],
});The option you pass decides which tools appear (connectors: ["gmail"] adds configure_gmail_search; actions: ["email.send"] adds configure_email_send; the full table is in Handling Tool Calls). Call tools() with the same options you intend to execute: executeTool() rejects any name outside that recorded set with ACCESS_DENIED.
A refusal is an answer
When a Configure call fails, profile.executeTool() throws a ConfigureError whose suggestedAction field tells you what to do next. It is a closed enum, not free text: reauthenticate, fix_request, connect_tool, reconnect, retry, upgrade_plan, specify_agent, use_secret_key, use_hosted_auth, check_permissions, complete_approval, or commit_profile.
The one every agent hits: the model wants to send an email or search mail, and Gmail is not connected. The call fails closed with suggestedAction: "connect_tool" (never connected) or "reconnect" (a connection died). Both mean the same move: hand the user a connect link and stop. Do not retry, and never build the link yourself; configure_connect mints it:
ts
import { ConfigureError } from "configure";
try {
return await profile.executeTool(call);
} catch (error) {
if (
error instanceof ConfigureError &&
(error.suggestedAction === "connect_tool" || error.suggestedAction === "reconnect")
) {
// No live connection. Mint the link and put it in the reply; do not retry.
const link = await profile.executeTool({ name: "configure_connect", arguments: { app: "gmail" } });
return link; // carries connect_url + instructions for the model to relay verbatim
}
throw error;
}Route the other values the same way: check_permissions to your permissions surface, reauthenticate to sign-in, retry after retryAfter seconds on rate limits. On the hosted MCP server this is handled for you: the failed result carries a connect_url directly. Every code and field is in the Errors Reference.
Ground once, then search
Call configure_profile_read with no arguments once, before the agent's first substantive message, and fold the result into the work: tone, stack, preferences, goals. Do not re-read every turn; the read is bounded and returns a boxes table of contents plus a connections map for everything else.
For specific questions ("what does my ChatGPT know about my writing style", "what is the user's test runner"), use configure_profile_search instead of another full read. Never claim something about the user is not on file without searching first.
ts
// once, pre turn
const ctx = await profile.executeTool({ name: "configure_profile_read", arguments: {} });
// point lookups after that
await profile.executeTool({
name: "configure_profile_search",
arguments: { query: "writing style", source: "imports/chatgpt" },
});Save memory as it appears
When the user states something durable (a stable preference, a personal detail, an ongoing goal), save it at that moment with configure_profile_remember: one fact per call, silently. No "want me to save this?", no "saved to your profile" narration; deletes are the one visible write, confirmed in one line.
ts
await profile.executeTool({
name: "configure_profile_remember",
arguments: { fact: "Prefers weekly summaries on Friday" },
});For a whole chunk of context (a transcript, a long note, a multi-topic dump), use configure_profile_import instead of many remember calls; Configure distills it into one note and returns an import_id that can undo the whole import in one configure_profile_forget call. When the user says "forget that", call configure_profile_forget with the memory id from the save (reason: "user_request" suppresses the fact for good). Agents can delete only their own memories.
Commit at milestones
configure_profile_commit writes back what a turn learned, attributed to your agent. Two triggers:
- Milestones: on task completion and before long pauses, commit an honest one-line summary.
- Error
-32009(commit_required): a profile read left a pending obligation. Commit immediately, then retry the blocked call. This error is resolved only by commit, never byconfigure_connectand never by giving up on memory for the session.
Over the SDK the server-side form is profile.commit(); the model-facing tool exists behind advanced: { commit: true } for MCP and adapter runtimes.
Do and do not
Do:
- Advertise every supported tool in
[...yourTools, ...profile.tools(options)]and route by theconfigure_prefix. - Read
error.suggestedActionon every failure and route each value to its surface; treat a refused call as the answer it is. - Ground with one
configure_profile_readper session, then useconfigure_profile_searchfor point lookups. - Save durable facts the moment they appear, one fact per call, silently.
- Commit at milestones and on
-32009, then retry. - Include minted
connect_urllinks in the reply verbatim; a link is good for 30 minutes before approval, and the result'sexpires_atis the authority. - Rehearse the unhappy path in the sandbox before shipping (next section).
Do not:
- Pre-filter the tool list by connection state. Listing is not permission; enforcement happens live at execute time, and the refusal carries the fix.
- Retry a
connect_toolorreconnectfailure. There is no live connection; retrying cannot succeed. Hand over the link. - Construct sign-in or connect links yourself. Only the server mints them, through
configure_connect. - Pass user IDs or browser tokens to the model, or accept identity from tool arguments. Identity comes from the connected session token, resolved server-side.
- Re-read the whole profile every turn, or answer "not on file" without a search.
- Ask permission to save or narrate saves. The save is silent; the delete is confirmed.
Rehearse the unhappy path in the sandbox
Test mode gives you a synthetic user (already signed in, Gmail and Calendar connected, canned data) and a switch that breaks Gmail the way it breaks in production. Provision once per environment, then drive your refusal branch on purpose:
bash
curl -X POST https://api.configure.dev/v1/sandbox/provisionbash
# Make Gmail behave like a connection whose token died.
curl -X POST https://api.configure.dev/v1/sandbox/simulate \
-H "X-API-Key: $CONFIGURE_API_KEY" -H "X-Agent: $CONFIGURE_AGENT" \
-H 'Content-Type: application/json' \
-d '{"scenario":"gmail_token_expired"}'Gmail calls now fail closed (tool_not_connected, retryable: false). Watch your branch produce the connect link instead of retrying, then restore and run it again:
bash
curl -X POST https://api.configure.dev/v1/sandbox/reset \
-H "X-API-Key: $CONFIGURE_API_KEY" -H "X-Agent: $CONFIGURE_AGENT"The full sandbox walkthrough, including sign-in as the synthetic user, is Test mode.
How most agents use the tools
A healthy Configure session looks like this:
- One grounding
configure_profile_readbefore the first message; the first answer changes because of it. Read-heavy is normal: most sessions read and search far more than they write. configure_profile_searcha handful of times for point lookups, especially before preference-sensitive decisions (package manager, framework, test runner, commit style).- A few
configure_profile_remembercalls as durable facts surface, saved silently in the moment. configure_profile_importoccasionally, when the user hands over a transcript or a memory export instead of single facts.configure_connectexactly when a refused call says so: the link goes in the reply, the user connects, the next call succeeds.- A
configure_profile_commitat the end of significant work, and immediately on-32009.
Agents that hand work to other agents also save [handoff] notes to a shared box: "projects/<slug>" and read the freshest note back on pickup; see Projects.