Sponsored Content
Docs
GitHub

Auth

Give the tools a GitHub token with only the permissions the agent needs.

Beginner6 min read

Overview

GitHub tools authenticates every call with a GitHub token, and the token's permissions decide what the call can do. A token that can't push can't change your code, whatever the model decides, so give the agent only the permissions its job needs.

The token can come from the environment, from your own code on every call, or from Vercel Connect, which mints short-lived tokens on demand so there's no secret to store.

Configure

Set token where you create the tools or the agent. Without it, the SDK reads GITHUB_TOKEN:

tools.ts
import { createGithubTools } from '@github-tools/sdk'

const tools = createGithubTools({
  preset: 'issue-triage',
  token: async (call) => getInstallationToken(call?.owner),
})
tokenWhen it's read
omittedGITHUB_TOKEN, when the tools are created
a stringOnce, when the tools are created
(call?) => Promise<string>On every tool call

A function runs on every tool call, so a short-lived token stays fresh. It receives the call: toolName, input after context defaults, and owner and repo when the tool targets a repository.

Use a fine-grained token

For local work and single-tenant agents, create a fine-grained personal access token limited to the agent's repositories. Start with read access, and add write permissions only for the writes you plan to allow.

Permissions per preset

The table shows what each preset needs to run all of its tools. Every token also needs Metadata (read). Read access is enough when you only allow the preset's reads.

Preset

Repository permissions

4 of 12 granted, 5 scopes

  • Actions
  • Administration
  • Checks
  • Code scanning alerts
  • Commit statuses
  • ContentsRead-only
  • Dependabot alerts
  • Discussions
  • IssuesRead and write
  • MetadataRead-only
  • Pull requestsRead-only
  • Secret scanning alerts

A Connect token requests the matching scopes, such as issues:write.

Each preset needs only some permissions. Pick one to see the GitHub App access its tools use, and the scopes a Connect token requests for it.
PresetContentsPull requestsIssuesDiscussionsActionsChecks, statusesOther
repo-explorerreadreadreadreadreadreadGists, for the gist tools
code-reviewreadwriteread, Commit statuses (write)
issue-triagereadreadwrite
ci-opsreadreadwriteread, Commit statuses (write)
security-auditreadreadwritereadreadCode scanning, Dependabot and secret scanning alerts (read)
release-managerwritereadwriteread
discussion-moderatorreadreadwritewrite
notification-inboxreadreadreadreadNotifications
pr-authorwritewrite
maintainerwritewritewritewritewriteread, Commit statuses (write)Administration (write), the three alert permissions (read), Gists, Notifications

Releases fall under Contents and reactions under Issues, so neither needs a permission of its own.

createCommitStatus is the only tool that needs Commit statuses (write). With a personal access token, only that tool fails without it. With Vercel Connect, the preset's token asks for statuses:write on every call, so a GitHub App without that permission can make every tool in the preset fail. getCollaboratorPermission needs only Metadata (read). updatePullRequestBranch needs Pull requests (write), and a GitHub App also needs Contents (write) on the pull request's head repository.

Vercel Connect

On Vercel, Vercel Connect replaces the stored token. A GitHub connector, a GitHub App that Vercel manages, is attached to your project, and your code asks it for a short-lived token scoped to the preset. There's no secret to store or rotate.

preset
  1. Agent

    listIssues({ owner: 'vercel', repo: 'ai' })

  2. SDK

    Token request for vercel/ai

    contents:readmetadata:readpull_requests:readissues:readissues:write

  3. Connect

    Minted token Anew

    vercel installation, repository ai

  4. GitHub

    GET /​repos/​vercel/​ai/​issues200

  5. Agent

    addLabels({ issueNumber: 42, labels: ['bug'] })

    write, approved

  6. SDK

    Token request for vercel/ai, same preset scopes

    contents:readmetadata:readpull_requests:readissues:readissues:write

  7. Connect

    Reused token Acached

    same connector and parameters

  8. GitHub

    POST /​repos/​vercel/​ai/​issues/​42/​labels200

Token A is short-lived and expires on its own. A later call gets a new one.

Every call gets a short-lived token. Each tool call asks Connect for a token on the repository it targets, scoped to the preset, or to the one tool under eve's auto preset.

