MCP Integration
Strøm exposes a Model Context Protocol (MCP) endpoint that allows AI agents like Claude, Cursor, and other MCP-compatible tools to interact with your workflow orchestration server using a standardized interface.
Overview
Section titled “Overview”The MCP endpoint provides a set of tools for AI agents to:
- List and query available workspaces and tasks
- Retrieve detailed task definitions and execution history
- Execute tasks and monitor job status in real-time
- View logs and troubleshoot failed steps
- Cancel running or pending jobs
This enables AI agents to autonomously orchestrate complex workflows, answer questions about your infrastructure, and help debug issues.
Enabling MCP
Section titled “Enabling MCP”Add the mcp section to your server-config.yaml:
mcp: enabled: trueThe MCP endpoint is served at http://localhost:8080/mcp using Streamable HTTP transport (JSON response mode). When auth is configured on the server, MCP clients must send Authorization: Bearer <token> headers with each request.
Host allow-list (public deployments)
Section titled “Host allow-list (public deployments)”The MCP Streamable-HTTP transport validates the request Host header to prevent
DNS-rebinding attacks, accepting loopback hosts by default. Strøm automatically
allow-lists the host from auth.base_url, so a standard reverse-proxy deployment
works out of the box (this is also why auth.base_url is required when
mcp.enabled: true).
If the server is reachable under additional hostnames (an alias domain, a
second ingress), list them explicitly — otherwise requests to those hosts are
rejected before authentication with a disallowed Host header warning:
mcp: enabled: true # Extra hostnames beyond auth.base_url's host and loopback. Include a port # only for non-default ports, e.g. "stroem.internal:8080". allowed_hosts: - alias.example.com # Browser Origin allow-list. Leave empty (default) for non-browser clients # like Claude Code; set it only when browser-based MCP clients connect. allowed_origins: []Available Tools
Section titled “Available Tools”The MCP server provides eleven tools for interacting with Strøm:
| Tool | Description | Parameters |
|---|---|---|
list_workspaces |
List all workspaces and their task counts, with the server-config display_name / description when set |
none |
list_tasks |
List tasks in a workspace | workspace? (optional; lists all if omitted) |
get_task |
Get detailed task definition | workspace, task_name |
execute_task |
Execute a task and return job ID | workspace, task_name, input? |
get_job_status |
Get current job status with step details | job_id |
get_job_logs |
The end of a job’s log (last 256 KiB), formatted | job_id, step?, tail_bytes? |
list_jobs |
List recent jobs with optional filters | workspace?, task_name?, status?, limit? |
cancel_job |
Cancel a running or pending job | job_id |
list_artifacts |
List a job’s artifacts (name, content type, size, download URL) | job_id |
get_artifact |
Fetch one artifact’s content | job_id, name |
create_upload_url |
Get a single-use URL to upload a file for a file input field |
workspace, task_name, file_name |
When the log is longer than the tail, the result ends with [truncated: showing the last 256.0 KiB of up to 83.3 MiB — earlier lines omitted].
get_artifact returns content shaped to the artifact’s type: textual artifacts
(text, JSON, YAML, XML) come back as inline text, image/* artifacts as image
blocks the agent can view, and any other binary (PDF, archives, …) as a base64
blob resource. Artifacts larger than 1 MiB are not inlined — download them via
the HTTP artifact API (the URL is in list_artifacts) instead.
Sending files
Section titled “Sending files”A task can have file input fields. get_task shows them as type: file. There are two ways to give such a field a file with execute_task.
Inline, for small files. Put an object in input instead of a value. The decoded content must not be more than 1 MiB (input_files.mcp_inline_max_bytes):
Call: execute_task( workspace="default", task_name="import-report", input={ "data_file": { "name": "report.csv", "content": "id,total\n1,10\n" } })Use content_base64 instead of content for binary data. The object must have exactly the two keys name and content (or content_base64); another shape or invalid base64 gives Input field '…': the file cannot be used. A file over the limit gives Input field '…': inline content is larger than N bytes. The server stores the file as your upload (content type application/octet-stream) and then creates the job.
Upload URL, for larger files and for agents that have a shell:
Call: create_upload_url(workspace="default", task_name="import-report", file_name="report.csv")Returns: { "upload_url": "https://strom.example.com/api/uploads/…", "method": "POST", "max_bytes": 104857600, "expires_at": "2026-10-30T12:15:00Z" }Then POST the raw bytes to upload_url with no Authorization header (curl --data-binary @report.csv "$upload_url"). The URL token is the credential. The URL works once and expires after 15 minutes (input_files.upload_url_ttl_secs). The token is used up when the request arrives, before the body is read: if the upload fails, ask for a new URL. A used, expired or unknown URL answers 404 Upload URL not found. When the upload arrives, the server checks again that the URL’s user may still run the task and that the task still has a file field. If not, it answers the same 404 Upload URL not found and the URL stays used. The answer holds the file_id. Pass it as the field value in execute_task.
create_upload_url needs auth.base_url in the server config. Without it the tool answers upload URLs need auth.base_url; send the file inline or use the REST upload. It needs the Run permission on the task (View only gives Insufficient permissions: execute requires Run access), a task with a file field (else task has no file input) and a usable file_name (else invalid file name).
Authentication
Section titled “Authentication”When auth is configured on your Strøm server, MCP clients must authenticate using the standard HTTP Authorization header:
Authorization: Bearer <token>Strøm supports two token types:
- JWT tokens: Long-lived (30 days) user session tokens issued at login. Supported on both API keys and user logins.
- API keys: Long-lived programmatic tokens with the
strm_prefix. Ideal for MCP integrations and CI/CD pipelines. Manage API keys via the Settings page or/api/auth/api-keysendpoint.
MCP clients without an Authorization header can still connect if auth is disabled on the server.
OAuth 2.1 (MCP spec 2025-06-18)
Section titled “OAuth 2.1 (MCP spec 2025-06-18)”When auth is enabled, Strøm acts as both the OAuth 2.1 Resource Server for /mcp and the Authorization Server that issues tokens for it. A spec-conformant MCP client connects with no manual configuration — it discovers the auth flow on the first 401.
The flow:
- Client calls
/mcpwithout a token. - Strøm responds
401 UnauthorizedwithWWW-Authenticate: Bearer resource_metadata="https://.../.well-known/oauth-protected-resource". - Client fetches that document — RFC 9728 Protected Resource Metadata — which advertises the authorization server URL.
- Client fetches
/.well-known/oauth-authorization-server— RFC 8414 — to learn the endpoints, grant types, and PKCE methods. Strøm advertises:response_types_supported: ["code"]grant_types_supported: ["authorization_code", "refresh_token"]code_challenge_methods_supported: ["S256"](OAuth 2.1 forbidsplain)token_endpoint_auth_methods_supported: ["none", "client_secret_post"]
- Client self-registers via
POST /oauth/register— RFC 7591 Dynamic Client Registration. The response includes aclient_id(andclient_secretif the client requested confidential auth). - Client opens the user’s browser to
/oauth/authorize?response_type=code&client_id=...&redirect_uri=...&code_challenge=...&code_challenge_method=S256&scope=mcp&resource=https://.../mcp&state=.... - Strøm validates the request, then redirects to
/consent?...— the React consent screen. If the user isn’t logged in, the SPA bounces them through/login?next=/consent?...first. - The user clicks Allow. The SPA
POST /api/oauth/consentmints a single-use authorization code and returns the final redirect URL, which the browser then navigates to:redirect_uri?code=<code>&state=<state>. - Client exchanges the code at
POST /oauth/tokenwith the matchingcode_verifier. Strøm verifies PKCE (BASE64URL(SHA256(code_verifier)) == stored code_challenge), then mints:- An access token — JWT, 1 hour TTL,
audbound to the canonical/mcpURL (RFC 8707 Resource Indicators). - A refresh token — opaque, DB-backed, 30 day TTL, rotated on each use (OAuth 2.1 §4.13).
- An access token — JWT, 1 hour TTL,
- Client uses the access token as the
Bearervalue on/mcp. The MCP middleware enforces theaudclaim — a token issued for a different resource is rejected, even if signed with the same secret.
Dynamic Client Registration (DCR)
Section titled “Dynamic Client Registration (DCR)”POST /oauth/register is public — any client can register without an admin step. To limit abuse:
- Each registration expires after 30 days. Long-running integrations re-register on the first request after expiry; the cost is one automatic POST.
- Maximum 5
redirect_urisper client. - Only
https://...redirects are allowed, except forhttp://localhost,http://127.0.0.1, andhttp://[::1](loopback exceptions per OAuth 2.1 §4.1). - Only the
authorization_code+refresh_tokengrants and themcpscope are permitted.
Audience binding
Section titled “Audience binding”Tokens issued by /oauth/token carry an aud claim equal to the canonical resource URL (e.g. https://stroem.example.com/mcp). The MCP middleware verifies this on every request. A token leaked from one resource server cannot be replayed against another, even if both trust the same issuer.
Legacy strm_* API keys and login-flow JWTs predate the OAuth flow and have no aud claim — the middleware accepts them unchanged. New integrations should prefer the OAuth flow.
Configuration
Section titled “Configuration”Set auth.base_url in server-config.yaml so the discovery documents and aud claim resolve to the canonical public URL:
auth: jwt_secret: "..." refresh_secret: "..." base_url: "https://stroem.example.com"Without base_url, Strøm falls back to the request Host (respecting X-Forwarded-Host / X-Forwarded-Proto) — fine for local dev, brittle behind multiple proxy layers.
Claude Desktop with auto-discovery
Section titled “Claude Desktop with auto-discovery”A spec-conformant MCP client only needs the /mcp URL. Auth handles itself:
{ "mcpServers": { "stroem": { "url": "https://stroem.example.com/mcp", "transport": "streamable-http" } }}The first connection triggers the 401 → discovery → DCR → browser consent → token exchange flow described above. After the user clicks Allow once, Claude Desktop refreshes the access token silently for as long as the refresh token is valid (30 days).
Connecting from Claude Desktop
Section titled “Connecting from Claude Desktop”To connect Claude Desktop to your Strøm server, add the MCP configuration to claude_desktop_config.json:
On macOS/Linux, edit ~/.claude/claude_desktop_config.json:
{ "mcpServers": { "stroem": { "url": "http://localhost:8080/mcp", "transport": "streamable-http" } }}On Windows, edit %APPDATA%\Claude\claude_desktop_config.json:
{ "mcpServers": { "stroem": { "url": "http://localhost:8080/mcp", "transport": "streamable-http" } }}If your Strøm server requires authentication, add the token:
{ "mcpServers": { "stroem": { "url": "http://localhost:8080/mcp", "transport": "streamable-http", "headers": { "Authorization": "Bearer YOUR_API_KEY_OR_JWT_TOKEN" } } }}Restart Claude Desktop after updating the config. The Strøm tools will appear in Claude’s tool palette.
Example: Typical Agent Workflow
Section titled “Example: Typical Agent Workflow”Here’s a typical interaction sequence between an AI agent and Strøm via MCP:
1. Discover available workspaces
Section titled “1. Discover available workspaces”Call: list_workspacesReturns:[ { "name": "production", "display_name": "Production", "description": "Customer-facing deploys and jobs", "task_count": 12 }, { "name": "staging", "task_count": 8 }]2. List tasks in a workspace
Section titled “2. List tasks in a workspace”Call: list_tasks(workspace="production")Returns:[ { "name": "deploy-api", "description": "Deploy API server" }, { "name": "run-tests", "description": "Run test suite" }, { "name": "backup-db", "description": "Backup production database" }]3. Get task details
Section titled “3. Get task details”Call: get_task(workspace="production", task_name="deploy-api")Returns:{ "name": "deploy-api", "description": "Deploy API server", "input": { "version": { "type": "string", "required": true }, "environment": { "type": "string", "default": "staging" } }, "flow": [ { "name": "pull-image", "action": "docker-pull", ... }, { "name": "start-container", "action": "docker-run", "depends_on": ["pull-image"] } ]}4. Execute the task
Section titled “4. Execute the task”Call: execute_task( workspace="production", task_name="deploy-api", input={ "version": "v1.2.3" })Returns:{ "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}input must be a JSON object mapping the task’s field names to values. If your
MCP client serializes it as a JSON-encoded string (some clients do), the server
parses it back into an object automatically; anything that isn’t a JSON object
is rejected with an error rather than silently run with default values.
5. Monitor job status
Section titled “5. Monitor job status”Call: get_job_status(job_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890")Returns:{ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "running", "started_at": "2025-03-12T14:30:00Z", "steps": [ { "name": "pull-image", "status": "completed", "duration_secs": 45 }, { "name": "start-container", "status": "running", "started_at": "2025-03-12T14:30:50Z" } ]}6. Retrieve logs
Section titled “6. Retrieve logs”Call: get_job_logs(job_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890")Returns:[pull-image] Pulling image us-docker.io/stroem/api:v1.2.3...[pull-image] Pull complete (45.2s)[start-container] Starting container stroem-api-prod...[start-container] Container started with ID abc123def456[start-container] Health check: passedThis shows the tail of the log (last 256 KiB by default); pass tail_bytes for more history, up to log_storage.read.tail_max_bytes.
get_job_status masks secret values (••••••) in what it returns — chiefly
the steps’ error_message — the same way as GET /api/jobs/{id}, including
the secrets of any pinned commit
the job is connected to. When those secrets cannot be loaded at the moment, the tool
returns an error, redaction set unavailable, retry, instead of the job;
call it again later. When a pinned commit can never be loaded again, the job
is returned with every content string masked. A job that runs on a git ref
carries its ref, in get_job_status and in list_jobs.
get_job_logs returns log lines as the steps printed them; log lines are not
masked.
Use Cases
Section titled “Use Cases”AI-Assisted Deployments
Section titled “AI-Assisted Deployments”Agents can handle multi-step deployments with approval gates, automated rollbacks on health check failures, and notification of relevant teams.
Infrastructure Troubleshooting
Section titled “Infrastructure Troubleshooting”Agents can query job logs, identify failing steps, and suggest remediation steps based on error messages and logs.
Automation and CI/CD
Section titled “Automation and CI/CD”External CI/CD systems can use MCP to trigger Strøm tasks, poll job status, and integrate results into larger pipelines.
Interactive Exploration
Section titled “Interactive Exploration”Agents can explore available tasks, their input requirements, and past execution patterns to answer operational questions.
Access Control
Section titled “Access Control”When ACL is configured on your Strøm server, MCP tools enforce the same permission rules as the REST API:
- List tools (
list_workspaces,list_tasks,list_jobs): Filter results to only show resources the user has access to. Tasks withDenypermission are hidden.list_jobsfilters before applyinglimit, so a page holds up tolimitjobs you may see. - Read tools (
get_task,get_job_status,get_job_logs,list_artifacts,get_artifact): RequireVieworRunpermission.Denyreturns “not found”. A job that runs on a git ref is checked against the folder its own commit declares for its task. - Mutation tools (
execute_task,cancel_job): RequireRunpermission.View-only users receive an error.
The list_tasks response includes a can_execute field indicating whether the user can execute each task (true for Run, false for View).
Admin users bypass ACL over MCP, exactly as they do in the UI and REST API. Access mirrors the user’s actual privileges: an admin sees every workspace, task, and job, while a non-admin is limited to what the ACL grants their email or groups. This holds for both authentication methods — API keys and the OAuth flow. If you authenticate as an admin and still see empty lists, confirm you connected as an admin user (see Troubleshooting below).
Jobs created via MCP are tagged with source_type = "mcp" and the authenticated user’s email as source_id for audit trail.
Best Practices
Section titled “Best Practices”- Use API keys for integrations: Create dedicated API keys for MCP connections rather than sharing user credentials.
- Set token expiry: When creating API keys for long-running integrations, consider setting an expiry date for security.
- Monitor MCP requests: MCP operations are logged with the authenticated user’s email as
source_idfor audit tracking. - Validate agent outputs: When agents execute tasks, ensure their input validation matches your requirements (schemas are available via
get_task). - Use descriptive error messages: Include clear
error_messagefields in your task actions so agents can provide helpful troubleshooting guidance.
Troubleshooting
Section titled “Troubleshooting”Connection Refused
Section titled “Connection Refused”Ensure your Strøm server is running and accessible at the configured URL. If behind a proxy or load balancer, verify that the /mcp endpoint is not blocked or rewritten.
Authentication Errors
Section titled “Authentication Errors”Verify your API key or JWT token is valid and included in the Authorization header. API keys must start with strm_ prefix. Check the server logs for auth-related errors.
Tool Not Found
Section titled “Tool Not Found”Ensure MCP is enabled in server-config.yaml and your server has been restarted. Tools are only available after the server starts with mcp.enabled: true.
Workspace or Task Not Found
Section titled “Workspace or Task Not Found”Verify the workspace and task names are correct (case-sensitive). Use list_workspaces and list_tasks to confirm availability.
Empty Lists / “Not Found” for Everything
Section titled “Empty Lists / “Not Found” for Everything”If every list tool returns empty and reads/executes report “not found”, the authenticated identity has no ACL grants. MCP access mirrors the user’s actual privileges, so check: (1) you are connected as an admin user (admins bypass ACL), or (2) your email or a group you belong to is granted access in the server’s acl configuration. Note that the DB is_admin flag is what matters — it is honored for both API keys and OAuth-authenticated sessions.
See Also
Section titled “See Also”- Workflow Authoring — Learn how to define tasks and actions
- API Reference — REST API documentation
- Task Actions — Available action types for tasks