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:
- Log in to the FirmwareCI web UI.
- Navigate to the settings page.
- Open the tokens tab.
- 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
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.
| Tool | Purpose |
|---|---|
fwci_whoami | Return the identity, role, and organization your token maps to. Useful to confirm which org’s data you are reading. |
fwci_list_workflows | List workflows. Optional name (substring), project_id, page, limit. |
fwci_get_workflow | Fetch a workflow by ULID. |
fwci_list_duts | List 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_dut | Fetch a DUT by ULID, with full attributes and its availability. |
fwci_list_projects | List projects. Returns ULIDs usable as project_id elsewhere. |
fwci_list_storage | List storage configs (how a firmware binary is fetched). Optional name (substring), project_id, page, limit. |
fwci_get_storage | Fetch a storage config by ULID, with its commands and path map. |
fwci_list_jobs | List jobs. Filter by status (comma-separated), workflow_id, and/or a time window (created_after / created_before, RFC3339); paginate with page/limit. |
fwci_get_job | Fetch 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_logs | Per-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_test | Validate a test definition (YAML/JSON) against the schemas. Returns {valid: true} or an error. |
fwci_validate | Validate a document by kind (test, dut, dut-pre-post, workflow, storage). Returns valid plus structured issues (field + message) on failure. |
fwci_list_schemas | List schema names: test, workflow, step:<cmd>, common:<name>. |
fwci_get_schema | Fetch one JSON Schema by name. Carries agent guidance and an inline YAML example. |
fwci_list_examples | List canonical, schema-valid YAML examples shipped with the server. |
fwci_get_example | Fetch 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 code —
invalid_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:
| Prompt | Purpose |
|---|---|
bootstrap_workflow | Onboard a user or agent to FirmwareCI. |
explain_failure | Triage a failed job: pull logs, identify the failing step. |
convert_existing_ci | Translate 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:
| URI | Returns |
|---|---|
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:
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
--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):
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:
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:
Export the token in your shell before starting Codex:
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:
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_afterfilters) - “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 onfwci_get_jobdirectly) - “Which DUTs are free right now?” (uses the
busy=falsefilter — checkdisabledandunavailabletoo, since an idle device is not necessarily usable) - “Which workflow is named ‘Boot Test’?” (uses the
namefilter) - “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-IDheader; see Choosing an organization. - Rate limited per organization. Responses include
X-Ratelimit-*headers; clients should back off on429.