# Connections

> MCP and OpenAPI connections with defineMcpClientConnection and defineOpenAPIConnection, OAuth callback routes, connection_search qualified tool names, and getToken/requireAuth flows.

- Repository: vercel/eve
- GitHub: https://github.com/vercel/eve
- Human docs: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f
- Complete Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/llms-full.txt

## Source Files

- `docs/connections.mdx`
- `packages/eve/src/public/connections/index.ts`
- `packages/eve/src/compiler/normalize-connection.ts`
- `packages/eve/src/protocol/routes.ts`
- `packages/eve/src/runtime/session-callback-route.ts`
- `apps/fixtures/agent-tui-client/agent/connections/stub-mcp-user.ts`

---

---
title: "Connections"
description: "MCP and OpenAPI connections with defineMcpClientConnection and defineOpenAPIConnection, OAuth callback routes, connection_search qualified tool names, and getToken/requireAuth flows."
---

Connections under `agent/connections/` wire an Eve agent to external MCP servers and OpenAPI HTTP APIs. The compiler validates each module at `eve build`; the runtime re-imports live `auth` callbacks, discovers remote tools, brokers credentials the model never sees, and exposes them through the dynamic `connection__` tool namespace.

## Authoring and discovery

Connection modules export a default `defineMcpClientConnection` or `defineOpenAPIConnection` definition. The runtime name is path-derived from the filename slug — `agent/connections/linear.ts` registers as `"linear"`. Do not add a `name` field.

:::files
agent/
  connections/
    linear.ts          # MCP → name "linear"
    petstore.ts        # OpenAPI → name "petstore"
:::

At graph resolution, Eve injects a **Connections** system-prompt section listing each connection and its `description`. When the agent declares at least one connection, the framework registers a dynamic tool resolver with slug `connection`. The model-facing search tool is `connection__search`; discovered tools are callable by qualified name `connection__<connection>__<tool>` (for example `connection__linear__list_issues`).

<Steps>
<Step title="Search for tools">

Call `connection__search` with `keywords` (required), optional `connection` to scope one connection, and optional `limit` (default 10). Results include `qualifiedName`, `inputSchema`, and `needsAuthorization` when sign-in is required.

</Step>
<Step title="Call discovered tools">

In the next response, invoke the returned `qualifiedName` directly (for example `connection__linear__list_issues`). The resolver re-derives the tool set from conversation history on each step; after compaction, stale search results drop from the toolset.

</Step>
</Steps>

<Note>
The harness docs refer to `connection_search`; at runtime the model sees `connection__search` under the `connection__` namespace prefix.
</Note>

## MCP connections

`defineMcpClientConnection` from `eve/connections` points at an MCP server over Streamable HTTP or SSE.

```ts title="agent/connections/linear.ts"
import { defineMcpClientConnection } from "eve/connections";

export default defineMcpClientConnection({
  url: "https://mcp.linear.app/sse",
  description: "Linear workspace: issues, projects, cycles, and comments.",
  auth: {
    getToken: async () => ({ token: process.env.LINEAR_API_TOKEN! }),
  },
});
```

Write `description` for the model — it appears in the system prompt and `connection__search` results.

### MCP definition fields

