--- title: MCP description: Let AI clients like Claude drive your Openship instance as tools, over the Model Context Protocol. --- import { Step, Steps } from 'fumadocs-ui/components/steps'; **MCP (Model Context Protocol)** is a standard way for AI clients — Claude, Cursor, VS Code Copilot, and others — to call an app's features as *tools*. Point one of those clients at Openship and it can list your projects, kick off deployments, check logs, add domains, and more — using the exact same permissions you'd have yourself. The important part: it's not a second, looser door into Openship. **Every tool call re-runs the full auth and permission stack**, so an agent can only ever see and do what its credential is allowed to — nothing more. Everything below is set up under **Settings → MCP**. That tab shows the exact endpoint for *your* instance, a per-client "add" recipe (Claude Code, Codex, Cursor, VS Code, Claude Desktop, Windsurf, Zed), and the list of clients you've already connected. ## The endpoint There's one endpoint, and it's the same on self-hosted and Openship Cloud: ``` POST /api/mcp ``` So on your instance that's `https:///api/mcp`. A few things worth knowing up front: - It's a stateless **Streamable-HTTP JSON-RPC 2.0** endpoint — request in, response out. - It's **`POST`-only**. A `GET` returns `405` (there's no server→client stream to open). - **No batching** — send one JSON-RPC message per request, not an array. You rarely type this URL by hand. **Settings → MCP** shows it pre-filled and gives you a copy button. ## Connect a client There are two ways to authenticate, and most clients pick one for you. Use the tab that matches your client. Openship is a standards-compliant **OAuth 2.1** MCP server, so capable clients need nothing but the URL. ### Paste the endpoint into your client Give the client your endpoint — `https:///api/mcp`. In Claude Code that's one command: ```bash claude mcp add --transport http openship https:///api/mcp ``` Codex registers the server and authorizes it as two commands: ```bash codex mcp add openship --url https:///api/mcp codex mcp login openship ``` Other clients take it through their own UI; **Settings → MCP** has the exact snippet for each one. ### Approve it in the browser For most clients this happens on the first connect: a browser window opens by itself. With Codex it's the explicit `codex mcp login openship` above — the server is registered without it, but every call comes back unauthorized until you run it. Either way, that browser page is where you **choose what the client may do** — read-only or full control, and either all resources or a specific set of projects, servers, and repositories. No credential to copy or paste. ### You're connected The client can now call tools within exactly the scope you approved. The connection shows up under **Settings → MCP → Connected clients**, where you can disconnect it at any time. Your choices are stored as the client's grant — the same grant model as a scoped [personal access token](/docs/api/tokens). The client can never exceed what you granted, or what you can access yourself. The Codex **CLI, desktop app, and IDE extension** all read the same local MCP configuration, so adding Openship once covers all three. Restart any of them that were already running — they read the config at startup. A **hosted** Codex task is a different environment. It doesn't inherit your machine's MCP config, and it can only reach `https:///api/mcp` if that environment has network access to your instance. For a private or LAN-only self-hosted Openship, it won't — the endpoint has to be reachable from wherever the agent actually runs. Openship Cloud, or a self-hosted instance on a public hostname, is reachable; a `localhost`/private-network endpoint is not. See [remote access](/docs/getting-started/installation) for exposing an instance deliberately. For clients without OAuth support — or when you want a fixed, narrowly-scoped credential in a config file — authenticate with a **personal access token** as a bearer credential. There's no separate MCP credential. Create the token under **Settings → Tokens** (see the [Tokens API](/docs/api/tokens)), then drop the endpoint and token into your client's config: ```json { "mcpServers": { "openship": { "url": "https:///api/mcp", "headers": { "Authorization": "Bearer opsh_pat_…" } } } } ``` For Claude Code, add the header on the command: ```bash claude mcp add --transport http openship https:///api/mcp \ --header "Authorization: Bearer opsh_pat_…" ``` Mint a **read-only** or **scoped** token for agents rather than handing over your full access. A read-only token limits the agent to read tools; a scoped token confines it to exactly the projects, servers, and repositories you grant — even below your own role. ## Every call is re-checked This is the heart of the security model, and it's why MCP access is as safe as your token: - Authenticating once resolves your **capability**, but that's used only to **filter `tools/list`** — so a client isn't shown tools its token could never use. - The real gate runs on **every `tools/call`**: the endpoint builds an internal request, forwards your bearer, and dispatches it through the same routes, validation, auth middleware, and per-resource permission check as a normal HTTP call. The practical upshot: a read-only token can only reach read tools, and a scoped token stays boxed into its granted resources. A scoped token isn't even *shown* the org-wide list and create tools — `get_projects` (list every project) simply won't appear in its `tools/list`; it sees only the per-resource tools for the resource types it was granted. And if a `tools/call` touches something outside that scope, it comes back `404` — the scope doing its job. For the full protocol details (JSON-RPC methods, negotiation, error codes), see the [MCP API reference](/docs/api/mcp). The **organization boundary** applies to MCP exactly as it does to a browser session: because every call runs through the same per-resource permission check, an MCP client acts only within organizations you belong to and can never reach another org's projects, servers, or secrets — a resource outside your membership comes back `404`, just as it would in the dashboard. See [Isolation](/docs/security/isolation). ## Which tools map to which API Tools aren't a separate feature set — each one **maps onto a permission-tagged REST route**, and the tool name is built from that route's method and path. So `get_projects` is `GET /api/projects`, `post_domains` is `POST /api/domains`, and `patch_projects_by_id` is `PATCH /api/projects/:id`. Call `tools/list` to see the exact set your token can use. Not every route is a tool: a route becomes one only when it **opts in**, so the set is a curated slice of the API rather than the whole surface. Each exposed tool also carries two hints a client can surface — `readOnlyHint` (the tool only reads) and `destructiveHint` (it deletes or tears something down) — so an agent UI can flag a destructive call before it runs. | Tool name (examples) | REST route | Reference | |---|---|---| | `get_projects`, `patch_projects_by_id` | `/api/projects` | [Projects API](/docs/api/projects) | | `get_deployments`, `post_deployments_build_access` | `/api/deployments` | [Deployments API](/docs/api/deployments) | | `get_projects_by_id_services`, `post_projects_by_id_services` | `/api/projects/:id/services` | [Services API](/docs/api/services) | | `get_domains`, `post_domains` | `/api/domains` | [Domains API](/docs/api/domains) | | `get_github_repos` | `/api/github` | [GitHub API](/docs/api/github) | | `get_analytics` | `/api/analytics` | [Analytics API](/docs/api/analytics) | | `get_notifications_channels` | `/api/notifications` | [Notifications API](/docs/api/notifications) | | `get_settings`, `patch_settings_build_mode` | `/api/settings` | [Settings API](/docs/api/settings) | | `get_projects_by_projectId_backup_policies`, `post_backup_policies_by_policyId_run` | `/api/backups` | [Backups API](/docs/api/backups) | | `get_cloud_status`, `get_cloud_workspaces` | `/api/cloud` | [Cloud API](/docs/api/cloud) | | `get_jobs`, `post_jobs_by_key_run` | `/api/jobs` | [Jobs guide](/docs/guides/jobs) | | `get_apps_catalog`, `post_apps` | `/api/apps` | *(REST reference pending)* | | `get_updates`, `post_updates_scan` | `/api/updates` | [Updating Openship](/docs/guides/updating) | The `tokens`, `auth`, and `mcp` modules are hard-blocked from ever becoming tools — a full-access MCP token can't mint itself a fresh credential or escape its own scope. Manage tokens and connected clients from the dashboard or the [Tokens API](/docs/api/tokens), not from an agent. ## Guided flows Some tasks are a chain of calls in a specific order — deploying a repo, uploading a folder, installing a catalog app. The server ships a small set of **prompts** (`prompts/list` / `prompts/get`) that spell those chains out step by step, referencing the exact tools to call. Compatible clients surface them as slash-commands or starters, so an agent can pick a flow and follow it rather than reverse-engineering the order from the flat tool list. See the [MCP API reference](/docs/api/mcp#guided-flows-prompts) for the full catalog. ## Verify from the terminal To confirm a token works and see what it can do, ask for the tool list: ```bash curl -s https:///api/mcp \ -H "Authorization: Bearer opsh_pat_…" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` This returns exactly the tools that token is allowed to call. ## If something goes wrong There's no bearer credential, or it's expired or revoked. The response includes a `WWW-Authenticate` header so OAuth clients can start discovery; if you're using a static token, mint a fresh one under **Settings → Tokens** and update your client config. Expected. This is a request/response server with no server→client stream, so `GET /api/mcp` isn't supported. Always `POST` your JSON-RPC message. `tools/list` worked but a specific `tools/call` reports `404` — that's the token's scope working as intended. It wasn't granted that resource. Re-authorize the client (or mint a token) with the resource included. ## Manage connected clients Every OAuth client you've approved is listed under **Settings → MCP → Connected clients**, tagged with its access level (read-only or full control) and how many resources it can reach. **Disconnect** revokes the client's issued tokens immediately and drops its consent, so it has to re-authorize next time. The same list and controls are available programmatically via the [Tokens API](/docs/api/tokens). ## What next?