# How OpenWork Manages Provider Credentials and Handles Credentials Sync

> Learn how OpenWork manages provider credentials locally and syncs them with the Den control plane using MCP. Explore shared/per-member modes and automatic refresh.

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

---

**OpenWork manages provider credentials through a decentralized architecture that stores OAuth tokens locally in `~/.config/openwork/credentials.json` while synchronizing state with the Den control plane via Model-Control-Protocol (MCP), supporting both shared and per-member credential modes with automatic refresh handling and secure redaction.**

OpenWork's architecture isolates sensitive provider credentials from application logic to ensure security across distributed environments. The system implements a deterministic sync protocol that coordinates credential state between the desktop client, the Den control plane, and remote MCP connections. This approach ensures that API keys and OAuth tokens remain encrypted, versioned, and automatically refreshed without exposing secrets to logs or diagnostic streams.

## Credential Storage Architecture

OpenWork distributes credential storage across three distinct layers, each optimized for its specific security and accessibility requirements.

### Desktop Client Storage

The desktop client persists **OAuth access tokens**, refresh tokens, and API keys for providers like Anthropic and OpenAI in `~/.config/openwork/credentials.json`. In development environments, the client uses a mock keychain instead of the system credential store. All credential objects conform to the `EnterpriseMcpOAuthCredential` interface defined in [`packages/enterprise-mcp-client/src/client.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-client/src/client.ts), ensuring type safety through Zod schema validation before any data traverses the network.

### Den Control Plane Storage

The **Den control plane** maintains centralized, encrypted records for organization members and teams. These `CredentialRecord` objects enable cross-team synchronization while preserving access controls. The server manages credential continuity through the `credentialContinuity` mechanism implemented in [`packages/enterprise-mcp-mock-server/src/runtime/mock-server.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-mock-server/src/runtime/mock-server.ts), which preserves OAuth refresh tokens across client activations and prevents unnecessary re-authorization flows.

### MCP Remote Handling

Remote MCP connections transmit **credential metadata** (such as bearer tokens) over the transport layer without persisting them on the client. The `CredentialMode` enum—defined in [`packages/types/src/agent-context-diagnostics.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/agent-context-diagnostics.ts)—determines whether credentials travel as `shared` resources or `per_member` assignments, dictating how the sync protocol handles token refresh and invalidation.

## Credential Modes: Shared vs. Per-Member

OpenWork distinguishes two modes of credential handling through the `credentialMode` field declared in [`packages/types/src/den/mcp-connection-action.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/mcp-connection-action.ts).

**Shared mode** allows a single credential to be reused by every member of a team. This model suits organization-wide API keys or service accounts where individual identity is irrelevant to the provider.

**Per-member mode** assigns each user a distinct credential that may be refreshed independently. This approach is essential when providers enforce individual rate limits or when audit trails require user-specific access tokens.

The mode declaration (`credentialMode: z.enum(["shared","per_member"])`) drives how the desktop client reads, updates, and syncs credentials during the MCP lifecycle.

## The Credentials Sync Lifecycle

The credentials sync process follows a deterministic five-phase cycle that maintains consistency across distributed components.

### Initialization

When the desktop app launches, it reads persisted credentials from the local keychain or generates temporary mock credentials for development. The client advertises its credential mode and current revision to the MCP via the `engine-mcp-sync` diagnostic event, using the `syncStatus` enum defined in [`packages/types/src/agent-context-diagnostics.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/agent-context-diagnostics.ts).

### MCP Sync Call

The client issues an `mcp-sync` RPC to the Den server with a payload containing the current credential revision hash and the declared `credentialMode`. This request enables the server to detect stale or outdated tokens without transmitting the actual secret values.

### Server-Side Reconciliation

Den validates the incoming revision against its encrypted database, merges organization-wide updates, and calculates the authoritative credential state. If the server detects an expired token, it returns an `oauth_credential_expired` fault, prompting the client to initiate a fresh OAuth flow rather than attempting a silent refresh.

### Client Update

Upon successful sync, the client persists refreshed credentials—including new access and refresh tokens—and increments its local revision counter via `nextRevision()`. The updated `syncStatus` emits as part of the diagnostic stream, allowing downstream agents to detect credential continuity and retry tool executions that previously failed due to token staleness.

### Invalidation and Recovery

When tool execution receives a `401` response with `invalid_token`, the client records a `credential-invalidation` event, clears the stored credential from `~/.config/openwork/credentials.json`, and forces a re-authorization flow. The enterprise MCP client test suite in [`packages/enterprise-mcp-client/test/enterprise-mcp-client.test.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-client/test/enterprise-mcp-client.test.ts) validates this behavior through simulated `credential-invalidation` events.