| Field | Purpose |
| ----- | ------- |
| `url` | MCP server HTTP endpoint (Streamable HTTP or SSE). |
| `description` | Model-facing summary of the connection and its tools. |
| `auth` | Optional authorization strategy (see [Authorization](#authorization)). |
| `headers` | Optional extra HTTP headers; stacks with `auth`. |
| `tools` | Optional filter: exactly one of `allow` or `block` on remote tool names. |
| `approval` | Optional per-connection approval gate (`never()`, `once()`, `always()` from `eve/tools/approval`). |

Filtered tools never appear in `connection__search` results.

## OpenAPI connections

`defineOpenAPIConnection` turns an OpenAPI 3.x or Swagger 2.0 document into one tool per operation.

```ts title="agent/connections/tfl.ts"
import { defineOpenAPIConnection } from "eve/connections";

export default defineOpenAPIConnection({
  spec: "https://api.tfl.gov.uk/swagger/docs/v1",
  description: "Transport for London Unified API from its public Swagger 2.0 document.",
  operations: { allow: ["Journey_Meta"] },
});
```

Each operation becomes `connection__<connection>__<operationId>`. When `operationId` is missing, Eve synthesizes `<method>_<sanitized-path>`.

### OpenAPI-specific fields

| Field | Purpose |
| ----- | ------- |
| `spec` | HTTPS URL fetched at runtime, or an inline parsed object. |
| `baseUrl` | Optional override for the document's first usable `servers` entry. |
| `operations` | Filter keyed on `operationId` (`allow` or `block`); mirrors MCP `tools`. |

`auth`, `headers`, and `approval` behave the same as MCP connections.

## Authorization

Eve sends resolved tokens as `Authorization: Bearer <token>`. Tokens are cached per workflow step keyed by `(connectionName, principalKey)` and never serialized into durable step payloads or conversation history.

### Principal types

<ParamField body="principalType" type='"app" | "user"' default="app for getToken-only auth">
Declares whether the connection acts as the agent (`"app"`) or on behalf of the end user (`"user"`).
</ParamField>

- **`"app"`** — one shared credential across all sessions. `getToken`-only auth defaults here when `principalType` is omitted.
- **`"user"`** — per-user tokens keyed by `issuer` + `id`. Required for interactive OAuth (`defineInteractiveAuthorization` pins `"user"`).

### Non-interactive `getToken`

A `getToken`-only `auth` object covers static API keys, pre-provisioned JWTs, and out-of-band OAuth. `getToken` runs before every connection tool call and returns `TokenResult`:

```ts
{ token: string; expiresAt?: number }  // expiresAt in ms since epoch
```

Set `expiresAt` when the token has a known TTL; Eve treats expired cache entries as misses. Throw `ConnectionAuthorizationRequiredError` to signal missing credentials — the runtime emits `authorization.required` without suspending the turn.

```ts title="No auth"
export default defineMcpClientConnection({
  url: "http://localhost:3001/mcp",
  description: "Local dev server.",
});
```

### Headers without Bearer auth

Use `headers` for API-key schemes or extra configuration. Headers stack on top of `auth`; do not set an `Authorization` header when `auth` is also provided.

### Interactive OAuth

For browser-based consent, provide all three methods via `defineInteractiveAuthorization` (or `connect()` from `@vercel/connect/eve`):

1. **`getToken`** — return a cached token or throw `ConnectionAuthorizationRequiredError` to start consent.
2. **`startAuthorization`** — runs in a durable step; returns a user-facing `challenge` and optional JSON-serializable `resume` (for example a PKCE verifier).
3. **`completeAuthorization`** — exchanges the IdP callback for a `TokenResult`.

`startAuthorization` and `completeAuthorization` are both-or-neither; providing exactly one is a definition error at `defineMcpClientConnection` / `defineOpenAPIConnection` time.

```ts title="Self-hosted OAuth"
import {
  ConnectionAuthorizationFailedError,
  ConnectionAuthorizationRequiredError,
  defineInteractiveAuthorization,
  defineMcpClientConnection,
} from "eve/connections";

export default defineMcpClientConnection({
  url: "https://mcp.linear.app/sse",
  description: "Linear workspace.",
  auth: defineInteractiveAuthorization<{ verifier: string }>({
    getToken: async ({ principal }) => {
      const token = await lookupCachedToken(principal);
      if (!token) throw new ConnectionAuthorizationRequiredError("linear");
      return { token };
    },
    startAuthorization: async ({ callbackUrl }) => {
      const verifier = makePkceVerifier();
      return {
        challenge: { url: buildAuthorizeUrl(callbackUrl, verifier) },
        resume: { verifier },
      };
    },
    completeAuthorization: async ({ resume, callback }) => {
      const token = await exchangeCode(resume!.verifier, callback.params.code!);
      return { token };
    },
  }),
});
```

#### Challenge fields

| Field | Purpose |
| ----- | ------- |
| `url` | Authorize URL for redirect or device flows. |
| `userCode` | Device code for device flows. |
| `instructions` | Call to action when no URL is available. |
| `displayName` | Provider label for channel sign-in UI (presentation only). |

`displayName` on the `auth` definition overrides a challenge-level value. Identity (scope, token cache, callback URL) stays keyed by the path-derived connection name.

#### Authorization errors

Throw these from `getToken` or `completeAuthorization`:

- **`ConnectionAuthorizationRequiredError(connectionName)`** — user must authorize; emits `authorization.required` and, for interactive strategies, parks the turn.
- **`ConnectionAuthorizationFailedError(connectionName, { reason?, retryable? })`** — authorization failed. `reason` is a stable machine-readable code on `authorization.completed` events. `retryable` defaults to `true`; set `false` for terminal cases like `access_denied`.

Narrow caught errors with `isConnectionAuthorizationRequiredError(err)` and `isConnectionAuthorizationFailedError(err)` (name-based guards that survive bundling).

### Vercel Connect

For managed OAuth with encrypted token storage and refresh:

```ts
import { connect } from "@vercel/connect/eve";
import { defineMcpClientConnection } from "eve/connections";

export default defineMcpClientConnection({
  url: "https://mcp.linear.app/sse",
  description: "Linear workspace.",
  auth: connect("linear"),  // Connect client UID
});
```

Connect-managed OAuth is user-scoped. The compiler duck-types a `vercelConnect: { connector }` marker onto the auth definition for build output without importing `@vercel/connect/eve` at compile time.

### Revoked tokens mid-call

`getToken` runs only before a tool call. When a downstream request returns `401` during `execute`, map it to `ctx.requireAuth()` (authored tools) or rethrow `ConnectionAuthorizationRequiredError` (connections). Eve evicts the rejected bearer from the per-step cache and optionally calls `auth.evict()` to purge strategy-level caches (for example Vercel Connect's in-process cache) before re-running consent.

### Approval plus authorization

When a connection declares both `approval` and interactive `auth`, the user sees **approve, then sign in**. Approval is recorded on session state before the OAuth park; after authorization resumes, the tool is not re-approved.

## OAuth callback route

Interactive connection authorization parks the turn until the IdP redirects back. Eve owns the callback under `/eve/v1` rather than exposing raw workflow webhook primitives.

:::endpoint GET /eve/v1/connections/:name/callback/:token
Receives OAuth IdP redirects (authorization code in query string). Also registered for `POST` to support `form_post` response modes.
:::

<ParamField path="name" type="string" required>
Path-derived connection name (for example `linear`).
</ParamField>

<ParamField path="token" type="string" required>
Unguessable workflow hook capability minted by `getHookUrl`. Authorizes the resume; the route is intentionally unauthenticated.
</ParamField>

The handler:

1. Parses query and form-encoded params into an `AuthorizationCallback` (params only — request headers are dropped).
2. Calls `resumeHook(token, { kind: "deliver", payloads: [{ authorizationCallback }] })` to wake the suspended turn.
3. Returns an "Authorization complete" landing page (or `404` when no pending authorization matches).

`getHookUrl(connectionName)` builds the full redirect URL as `{baseUrl}/eve/v1/connections/{name}/callback/{token}` using a per-session hook token derived from the session ID.

```mermaid
sequenceDiagram
  participant Model
  participant Runtime
  participant IdP
  participant Browser

  Model->>Runtime: connection__linear__list_issues
  Runtime->>Runtime: getToken throws Required
  Runtime->>Runtime: startAuthorization (durable step)
  Runtime-->>Browser: authorization.required + challenge.url
  Browser->>IdP: User consents
  IdP->>Runtime: GET /eve/v1/connections/linear/callback/:token
  Runtime->>Runtime: resumeHook → completeAuthorization
  Runtime->>Model: Tool result with fresh token
```

## `getToken` and `requireAuth` on authored tools

Authored tools under `agent/tools/` can declare the same `auth` shapes as connections. `ToolContext` then exposes:

- **`ctx.getToken()`** — resolves the bearer via the per-step cache, then the authored `getToken`. Cache miss on interactive strategies throws `ConnectionAuthorizationRequiredError` and parks the turn.
- **`ctx.requireAuth()`** — throws `ConnectionAuthorizationRequiredError` without resolving a token first; use to gate on sign-in or to re-challenge after a downstream `401`.

Calling either without an `auth` field on the tool throws. The scoped authorization machinery is identical to connections, keyed by the tool's path-derived name instead of a connection name. See [State, hooks, and session context](/state-hooks-and-context) for the full `ToolContext` surface.

## Compile-time behavior

`compileConnectionDefinition` imports each connection module during `eve build`, reads the protocol marker stamped by the `define*` factory (`"mcp"` or `"openapi"`), and writes serializable metadata to the compiled manifest. Live `auth` callbacks, OpenAPI `spec` objects, and operation filters are resolved at runtime by re-importing the authored module. Authoring errors surface at build time rather than on first request.

## Troubleshooting

| Symptom | Likely cause |
| ------- | ------------ |
| `connection__search` missing | Agent has no files under `agent/connections/`. |
| Connection in prompt but search returns errors | Remote server unreachable, invalid `url`/`spec`, or auth failure during `listTools`. |
| `Connection callback not pending` (404) | Callback arrived after the park expired, or token mismatch. |
| Infinite sign-in loop | Token rejected immediately after `completeAuthorization`; runtime fails with `token_rejected_after_authorization`. |
| `principal_required` | `principalType: "user"` connection invoked without an authenticated user session. |

Run `eve info` to verify connection discovery. Check `.eve/discovery/diagnostics.json` after `eve build` for compile-time validation errors.

## Related pages

<CardGroup>
<Card title="Project layout" href="/project-layout">
Where `agent/connections/` fits in the authored agent tree and path-derived naming rules.
</Card>
<Card title="Default harness" href="/default-harness">
Built-in `connection__search` registration and how discovered tools join the toolset.
</Card>
<Card title="Tools" href="/tools">
Authored tools, approval predicates, and per-tool `auth` with `getToken` / `requireAuth`.
</Card>
<Card title="Execution model" href="/execution-model">
Turn parking, durable steps, and resume semantics for OAuth and HITL.
</Card>
<Card title="HTTP API" href="/http-api">
Stable `/eve/v1` routes including connection OAuth callbacks.
</Card>
<Card title="Security model" href="/security-model">
Secret brokering, token handling, and trust boundaries for connections.
</Card>
<Card title="Auth and deployment" href="/auth-and-deployment">
Route auth, Vercel Connect setup, and production verification.
</Card>
</CardGroup>
