# WorkOS for AI agents

WorkOS is an AI-integrated workspace platform that runs on Skjld
Labs' own servers in Norway — a Notion-style tool for documents,
databases, tasks, projects, CRM, calendar, booking, time tracking,
meeting transcription, and sharing. The full data model is exposed
through a remote MCP server, so agents can read and write to a
workspace using standard MCP tools.

This page is also rendered as HTML at <https://workos.no/for-agenter>.

## TL;DR

- **MCP endpoint:** `https://workos.no/api/mcp`
- **Transport:** Streamable HTTP (POST + JSON-RPC 2.0). Stateless. GET returns 405.
- **Auth:** OAuth 2.1 (Authorization Code + PKCE). Bearer token in the `Authorization` header.
- **Scopes:** `read`, `write`.
- **Discovery:** `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`. Surfaced via 401 + `WWW-Authenticate`.
- **Token lifetime:** access 60 minutes, refresh 30 days.

## Connecting from an MCP client

Most MCP clients only need the endpoint URL. They follow the standard
discovery flow: a 401 response from `/api/mcp` advertises the
authorization server through the `WWW-Authenticate` header, the
client registers itself dynamically (RFC 7591), runs the
authorization-code-with-PKCE flow, and exchanges the resulting code
for an access token. The OAuth consent screen opens in the user's
browser; tokens persist after that.

### Claude Desktop

Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
on macOS or `%APPDATA%\Claude\claude_desktop_config.json` on Windows:

    {
      "mcpServers": {
        "workos": {
          "url": "https://workos.no/api/mcp"
        }
      }
    }

### Claude.ai (web)

Settings → Connectors → Add custom connector → paste the URL.

### Cursor

Edit `~/.cursor/mcp.json`:

    {
      "mcpServers": {
        "workos": {
          "url": "https://workos.no/api/mcp"
        }
      }
    }

### Cline (VS Code)

Cline must be told the transport is Streamable HTTP — its default is
SSE, which this server does not support:

    {
      "mcpServers": {
        "workos": {
          "type": "streamableHttp",
          "url": "https://workos.no/api/mcp"
        }
      }
    }

### Any other MCP client (manual OAuth flow)

If your client does not handle the OAuth 2.1 dance for you, follow
this sequence. **Important:** the WorkOS server cannot open a browser
for you. The *client* must build the authorize URL, present it to the
user, and capture the resulting authorization code — either via a
loopback HTTP listener (recommended) or by asking the user to paste
the code back in.

1. **Register the client.** POST `/api/oauth/register` with JSON:
   `{ "client_name": "...", "redirect_uris": ["..."], "scope": "read write" }`.
   You receive a `client_id`. Public clients only — no client secret.
   Allowed redirect URIs: any HTTPS URL, plus `http://localhost:*` and
   `http://127.0.0.1:*` for CLI/desktop clients.
2. **Generate PKCE pair.** Make a random `code_verifier` (43–128 chars,
   URL-safe). Compute `code_challenge = base64url(sha256(code_verifier))`.
