AI Agents (MCP Server)

What is the MCP Server?

FirmwareCI ships a Model Context Protocol (MCP) server that exposes your projects, workflows, DUTs, storage configs, jobs, and test schemas to AI coding agents — Claude Code, Cursor, VS Code (GitHub Copilot), Codex, OpenCode, and any other MCP-capable client.

With it connected, an agent can answer questions about your test runs, help author and validate new tests against the real schemas, and walk you through onboarding — all from your editor or terminal, without you copying IDs and YAML back and forth.

Read-only. The current MCP server is strictly read-only. Agents can list, inspect, author, and validate, but cannot create, edit, delete, or trigger anything on the server.

Prerequisites

  • An MCP-capable AI client (see Enabling the Server).
  • A FirmwareCI API token — a user token or an organization token.

Getting your token:

  1. Log in to the FirmwareCI web UI.
  2. Navigate to the settings page.
  3. Open the tokens tab.
  4. Create a user token, or ask your administrator if an org token is required.

The MCP server uses the same token as the CLI and CI/CD integrations. Every request is scoped to a single organization — agents never see data from other organizations. Which one, and what to do when your account belongs to several, is covered under Choosing an organization.

Endpoint

https://api.firmware-ci.com/mcp

Transport: Streamable HTTP. Authentication: Authorization: Bearer <token> header.

Capabilities

Tools

All tools are read-only or pure functions. Pagination is page-based (page * limit = row offset); arguments are passed as strings. Every list tool returns has_more so you know when to stop paging.

ToolPurpose
fwci_whoamiReturn the identity, role, and organization your token maps to. Useful to confirm which org’s data you are reading.
fwci_list_workflowsList workflows. Optional name (substring), project_id, page, limit.
fwci_get_workflowFetch a workflow by ULID.
fwci_list_dutsList Devices Under Test. Optional name (substring), busy (true/false), project_id, page, limit. Each entry reports whether the device is busy, unavailable (backend unreachable) or disabled (deliberately held out of the pool).
fwci_get_dutFetch a DUT by ULID, with full attributes and its availability.
fwci_list_projectsList projects. Returns ULIDs usable as project_id elsewhere.
fwci_list_storageList storage configs (how a firmware binary is fetched). Optional name (substring), project_id, page, limit.
fwci_get_storageFetch a storage config by ULID, with its commands and path map.
fwci_list_jobsList jobs. Filter by status (comma-separated), workflow_id, and/or a time window (created_after / created_before, RFC3339); paginate with page/limit.
fwci_get_jobFetch a job by ULID: status, test, DUT, timestamps, error. For failed/aborted jobs it also returns the failing step (failed_stage, failed_step) and a short failed_excerpt — so you often skip a separate logs call.
fwci_get_job_logsPer-step logs for a job. Tails running jobs. Optional only_failed (true) and step (substring) narrow the output to the steps you care about.
fwci_validate_testValidate a test definition (YAML/JSON) against the schemas. Returns {valid: true} or an error.
fwci_validateValidate a document by kind (test, dut, dut-pre-post, workflow, storage). Returns valid plus structured issues (field + message) on failure.
fwci_list_schemasList schema names: test, workflow, step:<cmd>, common:<name>.
fwci_get_schemaFetch one JSON Schema by name. Carries agent guidance and an inline YAML example.
fwci_list_examplesList canonical, schema-valid YAML examples shipped with the server.
fwci_get_exampleFetch one example by path.

Names (workflows, DUTs, storage) are unique only within a project, so pass project_id alongside name when the same name may exist in more than one project. A name-filtered list returns truncated: true if there were more candidates than the server scans in one pass — narrow with project_id or a longer substring if you see it.

When a tool call fails, the error carries a machine-readable codeinvalid_arg, not_found, forbidden, unauthorized, or internal — alongside the human-readable message, so an agent can react (e.g. fix an argument vs. give up) without parsing prose.

Prompts

Pre-built conversation starters the client surfaces to you:

PromptPurpose
bootstrap_workflowOnboard a user or agent to FirmwareCI.
explain_failureTriage a failed job: pull logs, identify the failing step.
convert_existing_ciTranslate a GitHub Actions or GitLab CI config into a FirmwareCI test.

Resources

URI-addressable records, useful when an agent wants to quote or cite an entity without a tool call:

URIReturns
fwci://workflow/{id}Full workflow record (JSON).
fwci://job/{id}Full job record (JSON).
fwci://dut/{id}Full DUT record (JSON), availability included.
fwci://project/{id}Full project record (JSON).
fwci://docs/{+path}FirmwareCI documentation by path.

Choosing an organization

Every tool reads from exactly one organization. The server resolves which:

  • An organization token is bound to the organization it was created in and can never reach another. Nothing else to do.
  • A user token can reach every organization you are a member of. If that is exactly one, it resolves to that one and there is nothing else to do.
  • A user token for an account in several organizations is ambiguous. The server refuses the request rather than guessing, and the error names the organizations it will accept.

