Skip to content

Connected apps and actions ​

When a user connects an app on Configure's page, its tools appear on their MCP session. You do not have to do anything. This page lists the tools, marks the ones that change external state, and shows how to limit what your agent can call.

The tools ​

A user's MCP session has seventeen tools: configure_connect, eight profile tools, and the eight connected-app tools below. Every session lists all of them, whether or not the user connected the app. If the user has not authorized a tool, the call fails closed and returns the link that fixes it.

ReadsChanges external state
configure_gmail_searchconfigure_email_send
configure_calendar_getconfigure_calendar_create_event
configure_drive_search
configure_notion_search
configure_sheets_search, configure_sheets_read

These tools are on the SDK and REST only, not on the MCP session:

  • configure_email_search, which searches Gmail and Outlook together.
  • The Sheets writes: configure_sheets_values_update, configure_sheets_values_append, configure_sheets_create_spreadsheet, and configure_sheets_add_sheet.

On the SDK path, profile.tools() returns 14. It leaves out configure_profile_commit and configure_project_share, which the MCP session carries. These are always included: configure_profile_read, configure_profile_search, configure_profile_remember, configure_profile_forget, configure_profile_import, and configure_connect.

Scope what your agent may do ​

On the Quickstart path, the model gets these tools through the MCP session (step 7) and calls them itself. The session sets the scope. A tool the user has not authorized refuses and returns the link that fixes it.

You can also run the calls yourself. Use a handle that carries the user's own token, from Sign in with Configure. Then you choose which tools the model sees:

ts
import { Configure } from "configure";
const configure = new Configure({ apiKey: process.env.CONFIGURE_API_KEY, agent: process.env.CONFIGURE_AGENT });
const profile = configure.profile({ token: session.token });   // the user's token, not your externalId

const tools = profile.tools({
  connectors: ["gmail", "calendar"],       // reads to expose
  actions: ["calendar.create_event"],      // state-changing tools to expose; [] for none
});

// Hand `tools` to the model (Anthropic shape; toOpenAIFunctions(tools) for OpenAI),
// then run whatever it calls:
const result = await profile.executeTool(call);

A tool outside the set is refused with ACCESS_DENIED before any request leaves your server. On a profile keyed by externalId, executeTool serves only the profile tools. It answers connector calls with authorization_required and a link. Reach connected apps through the session instead.

What a refusal carries ​

How a refusal reaches you depends on how you made the profile handle. One handle throws. The other returns a value.

On a profile keyed by token, a connector the user has not connected, or whose access expired, throws a ConfigureError with code: "TOOL_NOT_CONNECTED". suggestedAction is "connect_tool" for an app the user never connected and "reconnect" for one whose access expired. There is no connect_url on that error. Mint one with profile.connect({ app }) and put it in your reply.

On a message channel, do not send that link. A text keeps it forever, and a minted link expires while it sits in the thread. Use auth.createMessageSignInUrl({ reason: "reconnect" }) instead, as Message agents explains.

On a profile keyed by externalId, the same call returns instead of throwing. The value is the connect payload: { status: "authorization_required", connect_url, app, connected: false, instructions }. Use connect_url verbatim.

A missing scope is a different failure. It fails with permission_denied.

You can narrow the session's tool list too. Do this on a busy chat. Seventeen tool definitions add real prompt weight, and the model should not reach some of them in your product. configure_profile_read also tells the model to call it first on every turn, which conflicts with the context you already injected. Filter tools by name after listTools(). Give the model only the tools your product uses.

Read the cause before you tell the model anything. A quota, a rate limit, an expired token, and an unconnected app all surface as a failed tool call. Some frameworks flatten the cause to something like "An error occurred". Given that, a model guesses the most common cause and tells a connected user to connect again. One integration showed a user that prompt four times while the account was connected. The real error was a monthly read quota.

Keep the original error and branch on it. Say "connect" only when the payload says so. Read the cause from the error object instead of stringifying it. A rejected MCP call is an object, so String(error) gives you "[object Object]", and every pattern you match against it fails. A revoked or expired token behaves the same way as an unconnected app. Reconnecting is one click for the user and needs no code from you.

Actions and approval ​

Two checks gate a state-changing call. The user must have connected the app, and the user must have approved your agent. Both checks fail closed on Configure's side. Neither one is a per-call prompt from Configure.

You decide whether to confirm each send or event with the user, and you build that UI. The components bundle ships a card for it: <configure-tool-approval>. It shows the tool and its parameters with Approve and Deny. It times out to Deny. It emits configure:tool-approve, configure:tool-deny, or configure:tool-always-allow with the action id. The card executes nothing. Your handler runs the tool only on approve. Tool annotations also mark configure_email_send as destructive, so MCP clients that confirm destructive calls confirm it on their own.

Personalization infrastructure for agents