# MCP reference

LogiKFlow's MCP server lets an agent read a pull request and save its reading order.

| | |
|---|---|
| Endpoint | `https://logikflow.dev/mcp` |
| Transport | Streamable HTTP (stateless, JSON responses) |
| Authorization | OAuth 2.1 with PKCE; scope `reviews` |
| Server name | `logikflow` |

## Tools

### get_ordering_guide

Returns the [ordering guide](/docs/ordering-guide) as Markdown. Call it once before saving an order. It takes no input and is read-only.

### get_pull_request

Returns a pull request's changed files with reading hints, the saved order and its version, whether you may change the order, and the review link. Read-only.

| Input | Type | Required | Description |
|---|---|---|---|
| `owner` | string | yes | Repository owner, for example `acme` |
| `repo` | string | yes | Repository name, for example `app` |
| `number` | integer | yes | Pull request number |
| `include_patches` | boolean | no | Include each file's diff. Default `false`. Each file is capped at 20,000 characters and the response at 400,000. |

Result (JSON text):

```json
{
  "reviewUrl": "https://logikflow.dev/r/acme/app/pull/123",
  "repository": "acme/app",
  "pullRequest": {
    "number": 123, "title": "Add team invites", "state": "open",
    "author": "octo", "base": "main", "head": "team-invites", "headSha": "9fce14f", "url": "https://github.com/acme/app/pull/123"
  },
  "canArrange": true,
  "filesTruncatedByGitHub": false,
  "files": [
    { "path": "models/invite.ts", "status": "added", "additions": 20, "deletions": 0 },
    { "path": "services/invite-service.ts", "status": "added", "additions": 112, "deletions": 0, "dependsOn": ["models/invite.ts"] },
    { "path": "services/invite-service.test.ts", "status": "added", "additions": 60, "deletions": 0, "dependsOn": ["services/invite-service.ts"], "testOf": "services/invite-service.ts" },
    { "path": "package-lock.json", "status": "modified", "additions": 4, "deletions": 4, "likelyGenerated": true },
    {
      "path": "app/router.ts", "status": "modified", "additions": 9, "deletions": 1, "dependsOn": ["services/invite-service.ts"],
      "changes": [
        { "id": "h1pdwiwd", "header": "@@ -12,6 +12,7 @@ import", "preview": "+ import { invites } from \"./invites\";", "additions": 1, "deletions": 0 },
        { "id": "hyzm99a", "header": "@@ -40,4 +41,12 @@ export const routes", "preview": "+ { path: \"/invites\", view: invites },", "additions": 8, "deletions": 1 }
      ]
    }
  ],
  "currentOrder": null,
  "baseVersion": 0
}
```

`currentDiagram`, when a diagram exists, has `version`, `updatedBy`, `savedBeforeLatestCommits` and `diagram` (the same shape `save_diagram` takes). `diagramBaseVersion` is its version, or `0`. `diagramPreference` is `always`, `ask` or `never`, from the repository's conventions file (`ask` when it doesn't say).

