ConnectorManager
MCP OAuth flows and connector instance management
packages/desktop/src/main/connector-manager.ts handles everything related to adding, authenticating, and removing MCP connector instances in the desktop app.
ConnectorType
ConnectorType is an interface for provider catalog entries (not a string union):
| Field | Type | Description |
|---|---|---|
serverId | string | Provider slug (e.g. "notion", "google-drive") |
displayName | string | Human-readable name |
description | string | Short description |
iconUrl | string? | Provider logo URL |
category | string? | Category for grouping |
authMode | string | SERVER_MEDIATED / REMOTE_OAUTH / API_KEY / NONE |
mcpTransport | string | STDIO / SSE / HTTP |
mcpUrl | string? | Remote server URL |
ConnectorInfo
The type returned by listInstances:
| Field | Description |
|---|---|
id | Database record ID |
instanceId | Unique ID for this connector instance |
serverId | Provider ID (e.g. "notion", "google-drive") |
label | Human-readable name |
hosting | CLOUD / LOCAL |
transport | STDIO / SSE / HTTP |
status | CONNECTED / DISCONNECTED / CONNECTING / ERROR |
lastError | Error message if status is ERROR |
lastUsedAt | Last usage timestamp |
createdBy | userId who created this instance |
createdAt | Creation timestamp |
Key Methods
getAvailableConnectors()
Fetches the public connector provider catalog from GET /api/v1/connector-providers. Returns all enabled providers with display metadata (name, icon, category, auth mode). No authentication required.
listInstances(token, orgId)
Returns the combined list of connector instances:
- Fetches org-level instances from
GET /api/v1/connectors(cloud DB). - For
REMOTE_OAUTHandNONEconnectors, also checks the local registry (~/.cosmo/mcp.json). - Merges both lists, deduplicating by
instanceId.
addInstance(token, orgId, serverId, label)
Starts the appropriate auth flow based on the provider's authMode:
SERVER_MEDIATED
- Calls
POST /api/v1/mcp-oauth/startto get anauthUrl. - Opens the URL in the system browser.
- The backend handles the OAuth exchange and stores credentials.
- Polls
GET /api/v1/mcp-oauth/poll?state=...until credentials are ready. - Creates the connector record via
POST /api/v1/connectors.
REMOTE_OAUTH
- Calls
ConnectorManager.startRemoteOAuth(serverId, instanceId). - The
OAuthCallbackServerbinds tohttp://localhost:52625/oauth/callback. - Opens the MCP server's authorization URL in the browser.
- Waits for the redirect with the authorization code.
- Exchanges the code for tokens using the MCP server's token endpoint.
- Saves tokens to
userData/mcp-tokens/{instanceId}.json. - Saves OAuth client info to
userData/mcp-client/{instanceId}.json. - Creates the connector record in the backend with
hosting: CLOUD.
API_KEY
- The renderer collects the API key from the user.
addInstancestores it viaPOST /api/v1/connectors/:id/credentials.
NONE
Connector is created immediately with no credential exchange.
removeInstance(token, orgId, instanceId)
- Deletes the connector from the backend via
DELETE /api/v1/connectors/:instanceId. - Removes local token files (
mcp-tokens/{instanceId}.json,mcp-client/{instanceId}.json). - Removes the entry from the local MCP registry.
OAuthCallbackServer
packages/desktop/src/main/oauth-callback-server.ts runs a local HTTP server on port 52625 (fixed, so it can be pre-registered as a redirect URI with OAuth providers).
| Method | Description |
|---|---|
start() | Binds to port 52625 |
waitForCode(state?) | Returns Promise<code> — resolves when the browser redirects back |
cancelPending(state?) | Cancel without rejecting |
stop() | Shut down the server |
The callback endpoint is GET /oauth/callback?code=...&state=.... After the redirect it shows a success or error page in the browser.
Timeout: 2 minutes per auth attempt.
Token Persistence
Remote OAuth tokens are stored per-instance in the app's userData directory:
All files live in the single mcp-tokens/ directory:
userData/
└── mcp-tokens/
├── {instanceId}.json ← OAuth tokens { accessToken, refreshToken, expiresAt }
└── {instanceId}-client.json ← OAuth client info { clientId, clientSecret, ... }saveRemoteTokens(instanceId, tokens) / loadRemoteTokens(instanceId) handle token reads and writes.
saveClientInfo(instanceId, info) / loadClientInfo(instanceId) handle client info reads and writes.