title: MCP server description: Connect AI agents to Diffinery over the Model Context Protocol at https://diffinery.com/mcp, with client setup, the tool list, permissions and example calls.
Diffinery runs a Model Context Protocol (MCP) server at https://diffinery.com/mcp. An MCP client such as Claude Code, Cursor or VS Code can use it to read code, commit changes, open and review pull requests, follow CI, publish releases, run commands in sandboxes and look things up in these docs, all without a git client. Every tool goes through the same permission checks, branch protection and agent guardrails as git and the REST API.
Endpoint and transport
| Property | Value |
|---|---|
| URL | https://diffinery.com/mcp |
| Transport | Streamable HTTP. Send JSON-RPC 2.0 messages with POST; every response is a single JSON body (application/json), never an event stream. |
| Sessions | None. The server is stateless and doesn't issue an Mcp-Session-Id; send the token with every request. |
| Batches | A JSON array of requests gets a JSON array of responses. |
| Notifications | Requests without an id (such as notifications/initialized) get 202 Accepted with an empty body. |
GET /mcp | 405 with {"message": "Diffinery MCP: POST JSON-RPC here with Authorization: Bearer <token>"}. There is no server-to-client stream. |
| Protocol versions | 2025-06-18 (default), 2025-03-26, 2024-11-05. initialize answers with the version you asked for when it is supported, otherwise 2025-06-18. |
| Capabilities | tools only (listChanged: false). No resources or prompts. |
| Methods | initialize, ping, tools/list, tools/call, and the notifications notifications/initialized and notifications/cancelled |
| Server info | {"name": "diffinery", "title": "Diffinery", "version": "0.1.0"}, plus short usage instructions for the model (how to read code, commit, open pull requests, watch CI, use sandboxes and search the docs) |
Authentication
Every request needs a Diffinery token in the Authorization header:
Authorization: Bearer dfy_...
token dfy_... and HTTP Basic auth with the token as the password also work. Use an agent's token for an AI agent (see Agents), or your own personal token (see Tokens). The token's scope limits what the tools can do: every tool that isn't read-only needs a token with the write or admin scope, so a read token can only use read-only tools (see Token scope and write tools). There is no OAuth flow.
| Response | Meaning |
|---|---|
401 {"message": "sign in required"} | No Authorization header. |
401 {"message": "invalid, expired or revoked token"} | The token is wrong, expired or revoked, or its account (or agent) is disabled. |
401 {"message": "unrecognised token; create one under Settings → Access tokens"} | The header doesn't hold a dfy_ token. |
Connect your client
Create a token first: for an agent, in Settings → Agents or with diffinery agent create (which needs an admin-scoped token or a browser session); for yourself, in Settings → Access tokens. Keep it out of files you commit; most clients below can read it from an environment variable.
Claude Code
claude mcp add --transport http diffinery https://diffinery.com/mcp \
--header "Authorization: Bearer dfy_..."
To share the server with a project without sharing the token, put this in the project's .mcp.json and set DIFFINERY_TOKEN in each developer's environment:
{
"mcpServers": {
"diffinery": {
"type": "http",
"url": "https://diffinery.com/mcp",
"headers": { "Authorization": "Bearer ${DIFFINERY_TOKEN}" }
}
}
}
The diffinery CLI prints exactly this snippet with diffinery mcp-config (add --show-token to embed the saved token instead of ${DIFFINERY_TOKEN}). Run /mcp inside Claude Code to check that the server is connected.
Cursor
Add to .cursor/mcp.json in the project, or ~/.cursor/mcp.json for all projects:
{
"mcpServers": {
"diffinery": {
"url": "https://diffinery.com/mcp",
"headers": { "Authorization": "Bearer dfy_..." }
}
}
}
VS Code
Add to .vscode/mcp.json. VS Code prompts for the token and stores it securely:
{
"inputs": [
{ "type": "promptString", "id": "diffinery-token", "description": "Diffinery token (dfy_...)", "password": true }
],
"servers": {
"diffinery": {
"type": "http",
"url": "https://diffinery.com/mcp",
"headers": { "Authorization": "Bearer ${input:diffinery-token}" }
}
}
}
Claude Desktop and other clients that start local servers
Clients whose configuration only launches local (stdio) servers can reach Diffinery through a bridge such as mcp-remote, which forwards to the remote URL with your header:
{
"mcpServers": {
"diffinery": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://diffinery.com/mcp", "--header", "Authorization:${AUTH_HEADER}"],
"env": { "AUTH_HEADER": "Bearer dfy_..." }
}
}
}
Any other client
Point an MCP client that supports the Streamable HTTP transport at https://diffinery.com/mcp and configure a custom Authorization: Bearer dfy_... header. The usual JSON form is:
{ "mcpServers": { "diffinery": { "type": "http", "url": "https://diffinery.com/mcp", "headers": { "Authorization": "Bearer dfy_..." } } } }
Tools
Every tool that works on a repository takes owner and repo. Tools marked read-only have the MCP annotation readOnlyHint: true, so clients can run them without asking. Every tool marked No needs a token with the write or admin scope, whatever its minimum role. The full input schema of each tool is on MCP tools reference, and tools/list returns it at runtime.
Account and repositories
| Tool | Read-only | What it does | Minimum role |
|---|---|---|---|
whoami | Yes | The account the token acts as (login, kind) and, for an agent, on_behalf_of | Any token |
list_repos | Yes | Repositories visible to you, optionally filtered by owner | Any token |
get_organization | Yes | An organization's login and name; to its members, its agents, agents of its members and site administrators also default_repository_permission (the base permission members get on its repositories) | Any token |
list_invitations | Yes | Pending invitations to a repository (owner and repo), an organization (org) or the instance (neither), each with an id | Admin on the repository; organization owner or admin; site administrator |
revoke_invitation | No | Revoke a pending invitation by invitation_id, with the same owner and repo, org or neither; the address can then be invited again | Same as list_invitations; organization and instance invitations need an admin token |
Code
| Tool | Read-only | What it does | Minimum role |
|---|---|---|---|
list_tree | Yes | Files and directories at a path and ref (default: the default branch) | read |
get_file | Yes | A file's contents at a ref; binary files return only their size and binary: true | read |
list_branches | Yes | Branches, marking the default and protected ones | read |
compare | Yes | Commits ahead and behind, and the diff, between base and head | read |
Branches and commits
| Tool | Read-only | What it does | Minimum role |
|---|---|---|---|
create_branch | No | Create branch from another branch, tag or commit (from, default: the default branch) | write |
commit_files | No | One commit on branch that writes files and removes deletes; see Commit without git | write |
Pull requests
| Tool | Read-only | What it does | Minimum role |
|---|---|---|---|
list_pull_requests | Yes | Up to 100 pull requests by state (open, closed, all) | read |
get_pull_request | Yes | A pull request with its comments, reviews and mergeability (including blockers); include_diff: true adds the files and patches | read |
create_pull_request | No | Open a pull request: title, head, optional base, body, draft, intent, reviewers | write |
comment | No | Comment on an issue or pull request | triage |
review_pull_request | No | Submit an APPROVE, REQUEST_CHANGES or COMMENT review, with optional line comments | read (write to approve or request changes) |
merge_pull_request | No | Merge with method, expected_head_sha, delete_branch; queues the pull request when the base branch uses a merge queue | write |
dequeue_pull_request | No | Remove a pull request from the merge queue | write |
Issues
| Tool | Read-only | What it does | Minimum role |
|---|---|---|---|
list_issues | Yes | Up to 100 issues by state, search text q and label | read |
get_issue | Yes | An issue (or pull request) with its comments and reviews | read |
create_issue | No | Open an issue with title, optional body and labels | read |
update_issue | No | Change the title, body, state (open, closed) or labels of an issue or pull request | triage |
Workflows
| Tool | Read-only | What it does | Minimum role |
|---|---|---|---|
list_workflow_runs | Yes | The 30 newest runs, filtered by branch, sha, pr_number or status | read |
get_workflow_run | Yes | A run with its jobs, their status, conclusion and failure reason | read |
get_job_logs | Yes | A job's log as text; the last tail lines (default 400), with complete and truncated flags | read |
rerun_workflow | No | Re-run a run | write |
cancel_workflow | No | Cancel a queued or running run | write |
dispatch_workflow | No | Start a workflow_dispatch run of workflow (for example deploy.yml) on ref with inputs | write |
Releases
| Tool | Read-only | What it does | Minimum role |
|---|---|---|---|
list_releases | Yes | Releases, newest first, up to limit (default 30, at most 100), with total. Drafts are included only for writers. | read |
get_release | Yes | A release by tag or id, or the latest release when you pass neither | read |
create_release | No | Create a release for tag_name, creating the tag at target_commitish (default: the default branch) if it doesn't exist. Optional name, body, draft, prerelease, make_latest and generate_release_notes. | write |
update_release | No | Edit a release by id: tag_name, target_commitish, name, body, draft, prerelease, make_latest. Publishing a draft sends the release event. | write |
upload_release_asset | No | Attach a file of up to 25 MB to release release_id: name, and either content (text) or content_base64 (bytes), with optional label and content_type | write |
generate_release_notes | Yes | Draft notes for tag_name from the pull requests merged since the previous release (or previous_tag_name). Creates nothing. | write |
See Releases below for the result shape.
Docs
| Tool | Read-only | What it does | Minimum role |
|---|---|---|---|
search_docs | Yes | Search this documentation for query (for example merge queue or workflow_dispatch inputs). Returns up to 8 matching pages, each with page, title, section, the best matching heading, url, markdown_url and a snippet. | Any token |
read_docs | Yes | Read one docs page as Markdown. page is a slug (actions/workflow-syntax), a docs URL, or index for the overview that links to every page. Returns page, title and markdown. | Any token |
Sandboxes
| Tool | Read-only | What it does | Minimum role |
|---|---|---|---|
sandbox_create | No | Start a Linux sandbox with the repository checked out at ref; returns its id | write |
sandbox_exec | No | Run a shell command in the sandbox, with optional cwd and timeout_seconds | write |
sandbox_read_file | No | Read a file; text comes back as text, other files as base64 | write |
sandbox_write_file | No | Write a text file | write |
sandbox_close | No | Shut the sandbox down | write |
See Sandboxes for what is inside a sandbox and its limits.
Commit without git
commit_files creates a single commit from file contents:
| Argument | Meaning |
|---|---|
branch | Branch to commit to (required) |
message | Commit message (required) |
files | [{ "path", "content", "encoding": "utf-8" or "base64", "executable": true or false }] |
deletes | Paths to remove |
create_branch_from | Create branch from this branch, tag or commit if it doesn't exist |
expected_head_sha | Only commit if branch is still at this commit; otherwise branch moved; refresh and retry |
It returns { "sha": "...", "branch": "..." }. At least one file or delete is required (nothing to commit). Committing to a branch that doesn't exist without create_branch_from fails with branch fix-pricing-link not found. Commits made by agents are stamped with the agent and its owner; see Agents.
Releases over MCP
Release tools return a compact release object:
{
"id": 7,
"tag_name": "v1.4.0",
"target_commitish": "main",
"name": "v1.4.0",
"body": "## What's Changed\n...",
"draft": false,
"prerelease": false,
"latest": true,
"author": "review-bot",
"created_at": "2026-10-13T09:30:00.000Z",
"published_at": "2026-10-13T09:30:00.000Z",
"html_url": "https://diffinery.com/acme/website/releases/tag/v1.4.0",
"assets": [{ "id": 12, "name": "notes.txt", "label": "", "content_type": "text/plain", "size": 18, "download_count": 0, "download_url": "https://diffinery.com/acme/website/releases/download/v1.4.0/notes.txt" }]
}
make_latestis the string"true","false"or"legacy"; a new published release becomes the latest by default.draft: truekeeps a release visible to writers only.update_releasewithdraft: falsepublishes it, which startson: releaseworkflows and sends thereleasewebhook event.upload_release_assettakes the file inline, so it is limited to 25 MB (MCP uploads are limited to 25 MB; use the upload_url of the REST API for larger files). Pass exactly one ofcontentandcontent_base64. Text defaults totext/plain, bytes toapplication/octet-stream. For files up to 100 MB, use the release'supload_urlin the REST API.generate_release_notesonly drafts text; pass itsbodytocreate_release, or setgenerate_release_notes: truethere.- Publishing a release for a tag that doesn't exist creates the tag at
target_commitish, and that follows the same rules as any ref update. A draft creates its tag only when it is published.
Permissions
The token's account (person or agent), its role on the repository and the token's scope decide what each tool may do, exactly as for the REST API. The minimum role for each tool is in the tables above.
Token scope and write tools
Diffinery checks the token's scope before a tool runs. A tool that isn't read-only (readOnlyHint: false) needs a token with the write or admin scope. With a read token, the call fails with isError: true and the text this token needs the write scope, even when the role the tool asks for is only read or triage: create_issue, comment, a COMMENT review with review_pull_request, and the sandbox tools all need a write token. Read-only tools work with any scope, including read.
Use a write token for agents that change anything, and a read token for agents that only read code, issues, pull requests, runs, releases and docs.
Other rules
On top of the role and scope:
- Agents can't push or commit to the default branch or protected branches, and can't merge into them, unless the branch's rule allows it (
agentsMayPush,agentsMayMerge). - Pull requests opened by agents need a human approval, and approvals submitted by agents with
review_pull_requestnever count toward it. - Private repositories you can't see return
repository not found, the same as a repository that doesn't exist. - Release tools follow the same rules as the REST API: anyone who can read the repository can list published releases, writers create, edit and publish them and see drafts, and a release that creates a tag is a ref update, so agent branch and tag rules apply.
search_docsandread_docsread the public documentation. They work with any token, read-only included, and don't touch a repository.
See Agents and Permissions.
Results and errors
A successful tools/call returns the tool's output twice: as pretty-printed JSON in a text content block, and as structuredContent (the object itself, or { "result": ... } when the output is an array or a plain value):
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{ "type": "text", "text": "{\n \"path\": \"README.md\",\n ..." }],
"structuredContent": { "path": "README.md", "commit": "51c0e2aa...", "size": 412, "content": "# Website\n..." },
"isError": false
}
}
When a tool fails, the call still succeeds at the JSON-RPC level, with isError: true and the error message as text. The messages are the same as the REST API's:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [{ "type": "text", "text": "agents may not push to protected or default branches; open a pull request" }],
"isError": true
}
}
Missing or invalid arguments produce messages such as owner is required, title must be a string or number must be a positive integer.
Protocol-level errors use JSON-RPC error objects:
| Code | Message | Cause |
|---|---|---|
-32700 | parse error | The body isn't JSON (HTTP status 400). |
-32600 | invalid request | A message without a string method. |
-32601 | method not found: <method> | An unsupported method, such as resources/list. |
-32602 | unknown tool: <name> | tools/call named a tool that doesn't exist. |
Example calls with curl
Set your token once:
export DIFFINERY_TOKEN=dfy_...
Initialize
curl -s https://diffinery.com/mcp \
-H "Authorization: Bearer $DIFFINERY_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": { "tools": { "listChanged": false } },
"serverInfo": { "name": "diffinery", "title": "Diffinery", "version": "0.1.0" },
"instructions": "Diffinery is a git forge built for agents. Read code with get_file/list_tree, ..."
}
}
List tools
curl -s https://diffinery.com/mcp \
-H "Authorization: Bearer $DIFFINERY_TOKEN" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
Each tool has name, description, inputSchema (a JSON Schema object with properties and required) and annotations.readOnlyHint.
Read a file
curl -s https://diffinery.com/mcp \
-H "Authorization: Bearer $DIFFINERY_TOKEN" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_file","arguments":{"owner":"acme","repo":"website","path":"README.md"}}}'
Commit on a new branch and open a pull request
curl -s https://diffinery.com/mcp \
-H "Authorization: Bearer $DIFFINERY_TOKEN" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"commit_files","arguments":{
"owner":"acme","repo":"website","branch":"fix-pricing-link","create_branch_from":"main",
"message":"Fix the Contact sales link",
"files":[{"path":"src/pricing.html","content":"<a href=\"/contact\">Contact sales</a>\n"}]}}}'
curl -s https://diffinery.com/mcp \
-H "Authorization: Bearer $DIFFINERY_TOKEN" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"create_pull_request","arguments":{
"owner":"acme","repo":"website","title":"Fix the Contact sales link","head":"fix-pricing-link",
"intent":"Point the Contact sales link at /contact; no other changes."}}}'
Check CI
curl -s https://diffinery.com/mcp \
-H "Authorization: Bearer $DIFFINERY_TOKEN" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"list_workflow_runs","arguments":{"owner":"acme","repo":"website","pr_number":12}}}'
Then get_workflow_run with a run's id, and get_job_logs with a failed job's id, to see why it failed.
A typical agent workflow
whoamito confirm the account and its owner.search_docsandread_docswhen it needs to check how a Diffinery feature works (for exampleworkflow_dispatch inputsorbranch protection).list_treeandget_file(orcompare) to read the code.commit_fileswithcreate_branch_fromto put changes on a new branch, passingexpected_head_shaon later commits to avoid overwriting someone else's.sandbox_createandsandbox_execto build and run tests when needed.create_pull_requestwith anintentthat states the goal.list_workflow_runs,get_workflow_runandget_job_logsto follow CI, fixing failures with more commits.get_pull_requestto read reviews and the remaining merge blockers. A person approves and merges (or the agent merges withmerge_pull_requestwhere the branch allows agents to merge).
Limitations
- The server offers tools only: no resources, prompts or sampling.
- Responses are plain JSON. The server doesn't stream, keeps no session, and can't send messages to the client on its own.
- Authentication is a Diffinery token in a header; there is no OAuth flow, so clients that only support OAuth for remote servers need a bridge (see Claude Desktop and other clients that start local servers).
Troubleshooting
| Symptom | Cause and fix |
|---|---|
The client says it can't connect or gets 401 | The Authorization header is missing or the token is wrong. Check the header name and the Bearer prefix, and that the token hasn't expired. |
repository not found | Wrong owner or repo, or the account (or, for an agent, its owner) can't see the repository. |
this token needs the write scope | The tool isn't read-only and the token has only the read scope. Create a write token (for an agent, in Settings → Agents or with POST /api/v3/diffinery/agents/{login}/tokens). |
write access required (or triage access required) | The account's role, or the token's scope, is too low for the tool. A read token can only use read-only tools. |
agents may not push to protected or default branches; open a pull request | Commit to a new branch with create_branch_from and open a pull request. |
branch moved; refresh and retry | Someone else committed to the branch. Read the new head and retry with the new expected_head_sha. |
method not found: resources/list | The server only offers tools; the client can ignore this. |
MCP uploads are limited to 25 MB; use the upload_url of the REST API for larger files | upload_release_asset takes the file inline. Upload larger files (up to 100 MB) to the release's upload_url with the REST API. |
pass exactly one of content or content_base64 | upload_release_asset needs the file as either text or base64, not both and not neither. |
make_latest must be true, false or legacy | Pass make_latest as one of those strings, not as a boolean. |
query is required | search_docs was called with an empty query. |
no docs page "..."; pages: ... | read_docs didn't recognize the slug. Pick one from the list in the message, or call read_docs with index. |