3. **Build the authorize URL** (do not redirect or open it yourself —
   that is the user's job):
   `https://workos.no/oauth/authorize?response_type=code&client_id=...&redirect_uri=...&code_challenge=...&code_challenge_method=S256&scope=read+write&state=...`
4. **Show the URL to the user** and tell them to open it. Do not assume
   a browser will appear automatically — print the URL in plain text
   and wait. The user logs in (if needed), approves the scopes, and is
   redirected to your `redirect_uri` with `?code=...&state=...`.
5. **Capture the code.** Either:
   - **Loopback listener (recommended):** start a one-shot HTTP server
     on the `redirect_uri` you registered (e.g.
     `http://127.0.0.1:53682/callback`) before showing the URL, read
     `?code=` from the first incoming request, then shut the server
     down. Verify the `state` parameter matches what you sent.
   - **Manual paste:** register a redirect URI that renders the code
     visibly (a static page that prints `window.location.search`),
     and ask the user to paste it back into your CLI.
6. **Exchange the code for tokens.** POST `/api/oauth/token` with
   `grant_type=authorization_code`, `code`, `client_id`,
   `redirect_uri`, and `code_verifier`. You receive an
   `access_token` and a `refresh_token`.
7. **Call the MCP endpoint.** POST `/api/mcp` with
   `Authorization: Bearer <access_token>` and a JSON-RPC 2.0 payload.

To refresh: POST `/api/oauth/token` with
`grant_type=refresh_token`, the previous `refresh_token`, and
`client_id`. If refresh fails (token revoked or older than 30 days),
restart from step 3 — register again only if the `client_id` was lost.

#### Pseudocode for a CLI client

```
1. POST /api/oauth/register {
     client_name: "MyCLI",
     redirect_uris: ["http://127.0.0.1:53682/callback"],
     scope: "read write"
   } -> { client_id }

2. verifier = randombytes(64).base64url()
   challenge = sha256(verifier).base64url()
   state = randombytes(16).base64url()
   start_loopback_server(port=53682)  // listens once for /callback

3. print("Open this URL in your browser:")
   print("https://workos.no/oauth/authorize?response_type=code"
         + "&client_id=" + client_id
         + "&redirect_uri=http://127.0.0.1:53682/callback"
         + "&code_challenge=" + challenge
         + "&code_challenge_method=S256"
         + "&scope=read+write"
         + "&state=" + state)

4. wait for callback -> { code, state }
   verify state matches

5. POST /api/oauth/token {
     grant_type: "authorization_code",
     code, client_id,
     redirect_uri: "http://127.0.0.1:53682/callback",
     code_verifier: verifier
   } -> { access_token, refresh_token }

6. persist (client_id, refresh_token) in user-scoped config.
```

The `client_id` and `refresh_token` are the only durable values.
Re-running registration on every connect creates orphan client records
— register once, store the `client_id`, and reuse it.

## Tools

Call `tools/list` after `initialize` for the authoritative, current
list. 201 tools are grouped as:

- **Account:** `get_me`, `update_me`, `list_my_accounts`, `list_workspaces`, `get_workspace`, `list_workspace_members`, `list_effective_members`.
- **Pages:** `list_pages`, `search_pages`, `get_page`, `create_page`, `update_page`, `move_page`, `archive_page`, `restore_page`, `delete_page`.
- **Page blocks:** `append_blocks`, `insert_blocks_after`, `update_block`, `delete_blocks`, `replace_page_content`.
- **Page groups:** `list_page_groups`, `create_page_group`, `update_page_group`, `delete_page_group`, `reorder_page_groups`, `move_page_group_to_workspace`.
- **Databases:** `list_databases`, `create_database`, `get_database`, `update_database`, `delete_database`, `import_table_to_database`.
- **Database properties:** `add_db_property`, `update_db_property`, `remove_db_property`, `reorder_db_properties`.
- **Database rows:** `list_db_rows`, `create_db_row`, `update_db_cell`, `delete_db_row`, `move_db_row`.
- **Database views:** `list_db_views`, `create_db_view`, `update_db_view`, `delete_db_view`.
- **Tasks:** `list_my_tasks`, `list_upcoming_tasks`, `list_tasks_for_page`, `get_task`, `create_task`, `update_task`, `delete_task`, `toggle_task_complete`, `move_task`, `assign_task`, `unassign_task`, `task_stats_for_workspace`, `list_task_comments`, `add_task_comment`, `update_task_comment`, `delete_task_comment`, `list_task_lists`, `create_task_list`, `update_task_list`, `delete_task_list`.
- **CRM:** `list_crm_pipelines`, `get_crm_pipeline`, `get_crm_pipeline_summary`, `create_crm_pipeline`, `update_crm_pipeline`, `delete_crm_pipeline`, `reorder_crm_pipelines`, `ensure_default_crm_pipeline`, `add_crm_stage`, `update_crm_stage`, `delete_crm_stage`, `reorder_crm_stages`, `list_crm_companies`, `get_crm_company`, `search_crm_companies`, `create_crm_company`, `update_crm_company`, `delete_crm_company`, `list_crm_contacts`, `get_crm_contact`, `create_crm_contact`, `update_crm_contact`, `delete_crm_contact`, `list_crm_deals`, `get_crm_deal`, `create_crm_deal`, `update_crm_deal`, `delete_crm_deal`, `move_crm_deal`, `list_crm_activities`, `create_crm_activity`, `update_crm_activity`, `delete_crm_activity`, `list_crm_agreements`, `get_crm_agreement`, `create_crm_agreement`, `send_crm_agreement`.
- **Calendar:** `list_calendar_accounts`, `get_calendar_account`, `update_calendar_account`, `disconnect_calendar_account`, `list_calendar_events`, `get_calendar_event`, `create_calendar_event`, `update_calendar_event`, `delete_calendar_event`, `find_meeting_availability`, `list_upcoming_events`.
- **Booking:** `list_booking_pages`, `get_booking_page`, `create_booking_page`, `update_booking_page`, `delete_booking_page`, `publish_booking_page`, `unpublish_booking_page`, `list_booking_event_types`, `create_booking_event_type`, `update_booking_event_type`, `delete_booking_event_type`, `list_availability_rules`, `add_availability_rule`, `update_availability_rule`, `delete_availability_rule`, `list_bookings`, `get_booking`, `cancel_booking`, `update_booking_status`, `list_upcoming_bookings`.
- **Time tracking:** `list_time_projects`, `get_time_project`, `create_time_project`, `update_time_project`, `archive_time_project`, `unarchive_time_project`, `delete_time_project`, `list_time_entries`, `get_time_entry`, `create_time_entry`, `update_time_entry`, `delete_time_entry`, `bulk_create_time_entries`, `auto_stop_active_entry`, `get_project_time_stats`, `get_team_time_stats`, `get_timesheet_report`, `get_daily_time_report`, `get_invoice_data`.
- **Teams:** `list_teams`, `get_team`, `create_team`, `update_team`, `delete_team`, `add_team_member`, `remove_team_member`, `change_team_member_role`, `add_team_to_workspace`, `remove_team_from_workspace`, `list_workspace_teams`.
- **Meetings:** `list_meetings`, `get_meeting`, `create_meeting`, `update_meeting`, `append_transcript`, `generate_meeting_summary`, `list_meeting_templates`.
- **Comments:** `create_comment`, `update_comment`, `resolve_comment`, `list_comments`, `delete_comment`.
- **Sharing:** `create_share_link`, `list_share_links`, `revoke_share_link`.
- **Files:** `upload_image`, `upload_file`, `list_files`, `get_file`, `delete_file`.
- **API keys:** `list_api_keys`, `create_api_key`, `revoke_api_key`.
- **Notifications:** `list_notifications`, `get_notification`, `mark_notification_read`, `mark_all_notifications_read`, `delete_notification`.
- **Other:** `get_business_model`.

## Troubleshooting

- **405 Method Not Allowed on GET:** the client is trying SSE. This
  server uses Streamable HTTP (POST + JSON-RPC 2.0). Set
  `"type": "streamableHttp"` (Cline) or upgrade the client.
- **401 after about an hour:** the access token expired. Clients
  should refresh automatically; if yours does not, remove the
  connector and add it again to start a fresh OAuth flow.
- **Discovery returns 404:** the `.well-known` URLs are served by the
  Next.js app, not nginx. If a CDN or proxy intercepts them, point
  directly at `/api/mcp-metadata/authorization-server` and
  `/api/mcp-metadata/protected-resource`.
- **403 on write tools:** the token lacks the `write` scope. Re-run
  the OAuth flow requesting `read write`.
- **Empty workspace list:** the user has no workspace yet. Create one
  at <https://workos.no/create-workspace>.

## Links

- Homepage: <https://workos.no>
- Public agent page (HTML): <https://workos.no/for-agenter>
- LLM index: <https://workos.no/llms.txt>
- Support: hello@workos.no