`currentOrder`, when an order exists, has `version`, `updatedBy`, `source` (`web` or `agent`), `savedBeforeLatestCommits` (true when commits were pushed after it was saved), `overview` (when one was written), `sections` in the same shape `save_reading_order` takes, and `suggestions`: what would make the current order easier to follow (see [Suggestions](/docs/ordering-guide#suggestions)). `status` is GitHub's: `added`, `removed`, `modified`, `renamed`, `copied`, `changed` or `unchanged`. Renamed files also have `previousPath`. Files with two or more changes (diff hunks) have `changes`, one entry per change: `id` (use it in `hunks` to split the file), `header` (the `@@` line), `preview` (the first changed line, up to 120 characters), `additions` and `deletions`. IDs come from the change's content, so they survive line shifts from other changes.

Reading hints appear on a file only when LogiKFlow could work them out: `dependsOn` (changed files it imports, from import lines visible in the diff), `testOf` (the changed file this test covers, from naming conventions) and `likelyGenerated` (lockfiles and minified, vendored or generated output). See [Reading hints](/docs/ordering-guide#reading-hints).

### save_reading_order

Saves the reading order, replacing the current one. Earlier versions stay in history. Requires write access to the repository.

| Input | Type | Required | Description |
|---|---|---|---|
| `owner` | string | yes | Repository owner |
| `repo` | string | yes | Repository name |
| `number` | integer | yes | Pull request number |
| `base_version` | integer | yes | `baseVersion` from `get_pull_request`; `0` when no order exists |
| `overview` | string | no | Up to 2,000 characters: two or three sentences reviewers read before the first section. Backticks render as code. |
| `sections` | array | yes | 1 to 50 sections, in reading order |

Each section:

| Field | Type | Description |
|---|---|---|
| `title` | string, optional | Up to 200 characters. Omit only for a single untitled group in a tiny pull request. |
| `layer` | string, optional | `contracts`, `models`, `logic`, `storage`, `app`, `ui`, `wiring`, `resources` or `tooling`. Shown next to the title. |
| `note` | string, optional | Up to 4,000 characters. Backticks render as code. |
| `checks` | string[], optional | Up to 20 checklist items, each up to 300 characters |
| `files` | array | Files in reading order: `{ "path", "note"?, "checks"?, "focus"?, "hunks"? }` with the same limits. `focus` is `key`, `skim` or `generated`. `hunks` is a list of change IDs; see below. |

### Splitting a file into parts

To show a file's changes in different sections, list the file more than once, each time with `hunks` naming the change IDs for that part:

- Each change ID may appear in only one part of the file.
- At most one appearance may leave out `hunks`. It takes every change not named elsewhere, including changes from later commits.
- If every appearance has `hunks`, every change of the file must be named.
- Files that GitHub gives no diff for (binary or very large) can't be split.

```json
{ "title": "Routes", "files": [{ "path": "app/router.ts", "hunks": ["hyzm99a"], "note": "Part 2: the `/invites` route." }] }
```

Example input:

```json
{
  "owner": "acme", "repo": "app", "number": 123, "base_version": 0,
  "overview": "Adds team invites behind the `invitesEnabled` flag. Start with the flag and migration, then the model and service, then the dialog that calls it. The replace-on-reinvite rule in `InviteService` is the part to read slowly.",
  "sections": [
    {
      "title": "Flag and schema",
      "layer": "contracts",
      "note": "Everything new is behind `invitesEnabled`, off by default.",
      "checks": ["With the flag off, the members page is unchanged"],
      "files": [{ "path": "lib/flags.ts" }, { "path": "db/migrations/0003_invites.sql" }]
    },
    {
      "title": "Invite model and service",
      "layer": "logic",
      "note": "Builds on the schema above. `InviteService` owns the lifecycle; the model is a plain record.",
      "files": [
        { "path": "models/invite.ts" },
        { "path": "services/invite-service.ts", "note": "Re-inviting the same email replaces the open invite.", "focus": "key" }
      ]
    }
  ]
}
```

Result:

```json
{ "saved": true, "version": 1, "reviewUrl": "https://logikflow.dev/r/acme/app/pull/123", "overview": true, "sections": 2, "files": 4, "checklistItems": 1, "suggestions": [], "nextStep": "Done. Share the review link with the user. The pull request is small, so skip the diagram unless the user asks for one." }
```

`suggestions` lists what would make the order easier to follow, in plain sentences the agent can act on; it is empty when the order follows the [ordering guide](/docs/ordering-guide#suggestions). The save already happened, so the agent applies what fits and saves again with the new version. `nextStep` says so when there are suggestions, then tells the agent what to do about a diagram: nothing, ask the user, or draw one because the repository wants one for every pull request.

A checklist item with the same text in the same section or file keeps its identity across saves, so reviewers' ticks survive a re-arrange.

### save_diagram

Saves an optional flow diagram, shown in the pull request's **Diagram** tab. It replaces the current diagram and doesn't change the reading order. Requires write access to the repository. See [Diagrams](/docs/ordering-guide#diagrams) in the ordering guide for when and how to draw one.

| Input | Type | Required | Description |
|---|---|---|---|
| `owner`, `repo`, `number` | | yes | As above |
| `base_version` | integer | yes | `diagramBaseVersion` from `get_pull_request`; `0` when there is no diagram |
| `diagram` | object | yes | `{ "title"?, "groups"?, "nodes", "edges" }` |

| Field | Type | Description |
|---|---|---|
| `title` | string, optional | Up to 200 characters |
| `groups` | array, optional | Up to 12 boxes: `{ "id", "title", "note"? }`. Titles up to 80 characters. |
| `nodes` | array | 2 to 40: `{ "id", "title", "kind"?, "group"?, "lines"?, "file"?, "hunk"? }`. `kind` is `trigger`, `logic` (default), `decision`, `data`, `ui`, `test`, `config`, `external` or `warning`. `lines` is up to 4 `{ "label", "text" }` rows. `file` must be a changed file; `hunk` a change id of that file. |
| `edges` | array | Up to 80: `{ "from", "to", "label"?, "dashed"? }`, between node ids. Labels up to 40 characters. |

Ids are 1 to 40 letters, digits, `-` or `_`, unique across groups and nodes.

```json
{
  "owner": "acme", "repo": "app", "number": 123, "base_version": 0,
  "diagram": {
    "title": "How an invite is accepted",
    "groups": [{ "id": "server", "title": "Server" }, { "id": "tests", "title": "Coverage" }],
    "nodes": [
      { "id": "link", "title": "Invite link opened", "kind": "trigger", "lines": [{ "label": "example", "text": "/invite/abc123" }] },
      { "id": "accept", "title": "Accept invite", "kind": "logic", "group": "server", "file": "services/invite-service.ts", "lines": [{ "label": "change", "text": "closes the invite after joining" }] },
      { "id": "expired", "title": "Reject expired", "kind": "warning", "group": "server", "file": "services/invite-service.ts" },
      { "id": "spec", "title": "Invite service spec", "kind": "test", "group": "tests", "file": "services/invite-service.test.ts" }
    ],
    "edges": [
      { "from": "link", "to": "accept", "label": "token" },
      { "from": "accept", "to": "expired", "label": "if expired" },
      { "from": "accept", "to": "spec", "label": "covered by", "dashed": true }
    ]
  }
}
```

Result:

```json
{ "saved": true, "version": 1, "diagramUrl": "https://logikflow.dev/r/acme/app/pull/123#diagram", "nodes": 4, "edges": 3, "groups": 2, "linkedFiles": 2 }
```

## Errors

Tool errors come back as a result with `isError: true` and a message the agent can act on. Nothing is saved when a save fails.

| Situation | Message starts with |
|---|---|
| A changed file is missing, unknown or listed twice | The order must list every changed file |
| A split names an unknown change, leaves changes unplaced, or splits a file without a diff | The order must list every changed file, and place every change of a split file exactly once |
| Someone saved a newer version | Conflict: … saved version N |
| No write access | … doesn't have write access |
| Invalid input, such as an empty checklist item | The order is invalid |
| A diagram node points at a file or change not in the pull request | The diagram points at files or changes that aren't in the pull request |
| Invalid diagram, such as an edge to an unknown node | The diagram is invalid |
| Someone saved a newer diagram | Conflict: … saved diagram version N |
| Pull request not visible | GitHub says this pull request doesn't exist or this account can't see it |
| GitHub token expired or revoked | GitHub access for this connection has expired |
| GitHub rate limit | GitHub's rate limit was reached |

## Authorization

LogiKFlow follows the [MCP authorization specification](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization).

| Endpoint | Path |
|---|---|
| Protected resource metadata | `/.well-known/oauth-protected-resource/mcp` |
| Authorization server metadata | `/.well-known/oauth-authorization-server` |
| Authorization (consent page) | `/authorize` |
| Token | `/oauth/token` |
| Dynamic client registration | `/oauth/register` |

Client ID metadata documents are also accepted. Unauthenticated requests to `/mcp` get `401` with a `WWW-Authenticate` challenge.

- **Access tokens** last up to one hour and are refreshed automatically.
- **Refresh tokens** last 30 days from last use and at most 180 days.
- **Rate limit:** 120 MCP requests per minute per person.