Prefer a user token. One token then covers every organization you belong to, and you switch between them by changing a header rather than by minting and storing another secret. Reach for an organization token when you want the opposite property — a credential that is pinned to one organization no matter who holds it, which is what CI pipelines want.

Send an X-Org-ID header to pick one. Despite its name it takes the organization’s name — the handle you actually see — or its ULID:

claude mcp add --transport http --scope user firmwareci \
  https://api.firmware-ci.com/mcp \
  --header "Authorization: Bearer <YOUR_TOKEN>" \
  --header "X-Org-ID: my-org"

Run fwci org list to see the organizations you belong to. Names are unique and match case-insensitively. Once connected, fwci_whoami confirms which organization the agent is reading.

To work across two organizations in one session, register the server twice under different names, each with its own X-Org-ID. A user token carries your role in every organization it reaches, so treat it as the broader secret and scope it out of shared configuration.

Enabling the Server

Each client stores MCP configuration differently. In every example below, replace <YOUR_TOKEN> with your FirmwareCI token, and add an X-Org-ID header if your account belongs to more than one organization. Keep configuration files that contain a literal token out of version control.

Claude Code

claude mcp add --transport http --scope user firmwareci \
  https://api.firmware-ci.com/mcp \
  --header "Authorization: Bearer <YOUR_TOKEN>"

--scope user makes the server available in every project. Start a new session and run /mcp to confirm the connection.

Cursor

Add to ~/.cursor/mcp.json for all projects (or .cursor/mcp.json in a project root to scope it to that workspace):

{
  "mcpServers": {
    "firmwareci": {
      "url": "https://api.firmware-ci.com/mcp",
      "headers": { "Authorization": "Bearer <YOUR_TOKEN>" }
    }
  }
}

VS Code (GitHub Copilot)

Add to .vscode/mcp.json in your workspace (or your user profile via MCP: Open User Configuration). Note VS Code uses servers as the root key, and MCP tools are only available in Copilot’s Agent mode:

{
  "servers": {
    "firmwareci": {
      "type": "http",
      "url": "https://api.firmware-ci.com/mcp",
      "headers": { "Authorization": "Bearer <YOUR_TOKEN>" }
    }
  }
}

You can also add it interactively: open the Command Palette (Ctrl+Shift+P), run MCP: Add Server, and choose HTTP.

Codex

Add to ~/.codex/config.toml. Codex reads the token from an environment variable, so the secret stays out of the file:

[mcp_servers.firmwareci]
url = "https://api.firmware-ci.com/mcp"
bearer_token_env_var = "FWCI_TOKEN"

Export the token in your shell before starting Codex:

export FWCI_TOKEN="<YOUR_TOKEN>"

If your Codex build does not connect over streamable HTTP, add experimental_use_rmcp_client = true to the server table.

OpenCode

Add to your global config at ~/.config/opencode/opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "firmwareci": {
      "type": "remote",
      "url": "https://api.firmware-ci.com/mcp",
      "enabled": true,
      "headers": { "Authorization": "Bearer <YOUR_TOKEN>" }
    }
  }
}

Other clients

Using a different MCP client? Consult that tool’s own MCP documentation for how to register a server — support and configuration formats vary by client. You only need three things, which every MCP-capable client accepts in some form:

  • Transport: Streamable HTTP (sometimes labelled “remote” or “http”).
  • URL: https://api.firmware-ci.com/mcp
  • Header: Authorization: Bearer <YOUR_TOKEN>

Trying It Out

Once connected, ask your agent questions in natural language. The agent selects the right tools on its own:

  • “List my last 5 FirmwareCI jobs.”
  • “What failed since yesterday?” (uses the status + created_after filters)
  • “Find the failed jobs for workflow <ULID> and show me the failing step’s logs.”
  • “Why did job <ULID> fail?” (the failing step and excerpt come back on fwci_get_job directly)
  • “Which DUTs are free right now?” (uses the busy=false filter — check disabled and unavailable too, since an idle device is not necessarily usable)
  • “Which workflow is named ‘Boot Test’?” (uses the name filter)
  • “What test step schemas are available?”
  • “Write a FirmwareCI test that pings the DUT, then validate it.”
  • “Write a DUT pre-stage that flashes firmware, then validate it as a dut-pre-post.”
  • “Show me the storage config this workflow uses.”
  • “Which organization am I connected as?” (uses fwci_whoami)
  • “Help me onboard to FirmwareCI.”

Limitations

  • Read-only. No job creation, abort, or edits. Authoring happens locally (your agent writes files); the server only reads and validates.
  • One organization per connection. A token that could reach several organizations must name one with an X-Org-ID header; see Choosing an organization.
  • Rate limited per organization. Responses include X-Ratelimit-* headers; clients should back off on 429.