---
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?