## Security and Redaction

All outbound diagnostic text passes through a redaction pipeline defined by `diagnosticOriginSchema` in [`packages/types/src/agent-context-diagnostics.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/agent-context-diagnostics.ts). This pipeline automatically removes raw tokens, URLs, and absolute paths from logs, screenshots, and test tapes. The redaction guarantees that even diagnostic dumps or error reports never expose secrets, adhering to the principle that credentials remain inscrutable outside the encrypted storage boundaries.

## Implementation Examples

### Loading Credentials from Local Storage

The `CredentialStore` class in [`packages/enterprise-mcp-client/src/client.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-client/src/client.ts) abstracts keychain access:

```typescript
class CredentialStore {
  private credential?: EnterpriseMcpOAuthCredential;

  async load(): Promise<EnterpriseMcpOAuthCredential | undefined> {
    // In production this reads the real keychain; in dev we use a mock.
    return this.credential;
  }

  async save(newCred: EnterpriseMcpOAuthCredential) {
    this.credential = newCred;
    // Persist to disk or keychain as appropriate.
  }
}

```

### Performing an MCP Sync

The sync function constructs the revision payload and handles expiration faults:

```typescript
async function syncCredentials() {
  const local = await credentialStore.load();
  const payload = {
    credentialMode: "per_member",
    credentialRevision: local?.revision,
  };
  const response = await mcpRpc("engine-mcp-sync", payload);
  if (response.error === "oauth_credential_expired") {
    await startOAuthFlow(); // Re‑authorize the user
  } else {
    await credentialStore.save(response.credential);
  }
}

```

### Handling Token Invalidation During Tool Execution

The error handler wipes stale tokens and triggers re-authentication:

```typescript
async function runTool() {
  try {
    await mcpRpc("tool-execute", { /* … */ });
  } catch (e) {
    if (e.kind === "credential-invalidation") {
      await credentialStore.save(undefined); // wipe stale token
      await startOAuthFlow();               // force fresh login
    }
  }
}

```

## Summary

- OpenWork stores provider credentials locally in `~/.config/openwork/credentials.json` while synchronizing state with the Den control plane via MCP.
- **Credential modes** (`shared` and `per_member`) in [`packages/types/src/den/mcp-connection-action.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/mcp-connection-action.ts) determine multi-user access patterns and refresh strategies.
- The sync lifecycle uses revision hashes and `engine-mcp-sync` RPC calls to maintain consistency without transmitting secrets.
- Automatic **redaction** via `diagnosticOriginSchema` ensures tokens never appear in logs or diagnostic outputs.
- Expired tokens trigger `oauth_credential_expired` faults, while runtime invalidations emit `credential-invalidation` events that force re-authorization.

## Frequently Asked Questions

### Where does OpenWork store provider credentials on the local machine?

OpenWork persists credentials in `~/.config/openwork/credentials.json` on the desktop client, utilizing the system keychain in production environments and a mock store during development. This local storage maintains `EnterpriseMcpOAuthCredential` objects containing access tokens, refresh tokens, and revision counters, while the Den control plane maintains encrypted `CredentialRecord` entries for organizational synchronization.

### What is the difference between shared and per-member credential modes?

**Shared mode** utilizes a single credential across all team members, suitable for organization-wide API keys, while **per-member mode** assigns unique credentials to each user, enabling independent token refresh and individual audit trails. The mode is declared in the MCP connection configuration at [`packages/types/src/den/mcp-connection-action.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/mcp-connection-action.ts) and dictates how the sync protocol reconciles state between the client and Den server.

### How does OpenWork handle expired or invalid OAuth tokens?

When the Den server detects an expired token during the `mcp-sync` RPC, it returns an `oauth_credential_expired` error that triggers the client to initiate a fresh OAuth flow. During tool execution, a `401` response generates a `credential-invalidation` event, causing the client to clear the stored credential via `credentialStore.save(undefined)` and force immediate re-authorization, as validated in [`packages/enterprise-mcp-client/test/enterprise-mcp-client.test.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-client/test/enterprise-mcp-client.test.ts).