# How to Integrate External Services with OpenWork: A Complete MCP Integration Guide

> Integrate external services like Notion Linear and Stripe with OpenWork using the MCP framework. Discover and execute tools seamlessly via a standardized OAuth lifecycle. Learn how today.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-02

---

**OpenWork integrates external services like Notion, Linear, and Stripe through the Model-Context-Protocol (MCP), enabling AI agents to discover and execute tools via a standardized OAuth-based lifecycle.**

OpenWork uses the **Model-Context-Protocol (MCP)** to expose third-party APIs as callable capabilities for AI agents. To integrate external services with OpenWork, you will interact with the core MCP client in [`ee/apps/den-api/src/capability-sources/external-mcp-client.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/capability-sources/external-mcp-client.ts) and the connection management routes defined in [`ee/apps/den-api/src/routes/org/mcp-connections.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/org/mcp-connections.ts).

## Understanding the MCP Integration Lifecycle

Every external service integration follows a five-phase lifecycle implemented in the OpenWork codebase:

1. **Discovery** – OpenWork fetches the MCP server’s metadata, including OAuth endpoints and available tools, via the `ExternalMcpClient` class in [`external-mcp-client.ts`](https://github.com/different-ai/openwork/blob/main/external-mcp-client.ts).

2. **OAuth Registration** – For services requiring authentication, OpenWork either registers a dynamic client or uses pre-registered credentials. The client metadata document is served at [`/oauth/client-metadata.json`](https://github.com/different-ai/openwork/blob/main//oauth/client-metadata.json) as documented in [`docs/external-mcp-oauth.md`](https://github.com/different-ai/openwork/blob/main/docs/external-mcp-oauth.md).

3. **Authorization** – The user or admin completes the OAuth flow, and tokens are stored encrypted in Den via the routes in [`mcp-connections.ts`](https://github.com/different-ai/openwork/blob/main/mcp-connections.ts).

4. **Tool Catalog Retrieval** – OpenWork queries the MCP server’s `tools/list` endpoint, filters the results, and enforces size limits using `validateToolCatalogField` and `measureSerializedJson`.

5. **Tool Execution** – Agents invoke tools through `callExternalMcpTool`, which forwards arguments to the MCP server and returns structured results.

## Adding an External MCP Connection

You can add connections to external services either through predefined presets or by configuring custom MCP servers.

### Quick-Add with Presets

OpenWork ships with `EXTERNAL_MCP_PRESETS` in [`ee/apps/den-api/src/capability-sources/external-mcp-presets.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/capability-sources/external-mcp-presets.ts), providing one-click setup for popular services:

```ts
export const EXTERNAL_MCP_PRESETS = [
  { presetId: "notion", displayName: "Notion", url: "https://mcp.notion.com/mcp", authType: "oauth" },
  { presetId: "linear", displayName: "Linear", url: "https://mcp.linear.app/mcp", authType: "oauth" },
];

```

To create a connection using a preset or custom URL, send a POST request to the connections endpoint:

```bash
POST /v1/mcp-connections
{
  "name": "My Notion Connection",
  "url": "https://mcp.notion.com/mcp",
  "authType": "oauth",
  "credentialMode": "shared"
}

```

The API returns a connection ID and, if OAuth is required, an `authorizeUrl` for the admin to visit.

### Custom MCP Servers

For services not listed in presets, supply the MCP server URL directly. The `externalMcpUrlSchema` in [`mcp-connections.ts`](https://github.com/different-ai/openwork/blob/main/mcp-connections.ts) validates that URLs use HTTP/HTTPS protocols, contain no fragments, and embed no credentials.

## Implementing the OAuth Flow

When `authType` is set to `oauth`, complete the following steps to authorize the connection:

1. **Start the flow** by calling the connect endpoint:

```bash
POST /v1/mcp-connections/:id/connect/start

```

If a valid token exists, the response returns `{status: "connected"}`. Otherwise, you receive `{status: "needs_auth", authorizeUrl: "..."}`.

2. **Redirect the admin** to the `authorizeUrl`. The URL contains a signed `state` token generated by `createOAuthStateToken`.

3. **Complete authorization** on the MCP provider site. The provider redirects back to OpenWork’s callback URL at `/v1/mcp-connections/oauth/callback`.

4. **Process the callback** using `handleExternalMcpOAuthCallback` in [`mcp-connections.ts`](https://github.com/different-ai/openwork/blob/main/mcp-connections.ts), which validates the signed state, discovers the expected issuer, and calls `completeExternalMcpAuth` to store tokens.

5. **Token storage** – Encrypted tokens are saved in `ExternalMcpConnectionRow` (for shared mode) or `ConnectedAccountTable` (for per-member mode).

All callback and metadata URLs derive from the `DEN_API_PUBLIC_URL` environment variable.

## Retrieving the Tool Catalog

After authorization, fetch available tools for a connection:

```bash
GET /v1/mcp-connections/:id/tools

```

This endpoint executes `listExternalMcpTools`, which:

- Uses the MCP SDK’s `client.listTools` with pagination limits via `collectExternalMcpToolPages`.
- Enforces strict size limits (512 KB maximum) on tool names, titles, descriptions, and JSON schemas.
- Returns an array of `ConnectionTool` objects containing `name`, `title`, `description`, `inputSchema`, and `outputSchema`.

## Executing Tools from Agents

Agents invoke external tools through the execution endpoint:

```bash
POST /v1/mcp-connections/:id/tools/:toolName/call
{
  "arguments": { "query": "search-term" }
}

```

The handler executes `runExternalMcpToolCall` (aliased as `callExternalMcpTool`), which:

1. Builds an MCP client using `buildClient` and binds it to an `ExternalMcpOAuthProvider` transport.
2. Invokes `client.callTool({name, arguments})` inside `runExternalMcpRequestWithinDeadline` to enforce the `EXTERNAL_MCP_TOOL_CALL_TIMEOUT_MS` deadline.
3. Returns the raw result or a structured error (`tool_execution_failed`).

## Configuring Permissions and Access Control

OpenWork provides granular access control for external connections through two mechanisms:

**Credential Mode** – Set during connection creation:
- `shared`: Stores a single encrypted token accessible to all permitted members.
- `per_member`: Requires each user to authorize individually, storing tokens in `ConnectedAccountTable`.

**Access Grants** – Control who can use the connection via the `access` field supporting `orgWide`, `memberIds`, or `teamIds` scopes. Admins manage grants through:

```bash
POST /v1/mcp-connections/:id/access
{
  "access": { "orgWide": false, "memberIds": ["U123"], "teamIds": [] }
}

```

Permission checks occur in `memberCanUseExternalMcpConnection` and `listVisibleExternalMcpConnections`.

## Debugging with Diagnostics

OpenWork emits structured diagnostics for every MCP phase defined in `EXTERNAL_MCP_DIAGNOSTIC_PHASES`. Errors include a `referenceId`, `phase`, `code`, and actionable suggestions via `externalMcpDiagnosticForLog` and `externalMcpDiagnosticForResponse` in [`external-mcp-diagnostics.ts`](https://github.com/different-ai/openwork/blob/main/external-mcp-diagnostics.ts).

For example, if a tool's JSON schema exceeds 512 KB, the system throws `MCP_CATALOG_TOOL_SCHEMA_SIZE_LIMIT` with an operator hint to reduce schema complexity.

## Summary

- OpenWork uses **MCP (Model-Context-Protocol)** to standardize external service integration through the [`external-mcp-client.ts`](https://github.com/different-ai/openwork/blob/main/external-mcp-client.ts) core implementation.
- Connections support **preset configurations** for popular services or **custom URLs** validated against `externalMcpUrlSchema`.
- **OAuth flows** require calling the connect start endpoint, redirecting to the `authorizeUrl`, and completing the callback handled by `handleExternalMcpOAuthCallback`.
- Tools are retrieved via `listExternalMcpTools` with strict size validation, then executed through `callExternalMcpTool` with enforced timeouts.
- Access control uses **credential modes** (`shared` vs `per_member`) and **access grants** managed through `memberCanUseExternalMcpConnection`.
- **Structured diagnostics** in [`external-mcp-diagnostics.ts`](https://github.com/different-ai/openwork/blob/main/external-mcp-diagnostics.ts) provide detailed error reporting for troubleshooting integration failures.

## Frequently Asked Questions

### How do I add a custom MCP server not listed in the presets?

Supply the MCP server URL directly in the POST request body to `/v1/mcp-connections`. The `externalMcpUrlSchema` in [`mcp-connections.ts`](https://github.com/different-ai/openwork/blob/main/mcp-connections.ts) validates that the URL uses HTTP or HTTPS, contains no fragments, and includes no embedded credentials. You must also specify the `authType` (typically `oauth` or `none`) and `credentialMode` (`shared` or `per_member`).

### What happens if an external tool call times out?

OpenWork enforces the `EXTERNAL_MCP_TOOL_CALL_TIMEOUT_MS` deadline through `runExternalMcpRequestWithinDeadline`. If the MCP server fails to respond within this window, the system returns a structured error with code `tool_execution_failed` and a `referenceId` for debugging. Check the diagnostics in [`external-mcp-diagnostics.ts`](https://github.com/different-ai/openwork/blob/main/external-mcp-diagnostics.ts) to identify whether the timeout occurred during connection, discovery, or execution phases.

### Can I restrict which team members use a specific external connection?

Yes. Use the access control endpoints to configure the `access` field on any connection. You can set `orgWide: true` to allow all members, or specify arrays of `memberIds` and `teamIds` to restrict access. The functions `memberCanUseExternalMcpConnection` and `listVisibleExternalMcpConnections` enforce these permissions when agents attempt to list or call tools.

### Where are OAuth tokens stored and how are they secured?

Tokens are encrypted and stored either in the `ExternalMcpConnectionRow` table (for `shared` credential mode) or the `ConnectedAccountTable` (for `per_member` mode). The encryption occurs during the `completeExternalMcpAuth` phase after the OAuth callback. All token storage operations occur within the Den API routes defined in [`mcp-connections.ts`](https://github.com/different-ai/openwork/blob/main/mcp-connections.ts), ensuring secure handling of third-party credentials.