Create a GitHub connector

  1. Create a connector of type github from the Vercel dashboard, or with vercel connect.
  2. Install its GitHub App on the organization or account the agent works on, and pick the repositories.
  3. Link the connector to the project that runs the agent. Deployments authenticate with their OIDC token. Locally, run vercel link, then vercel env pull.
  4. Install @vercel/connect, an optional peer dependency used only by @github-tools/sdk/connect:
pnpm add @vercel/connect

Use the connector

connectGithubTools takes the connector name and the usual tool options, and derives the token's scopes from preset:

connect.ts
import { connectGithubTools } from '@github-tools/sdk/connect'
import { generateText } from 'ai'

const { text } = await generateText({
  model: 'anthropic/claude-opus-5.5',
  tools: connectGithubTools('github/my-connector', { preset: 'code-review' }),
  prompt: 'Summarize the open pull requests on my-org/my-repo.',
})

The token is minted inside each tool call, and Connect caches it per connector and parameters.

For createGithubTools or a single tool, connectGithubToken returns the token function. Pass it the same preset, since it derives its scopes on its own:

connect-token.ts
import { createGithubTools } from '@github-tools/sdk'
import { connectGithubToken } from '@github-tools/sdk/connect'

const tools = createGithubTools({
  preset: 'ci-ops',
  token: connectGithubToken('github/my-connector', { preset: 'ci-ops' }),
})

To scope the token to a hand-picked tool set instead, pass include and exclude. connectGithubScopesForPreset and its siblings return the same scope lists, see the API reference.

Leave a tool out of the token

A preset's token asks for the scopes of every tool in it, so the connector's GitHub App needs each of those permissions. code-review, ci-ops and maintainer include createCommitStatus, which needs Commit statuses (read and write). Add that permission to the App, or leave the tool out of the token.

connectGithubTools always asks for the whole preset's scopes. Build the token with connectGithubToken and exclude, and drop the tool from the tool set:

connect-without-commit-status.ts
import { createGithubTools } from '@github-tools/sdk'
import { connectGithubToken } from '@github-tools/sdk/connect'

const token = connectGithubToken('github/my-connector', { preset: 'code-review', exclude: ['createCommitStatus'] })
const { createCommitStatus, ...tools } = createGithubTools({ preset: 'code-review', token })

On the eve extension, exclude: ['createCommitStatus'] removes the tool and its scope together.

Use one app on several accounts

When the App is installed on several accounts, each call that targets owner/repo gets a token from the installation that owns that repository. Calls without a repository target, such as search, use the connector's default installation.

To pin one installation or narrow the token, pass Connect parameters as connect:

connect-scoped.ts
const tools = connectGithubTools('github/my-connector', {
  preset: 'issue-triage',
  connect: {
    installationId: 'inst_abc',
    repositories: ['my-org/my-repo'],
  },
})

installationId, authorizationDetails or repositories turn off the per-repository targeting, and scopes replaces the derived scopes. connect can also be a function of the call, (call) => params.

Per-user tokens

By default every caller acts as the App, with the project's access. When each user connects their own GitHub account, pass a user subject so calls act as that user:

connect-user.ts
const tools = connectGithubTools('github/my-connector', {
  preset: 'issue-triage',
  connect: { subject: { type: 'user', id: currentUserId } },
})

A user who hasn't connected GitHub gets CONNECT_USER_NOT_CONNECTED instead of falling back to the App.

Call getToken yourself

For full control over the Connect parameters, call getToken from @vercel/connect inside a token function:

get-token.ts
import { createGithubTools } from '@github-tools/sdk'
import { getToken } from '@vercel/connect'

const tools = createGithubTools({
  preset: 'code-review',
  token: () => getToken('github/my-connector', {
    subject: { type: 'app' },
    scopes: ['contents:read', 'pull_requests:read'],
  }),
})

Expired OIDC tokens

When VERCEL_OIDC_TOKEN is set, as after vercel env pull, the SDK passes it to Connect directly. This also works in sandboxes without a .vercel directory. That token isn't refreshed: once it expires, calls fail with OIDC_TOKEN_EXPIRED before any request. Run vercel env pull again. Deployments get a fresh token on their own.

FAQ