Tokens and Authentication
Every tool call hits the GitHub API with the token you provide. Scoping that token correctly is the first line of defense. You have two main options: a fine-grained personal access token you manage yourself, or short-lived tokens minted by Vercel Connect.
Align GitHub PAT with tool presets
Create a fine-grained token
Fine-grained personal access tokens let you restrict access per repository and per permission category. This is the recommended type for any production assistant.
- Go to github.com/settings/personal-access-tokens/new
- Select only the repositories your agent needs
- Enable permissions based on the preset you plan to use (see matrix below)
Map permissions to presets
| Preset | Repository access | Contents | Pull requests | Issues | Discussions | Actions | Checks/Statuses | Administration |
|---|---|---|---|---|---|---|---|---|
repo-explorer | selected repos | read | read | read | read | read | read | none |
code-review | selected repos | read | read (or write for comments/reviewer requests) | none | none | none | read | none |
issue-triage | selected repos | read | none | write | none | none | none | none |
ci-ops | selected repos | read | none | none | none | write | read | none |
security-audit | selected repos | read | read | write (to report findings) | none | read | read | none |
release-manager | selected repos | write (for release creation) | read | none | none | write (if triggering pipelines) | none | none |
discussion-moderator | selected repos | read | none | write (when linking issues) | write | none | none | none |
notification-inbox | selected repos | read | read | read | none | none | none | none |
pr-author | selected repos | write | write | none | none | none | none | none |
maintainer | selected repos | write | write | write | write | write | read | write (for repo creation and forking) |
Reactions (listIssueReactions, addIssueReaction, listCommentReactions, addCommentReaction) are covered by the Issues permission and need no scope of their own.
repo-explorer and maintainer also include gist read (and, for maintainer, write) tools. Gists aren't tied to a repository. GitHub only grants gist access to GitHub App user access tokens, never installation tokens, so give the PAT the "Gists" account permission separately, and expect gist tools to fail over Vercel Connect (see below).
notification-inbox and maintainer include listNotifications and markNotificationRead. Notifications belong to a user rather than a repository, so they need the "Notifications" account permission on the PAT and, like gists, fail with a Connect-minted installation token.
Releases are covered by the Contents permission on GitHub Apps: repo-explorer, release-manager, and maintainer need no separate scope for listReleases, getLatestRelease, or getRelease.
Mint tokens with Vercel Connect
For agents deployed on Vercel, Vercel Connect replaces long-lived PATs entirely. You attach a GitHub connector (a Vercel-managed GitHub App) to your project, and your server code requests a short-lived, scoped token at runtime, with no secret to store, rotate, or leak.
@github-tools/sdk/connect to derive scopes from your preset automatically. See the Vercel Connect guide for connectGithubTools and connectGithubToken.Create a GitHub connector
Create a connector from the Vercel dashboard (or vercel connect in the CLI) with the type github, then install it on the GitHub organization or user account your agent needs, selecting the repositories to expose.
Link it to your project
Link the connector to the Vercel project that runs your agent. On Vercel, the SDK authenticates automatically with the deployment's OIDC token. For local development, run vercel link then vercel env pull to get a development OIDC token.
Request a token at runtime
Install @vercel/connect and call getToken with the permissions your preset needs, then pass the result as token. Prefer the connect subpath when you want preset-derived scopes without maintaining a scope list:
import { connectGithubTools } from '@github-tools/sdk/connect'
import { generateText } from 'ai'
const { text } = await generateText({
model: 'anthropic/claude-sonnet-4.6',
tools: connectGithubTools('github/my-connector', { preset: 'code-review' }),
prompt: 'Summarize the open PRs on my-org/my-repo.',
})
Manual getToken (full control over every parameter): pass a lazy provider so minting happens at tool execution, not at import/build time:
import { getToken } from '@vercel/connect'
import { createGithubTools } from '@github-tools/sdk'
const tools = createGithubTools({
token: () => getToken('github/my-connector', {
subject: { type: 'app' },
scopes: ['contents:read', 'pull_requests:read'],
}),
preset: 'code-review',
})
await getToken(...) in modules that are imported at build time, especially eve agent/extensions/*.ts (or agent/tools/*.ts for the deprecated direct import) exports. Minting needs the Vercel OIDC header and only exists at request/runtime. Prefer the extension's connector option, connectGithubTools / connectGithubToken, or a token: () => getToken(...) provider as above.The provider is invoked at each tool execution, so short-lived tokens stay fresh across a long agent run. Note that in durable workflows the resolved token string is captured in the step arguments. A retried step reuses the token it was first invoked with rather than requesting a new one.
Call getToken per request rather than caching the string yourself. The SDK keeps an in-process cache and refreshes tokens as they approach expiry. For multi-tenant connectors (one connector installed on several GitHub organizations), pass installationId to target a specific installation. When you need a different connector per environment or tenant rather than the same connector with a different installation, connectGithubTools / connectGithubToken also accept a () => string | Promise<string> resolver in place of a connector name, see Vercel Connect: dynamic connector selection.
Why Connect over a PAT
| Fine-grained PAT | Vercel Connect | |
|---|---|---|
| Lifetime | Until expiry/revocation | Short-lived, auto-refreshed |
| Storage | Env var / secret manager | Nothing to store, minted at runtime |
| Scoping | Set once at creation | Per-request scopes and repositories |
| Multi-tenant | One token per org | One connector, many installations |
| Rotation | Manual | Automatic |
connector directly to the eve extension mount config, no separate import needed:import githubExtension from '@github-tools/eve-extension'
export default githubExtension({ connector: 'github/my-connector', preset: 'maintainer' })
connectGithubTools from @github-tools/sdk/connect/eve still works the same way.)Apply least-privilege step by step
Start with read-only
Enable only contents: read and use preset: 'repo-explorer'.
Validate in staging
Run the agent against a test repository and review all tool calls before adding write scopes.
Add writes for approved operations
Enable write permissions only for the specific families you need, and combine with approval control.
requireApproval: true.