@devflow-one/mcp
v0.1.17
Published
stdio MCP server exposing DevFlow gateway repo actions as tools for AI agents
Maintainers
Readme
@devflow-one/mcp — stdio MCP server
Exposes the DevFlow local gateway's repo actions (branch, PR/MR, pipeline, reviewers) as MCP tools so AI agents, skills, and workflows can drive them.
It is a thin stdio client of the local gateway the desktop app runs
(desktop/src/gateway/server.js). It reads the same auto-generated token file,
routes by Jira project key, and returns the gateway's result. No provider API
logic lives here.
How it works
- One MCP tool per gateway action (19 actions), each with a typed input schema:
routing fields (
issueId/projectKey/profile/repo) plus the action's own args. Tool names are snake_case (create_branch); the gateway's own action names stay camelCase and are unaffected by them. - All four routing fields are optional — omitted, they are inferred from the working directory (see Zero-config routing below).
- A
list_actionsdiscovery tool returns the gateway's advertised actions and the connected profiles (with the project keys each owns). It reports the gateway's camelCase names, not the snake_case tool names — call the tools. - A
resolve_contexttool answers "where am I?" — the project, issue, repo (with its configuredbaseBranch) and owning profile inferred from the working directory. Read-only. - Every call is
POST /actions/:gatewayActionon127.0.0.1:8787with the Bearer token (e.g. thecreate_branchtool posts to/actions/createBranch). - Gateway/routing failures (
NO_PROFILE,AMBIGUOUS_PROFILE,NO_CONTEXT,TIMEOUT, or an unreachable daemon) come back as structured tool errors (isError: true). - Destructive tools (
delete_branch,close_pull_request,merge_pull_request) take anallowDestructiveinput and are refused withDESTRUCTIVE_BLOCKEDunless it istrue— enforced both here and by the gateway, so it can't be bypassed. Confirmation is never inferred.
Zero-config routing
An agent working inside a configured repo checkout does not need to know the project key. When a routing field is omitted, it is inferred — highest precedence first:
- the explicit tool input
DEVFLOW_PROJECT/DEVFLOW_ISSUE/DEVFLOW_REPO/DEVFLOW_PROFILE- the checkout's
originremote, matched against your configured repos - the checkout path, matched against each repo's configured local path
- the issue key in the current branch name (
feature/ABC-123-thing→ABC-123) - a Jira-key-shaped ancestor directory (
~/work/ABC-123/repo)
So inside a configured checkout this is enough:
{ "name": "create_branch", "arguments": { "branchName": "feat/ABC-123" } }Passing a field always overrides inference. Supplying projectKey (or
issueId) also drops any repo/profile that was inferred for a different
project, so an explicit key can never be paired with another project's repo.
When inference can't decide
Nothing is ever guessed — each case is a structured error naming the fix:
| Code | Meaning | What to do |
| --- | --- | --- |
| AMBIGUOUS_PROFILE | Several configured repos match this directory. The error carries a candidates list. | Retry with projectKey (or repo) naming one. |
| NO_CONTEXT | No routing input, and the directory matches no configured repo. | Pass projectKey or issueId, or call resolve_context to see what is inferable. |
| NO_PROFILE | The owning Chrome profile is known but not running. The message names it. | Open that Chrome profile with the extension enabled, then retry. |
The last one matters: actions execute in the browser extension, so the profile that owns a project must be running. The daemon remembers which profile owns which project even while it's closed, so the error can name it instead of just failing.
Prerequisites
- The DevFlow desktop app must be running — it hosts the gateway and writes the token, so nothing here works without it. v1 also requires the browser extension connected for a profile to own project keys.
- Node >= 20 (for
npx).
No clone, no build: the server is published to npm as @devflow-one/mcp and
runs straight from npx.
Install
Claude Code
claude mcp add devflow -- npx -y @devflow-one/mcpCursor, Claude Desktop, and other JSON-configured clients
The same entry works in .cursor/mcp.json, claude_desktop_config.json, and
project-level .mcp.json:
{
"mcpServers": {
"devflow": {
"command": "npx",
"args": ["-y", "@devflow-one/mcp"]
}
}
}Intent by Augment (~/.augment/settings.json)
Intent injects mcpServers from its global settings into every agent it spawns:
{
"mcpServers": {
"devflow": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@devflow-one/mcp"],
"env": {}
}
}
}Intent aborts an agent whose MCP servers take more than a few seconds to answer
initialize, and a cold npx download can cross that. If the server shows up
as Connection closed, install it once and point command at the built entry
instead of npx:
mkdir -p ~/.augment/mcp-servers && cd ~/.augment/mcp-servers && npm i @devflow-one/mcp{
"devflow": {
"type": "stdio",
"command": "/absolute/path/to/node",
"args": ["/Users/you/.augment/mcp-servers/node_modules/@devflow-one/mcp/dist/index.js"],
"env": {}
}
}command must be the executable alone — flags such as -y belong in args.
Intent reads the config on every agent spawn, so a running agent keeps its
old (failed) connection until it is replaced with a new one.
Codex CLI (~/.codex/config.toml)
[mcp_servers.devflow]
command = "npx"
args = ["-y", "@devflow-one/mcp"]Restart the client after editing its config, then check that the devflow tools
(starting with list_actions) are listed.
Token & port (zero-config)
The token and port are discovered automatically from the file the desktop app writes. Override when needed:
| Env var | Purpose |
| --------------------------- | ---------------------------------------------------- |
| DEVFLOW_GATEWAY_TOKEN | Provide the token directly (skips the file lookup). |
| DEVFLOW_GATEWAY_TOKEN_PATH| Override where the token file is read from. |
| DEVFLOW_GATEWAY_PORT | Gateway port (default 8787). |
Routing defaults, applied when a tool input is omitted (see Zero-config routing):
| Env var | Overrides |
| ------------------ | ------------------- |
| DEVFLOW_PROJECT | projectKey |
| DEVFLOW_ISSUE | issueId |
| DEVFLOW_REPO | repo |
| DEVFLOW_PROFILE | profile |
Usage examples (tool calls)
// discover what's available
{ "name": "list_actions", "arguments": {} }
// what does the working directory imply? (project, issue, repo, profile + liveness)
{ "name": "resolve_context", "arguments": {} }
// the branch an issue's work belongs on, from the repo's branch template (incl.
// {parentIssueKey}: a sub-issue may resolve to its parent's branch) + whether it exists.
// Check it out before committing — commit_by_template commits the checked-out branch.
{ "name": "resolve_branch_name", "arguments": { "issueId": "ABC-124" } }
// create (or reuse, if it exists) the issue's configured branch. The template always
// wins; a passed branchName is only a fallback, and a replaced one is listed in `warnings`.
// → { "branchName": "ABC-123", "created": false, "parentIssueKey": "ABC-123", "warnings": [] }
{ "name": "create_branch", "arguments": { "issueId": "ABC-124" } }
// inside a configured checkout — no routing fields needed at all
{ "name": "create_branch", "arguments": { "branchName": "feat/ABC-123" } }
// list the configured PR types (id, label, isDefault) to pick a valid prType
{ "name": "list_pr_types", "arguments": { "projectKey": "ABC" } }
// open a PR/MR — always created as a draft. The PR type supplies the templates and
// labels; an omitted or unrecognized prType is auto-selected by matching type names
// against the title's conventional prefix, branch prefix, Jira issue type and title
// keywords, falling back to the first type. Pass a type's id or label when you know better.
{ "name": "create_draft_pull_request", "arguments": { "projectKey": "ABC", "title": "Fix login", "body": "…", "headBranch": "feat/x" } }
{ "name": "create_draft_pull_request", "arguments": { "projectKey": "ABC", "title": "Fix login", "body": "…", "headBranch": "feat/x", "prType": "Bugfix" } }
// the issue key is parsed from headBranch; an explicit issueId overrides it (issue-key
// label + {issueKey}/{issueTitle} tokens). {issueTitle} needs the Jira API token set in
// Settings, else it renders empty. The branch template always picks the source branch,
// so headBranch may be omitted; a different one is replaced and listed in `warnings`.
{ "name": "create_draft_pull_request", "arguments": { "issueId": "ABC-123", "title": "Fix login", "body": "…" } }
// …then take it out of draft when it's ready
{ "name": "mark_pr_ready_for_review", "arguments": { "projectKey": "ABC", "prId": 42 } }
// read a PR/MR's current title + description, then edit it. title/description replace;
// appendDescription adds text after a `---` rule, skipped if it's already at the end
{ "name": "get_pull_request", "arguments": { "projectKey": "ABC", "prId": 42 } }
{ "name": "update_pull_request", "arguments": { "projectKey": "ABC", "prId": 42, "appendDescription": "## Test plan\n…" } }
{ "name": "update_pull_request", "arguments": { "projectKey": "ABC", "prId": 42, "title": "Fix login redirect" } }
// routing by issue id (prefix → project key)
{ "name": "find_pr_by_issue_key", "arguments": { "issueId": "ABC-123", "issueKey": "ABC-123" } }
// pipeline status
{ "name": "get_pipeline_status", "arguments": { "issueId": "ABC-123", "ref": { "prId": 42, "headSha": "abc123" } } }
// commit a managed worktree with the project's commit-message template
{ "name": "commit_by_template", "arguments": { "projectKey": "ABC", "worktreePath": "/wt/ABC-123", "values": { "summary": "fix login" }, "push": false } }
// …or the repository's configured checkout (its localPath): only already-staged changes are committed
{ "name": "commit_by_template", "arguments": { "projectKey": "ABC", "worktreePath": "/code/app", "values": { "summary": "fix login" } } }
// assembled fix-review-comments prompt (read-only; runs no AI, makes no writes)
{ "name": "get_fix_comments_prompt", "arguments": { "issueId": "ABC-123", "prId": 42 } }
// notify the project's configured Slack channel(s)
{ "name": "notify_slack", "arguments": { "projectKey": "ABC", "message": "Review fixes ready on {pr}", "values": { "pr": "#42" } } }
// destructive — must confirm, else DESTRUCTIVE_BLOCKED
{ "name": "delete_branch", "arguments": { "projectKey": "ABC", "branchName": "old", "allowDestructive": true } }
{ "name": "merge_pull_request", "arguments": { "projectKey": "ABC", "prId": 42, "mergeMethod": "squash", "deleteSourceBranch": true, "allowDestructive": true } }Local development (contributors only)
Users don't need this — it's for working on the server itself from a clone of the monorepo:
cd mcp
npm install
npm run build # → dist/index.js (the stdio entry point)Point a client at the local build instead of the published package:
claude mcp add devflow-local -- node /absolute/path/to/monorepo/mcp/dist/index.jsVerify
npm test # unit: action catalog, arg construction, routing, context inference
npm run verify # scripted stdio client against a mock gateway (lists tools, calls list_actions + resolve_context + a real action, and checks the inference/ambiguity/offline-profile refusals)The context resolver (src/contextResolver.js, src/gitFacts.js) is a
generated copy of shared/ — edit the shared source and run
npm run sync:shared, never the copy.
Releasing
Publishing this package to npm is a manual, dry-run-first process — see
RELEASING.md at the repo root.
