kind: Integration
What it is
kind: Integration declares one connected MCP server — either a
remote streamable-http endpoint (e.g. Linear’s hosted MCP) or a
locally spawned stdio process (e.g. npx -y @some/mcp-server). It is
the standalone authoring surface for the case where you want one
integration declared once and shared across many crews
(scope: workspace), or want to declare a crew-scoped integration
outside the bulkier kind: Crew document.
The legacy inline mcp_servers: block nested under a crew (see
Crew and Workspace) still works and
remains the most ergonomic shape for bundling integrations with a crew
definition. kind: Integration is the inverse: declare once, scope
explicitly.
The kind is implemented in internal/manifest/kinds/integration.go.
Scope picks the table
A singlescope: discriminator chooses where the row lands:
scope | Table | Who sees it |
|---|---|---|
workspace (default) | workspace_mcp_servers | every crew in the workspace |
crew | crew_mcp_servers | only agents of the named crew |
crew scope requires crew_slug; workspace scope rejects
it (a crew_slug under workspace scope is treated as an authoring
mistake and fails loudly).
slug == name
metadata.slug MUST equal metadata.name — the server keys MCP-server
uniqueness on name within the (workspace, crew) tuple, but every
cross-kind reference uses slug, so the manifest forces the two to
match (the same convention Label uses).
env vs env_mapping
Two env-related maps both land in the same env_json column:
env— plain static environment variables (e.g.NODE_ENV: production). The value is the literal string the MCP process sees.env_mapping— the credential-indirection layer. Keys are the env-var the MCP server expects; values are the workspace credential’sname(conventionally identical, but can differ — e.g.{GITHUB_PERSONAL_ACCESS_TOKEN: GH_TOKEN}). At agent run time the resolver looks up each credential by name and substitutes the value before the MCP process starts.
env wins (a literal value beats a credential
reference for the same key).
YAML schema
Field reference
| Field | Type | Required | Notes |
|---|---|---|---|
metadata.name | string | yes | MCP server name; uniqueness key within (workspace, crew). |
metadata.slug | string | yes | MUST equal metadata.name. |
spec.scope | enum | no | workspace (default) | crew. |
spec.crew_slug | string | cond. | Required when scope: crew; must be empty when scope: workspace. |
spec.display_name | string | no | UI label. Defaults to metadata.name server-side (and the manifest mirrors that default so the round-trip diff is stable). |
spec.transport | enum | yes | streamable-http | stdio. The server has no sensible default for “what kind of MCP server is this”. |
spec.endpoint | string | cond. | Required for streamable-http; ignored otherwise. |
spec.command | string | cond. | Required for stdio; ignored otherwise. |
spec.args | []string | no | stdio positional args. Marshaled into args_json. No empty entries. |
spec.env | map[string]string | no | Literal env vars. No empty keys. |
spec.env_mapping | map[string]string | no | Credential references (env-var → credential name). No empty keys or values. |
spec.icon | string | no | lucide-react slug. |
spec.enabled | bool | no | Runtime connect toggle. Defaults to true; pointer-typed so an absent field is distinguishable from false. |
Examples
Remote (streamable-http), workspace scope
Local (stdio), crew scope
Static env plus a credential reference
CLI reference
There is no dedicatedcrewship integration per-kind admin command —
integrations are authored through the manifest pipeline (or installed
as part of a Connector / Recipe). The
relevant CLI surface is the global apply/export flow:
| Command | Description |
|---|---|
crewship apply --file integration.yaml | Declarative create/update (workspace or crew scope). |
crewship apply --dir ./manifests/ | Walk a directory; crews resolve before crew-scoped integrations. |
crewship apply --file integration.yaml --dry-run | Plan only — surfaces a dangling crew_slug and shows scope-change replaces. |
crewship export workspace | Round-trip — emits one document per integration across both scopes. |
REST endpoint mapping
| Manifest field | POST/PATCH body field | Notes |
|---|---|---|
metadata.name | name | |
spec.display_name | display_name | Defaults to name. |
spec.transport | transport | |
spec.endpoint | endpoint | streamable-http only. |
spec.command | command | stdio only. |
spec.args | args_json | JSON-encoded string on the wire. |
spec.env + spec.env_mapping | env_json | Merged map, JSON-encoded string. |
spec.icon | icon | |
spec.enabled | enabled |
| Verb | Workspace scope | Crew scope |
|---|---|---|
POST | /api/v1/integrations | /api/v1/crews/{crewId}/integrations |
GET | /api/v1/integrations | /api/v1/crews/{crewId}/integrations |
PATCH | /api/v1/integrations/{id} | /api/v1/crews/{crewId}/integrations/{id} |
DELETE | /api/v1/integrations/{id} | /api/v1/crews/{crewId}/integrations/{id} |
Validation rules
IntegrationDocument.Validate enforces:
apiVersion/kind, when set, equalcrewship/v1/Integration.metadata.nameandmetadata.slugare non-empty, andslug == name.transportis required and one ofstreamable-http|stdio.streamable-httprequires a non-emptyendpoint;stdiorequires a non-emptycommand.scope, when set, isworkspace|crew.crew_slugis required iffscope: crewand rejected underscope: workspace.env/env_mappinghave no empty keys;env_mappinghas no empty values;argshas no empty entries.- When
WorkspaceContextcarries crew data, a crew-scopedcrew_slugmust reference a declared or remote crew.
Apply behavior
ApplyUpsert (default)
- No remote on this scope →
ActionCreate: POST to the workspace or crew endpoint. - Remote on the matching scope, fields drift →
ActionUpdate: a sparse PATCH.args_json/env_jsonare compared after JSON normalisation so key-reordering doesn’t produce phantom drift. - Remote matches exactly →
ActionUnchanged.
Scope change (workspace ↔ crew)
The two scopes are different tables and there is no “move” endpoint, so a changed scope emits aDelete + Create pair, both visible in
the dry-run with a “scope change” note. The delete cascades any agent
bindings on the old row — review the plan before re-running with
--yes.
Round-trip via export
crewship export workspace calls ExportIntegrations, which walks
both the workspace scope and every crew’s crew scope, decoding
args_json back into spec.args and env_json back into spec.env.
Env lossiness: the server has no column distinguishing literal
env from credential-reference env_mapping (both live in
env_json), so every entry comes back under spec.env on export.
If you need the env_mapping shape preserved, keep your source YAML as
the source of truth and re-export to a side file rather than
overwriting the original. Output is sorted by scope, then crew, then
slug for stable diffs.
See also
- Crew — can declare integrations inline via
mcp_servers:. - Connector — install-only OAuth connectors (Linear, GitHub, …).
- Recipe — catalog installs that may bundle integrations.
- Backend:
internal/api/workspace_integrations.go,internal/api/crew_integrations.go. - This kind’s Go implementation:
internal/manifest/kinds/integration.go.