Let an agent act in a customer's Gmail or GitHub
Connect a person's Google, GitHub or Slack account once, give an app agent only the scopes it needs, and make risky writes wait for a yes.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
Your app agent needs to read a person's Gmail, or open an issue in their GitHub, and only after they agreed. This guide sets up one provider, one agent with a narrow scope and one risky scope that waits for a yes. It takes about 30 minutes.
Spec connections, for 0.3. Status: experimental, so import it from mainsdk/experimental/connections.
You need
- An OAuth app registered with the provider, with its client ID and secret. Its redirect URL is your app's
connections.callbackpath. - A MainSDK app with an agent defined in
main.yaml.
1. Declare the provider and the agent
connections:
store: db
callback: /api/connections/callback
providers:
gmail:
preset: google
client-id: 1234.apps.googleusercontent.com
client-secret: secret:GOOGLE_OAUTH_SECRET
scopes: [https://www.googleapis.com/auth/gmail.readonly, https://www.googleapis.com/auth/gmail.send]
hosts: [gmail.googleapis.com]
step-up: [https://www.googleapis.com/auth/gmail.send] # sending waits for a person's yes
agents:
support-bot:
connections:
gmail: [https://www.googleapis.com/auth/gmail.readonly] # read only, for this agentscopes is the most the app may ever ask for. The agent gets the read scope alone.
2. Send the person to the consent screen
import { connections } from "mainsdk/experimental/connections";
export async function GET(req: Request) {
return connections.connect(req, "gmail", { returnTo: "/settings/accounts" });
}The person sees one consent screen. After they agree, the callback stores the grant and returns them to returnTo.
3. Show what they agreed to
import { connections } from "mainsdk/experimental/connections";
const info = connections.describe("gmail");Render info.scopes and info.agents on your settings page, so the person can see which agent can do what.
4. Let the agent read
import { connections } from "mainsdk/experimental/connections";
declare const user: { id: string }; // the signed-in person
const res = await connections.fetch("gmail", "https://gmail.googleapis.com/gmail/v1/users/me/messages", {
scope: "https://www.googleapis.com/auth/gmail.readonly",
agent: "support-bot",
user,
});scope is required. A call for a scope the agent does not hold fails with MAIN-CONN-0004 and nothing is sent.
5. Make the send wait for a yes
A call that needs gmail.send returns Pending until the person approves it in your app. Handle Pending like any other waiting state, and render the approval request.
Check it worked
- After connecting,
connections.list()shows the grant withstatus: "active". connections.fetchwith the read scope returns a response.- The same call with a scope not listed under
agents.support-botfails withMAIN-CONN-0004. - Revoking the grant with
connections.revoke(id)makes the next call fail withMAIN-CONN-0002orMAIN-CONN-0007.
If it fails
MAIN-CONN-0006: the callback state was missing, expired or used. Start the connect flow again.MAIN-CONN-0005: the URL's host is not inhosts. Call the listed host only.MAIN-CONN-0021: a provider has no preset and nohosts. Add them.
Prompt for your coding agent
Connect a customer's Gmail with MainSDK's
connectionsinterface. Request only the read scope forsupport-bot, keep send behindstep-up, and show each scope and agent on the account settings page.
Was this page helpful?