@rockhopper-co/mcp-server 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +134 -1
  2. package/README.md +34 -10
  3. package/dist/api-client.d.ts +56 -5
  4. package/dist/api-client.d.ts.map +1 -1
  5. package/dist/api-client.js +86 -11
  6. package/dist/api-client.js.map +1 -1
  7. package/dist/auth/device-grant-client.d.ts +80 -0
  8. package/dist/auth/device-grant-client.d.ts.map +1 -0
  9. package/dist/auth/device-grant-client.js +138 -0
  10. package/dist/auth/device-grant-client.js.map +1 -0
  11. package/dist/auth/resolve-auth.d.ts +52 -0
  12. package/dist/auth/resolve-auth.d.ts.map +1 -0
  13. package/dist/auth/resolve-auth.js +100 -0
  14. package/dist/auth/resolve-auth.js.map +1 -0
  15. package/dist/auth/token-store.d.ts +51 -0
  16. package/dist/auth/token-store.d.ts.map +1 -0
  17. package/dist/auth/token-store.js +98 -0
  18. package/dist/auth/token-store.js.map +1 -0
  19. package/dist/cli.js +37 -11
  20. package/dist/cli.js.map +1 -1
  21. package/dist/prompts/index.d.ts.map +1 -1
  22. package/dist/prompts/index.js +20 -9
  23. package/dist/prompts/index.js.map +1 -1
  24. package/dist/resources/changes.d.ts.map +1 -1
  25. package/dist/resources/changes.js +8 -2
  26. package/dist/resources/changes.js.map +1 -1
  27. package/dist/resources/orchestration-guide.md +1 -1
  28. package/dist/server.js +1 -1
  29. package/dist/server.js.map +1 -1
  30. package/dist/tools/search.d.ts.map +1 -1
  31. package/dist/tools/search.js +113 -20
  32. package/dist/tools/search.js.map +1 -1
  33. package/dist/tools/write-files.js +4 -4
  34. package/dist/tools/write-files.js.map +1 -1
  35. package/dist/tools/write-reviews.d.ts.map +1 -1
  36. package/dist/tools/write-reviews.js +3 -1
  37. package/dist/tools/write-reviews.js.map +1 -1
  38. package/dist/types.d.ts +23 -1
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/zod-schemas.d.ts +111 -0
  41. package/dist/zod-schemas.d.ts.map +1 -0
  42. package/dist/zod-schemas.js +62 -0
  43. package/dist/zod-schemas.js.map +1 -0
  44. package/package.json +6 -1
package/CHANGELOG.md CHANGED
@@ -4,7 +4,140 @@ All notable changes to this project are documented here. Follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [0.5.0] — 2026-05-12
7
+ ## [Unreleased]
8
+
9
+ ### Fixed
10
+ - **`get_cell_history`, `resolve_comment`, and `rename_file` no longer
11
+ render `undefined` for every field (KI-096).** Diagnosis revealed the
12
+ formatters were correct — the backend was returning the wrong shape
13
+ on all three endpoints. Backend PR
14
+ [#478](https://github.com/Rockhopper-Co/backend/pull/478) fixed the
15
+ shapes; this PR adopts them:
16
+ - `getCellHistory` passes `?format=mcp` to opt into the backend's
17
+ normalized projection (`{versionId, value, changedBy, changedAt}`).
18
+ Default `format` preserves the raw-CTE shape the frontend cell-
19
+ history popover consumes — we never call that path.
20
+ - `resolveComment` + `updateEnrolledFile` continue to call the same
21
+ URLs but now receive the updated `FileChat` / `EnrolledFile` entity
22
+ (was TypeORM `UpdateResult`).
23
+ - `CellHistoryEntry.versionId` retyped `number` → `string` (was a
24
+ contributing root cause of the `Version undefined` symptom — the
25
+ backend's semver string never coerced to the declared numeric type).
26
+ - **`api-client.ts`: zod-parse opt-in for response validation
27
+ (KI-096).** `request<T>(path, init?, responseSchema?)` now accepts an
28
+ optional zod schema; when supplied, the response is parsed with
29
+ `safeParse` and any drift throws a useful diagnostic
30
+ (`Rockhopper API response failed schema check at <path>: <field> —
31
+ <message>`) instead of silently rendering `undefined` in formatters.
32
+ Three call sites opt in: `getCellHistory`, `resolveComment`,
33
+ `updateEnrolledFile`. Other methods stay unchanged; a sweep ticket
34
+ can migrate them later. New `src/zod-schemas.ts` module holds the
35
+ per-entity schemas.
36
+
37
+ ### Added
38
+ - **`search_files` `matchIn` parameter.** Optional enum (`name` |
39
+ `comments` | `versions` | `all`); defaults to `name` for back-compat.
40
+ Broadens search past file-name substring into comment text
41
+ (`FileChat.message`) and version descriptions (`FileVersion.description`).
42
+ Backed by [backend PR #472](https://github.com/Rockhopper-Co/backend/pull/472)
43
+ / ENG-1383; behavior available once that merges. Closes KI-080.
44
+ - **OAuth device-grant flow (RFC 8628) as default auth.** First launch
45
+ with no `ROCKHOPPER_TOKEN` env var now prints a verification code +
46
+ URL to stderr, polls the backend's `/auth/device/{code,token}`
47
+ endpoints, and persists the resulting bearer token in the OS keychain
48
+ (Keychain on macOS, Credential Manager on Windows, libsecret on
49
+ Linux). Subsequent launches reuse the stored token silently. Tokens
50
+ default to a 60-minute lifetime; on expiry the next launch silently
51
+ re-runs the flow. `ROCKHOPPER_TOKEN` still takes precedence when set
52
+ — PAT path preserved for headless / CI scenarios. Backed by
53
+ [backend PR #473](https://github.com/Rockhopper-Co/backend/pull/473)
54
+ / ENG-1384. Closes KI-081 / ENG-1444.
55
+ - **`get_unattributed_changes` cursor pagination + cap/summary (KI-097).**
56
+ File-wide mode now uses the dedicated paginated backend route
57
+ (`GET /unattributed-changes/paginated/:fileMsId`, added by
58
+ [backend PR #475](https://github.com/Rockhopper-Co/backend/pull/475) /
59
+ KI-102) instead of the legacy unpaginated route. The MCP tool gains an
60
+ optional `cursor` input for round-tripping the backend's cursor.
61
+ Responses are now capped at 200 displayed rows with a summary line
62
+ ("Showing X of Y change(s) on this page (Z total across the file)" +
63
+ top-sheets breakdown) and a pagination hint when more pages or hidden
64
+ rows are available. Audit measured one file at 12.5 MB / 28k rows on
65
+ the old route — context-blowing for AI clients; now bounded under
66
+ the 25k-token MCP limit. Sheet-filter mode (`sheetName` set) is
67
+ unchanged — it stays unpaginated since sheet size inherently bounds
68
+ it. Closes KI-097.
69
+
70
+ ### Changed
71
+ - `ROCKHOPPER_TOKEN` is now **optional** (was required). Unset → OAuth.
72
+ Set → PAT auth path.
73
+ - **`ApiClient.getUnattributedChanges` refactored into two methods**
74
+ (KI-097): `getUnattributedChangesBySheet(fileMsId, sheetName)` for
75
+ the sheet-filtered legacy route, and
76
+ `getUnattributedChangesPaginated(fileMsId, cursor?)` for the new
77
+ cursor-paginated route. The old combined method is removed. External
78
+ consumers of `ApiClient` (e.g. `mcp-gateway`) only use `createServer`
79
+ + `ApiClient` as types, not these methods directly, so the rename has
80
+ zero blast radius outside this repo.
81
+
82
+ ### Dependencies
83
+ - **`keytar`** (new runtime dep) — OS-native keychain wrapper. Linux
84
+ requires `libsecret` installed; otherwise the OAuth path errors with
85
+ a clear remediation message and you must fall back to PAT.
86
+
87
+ ### Changed (breaking)
88
+ - **`update_file_description` renamed to `rename_file`.** The tool always
89
+ performed a rename (its `name` input wires to backend
90
+ `PATCH /enrolled-files/:fileMsId`); the old name described non-existent
91
+ "description" semantics. Customers using the npm-installed local server
92
+ should update any tool-name allowlists or prompts referencing
93
+ `update_file_description`. Closes KI-100 / ENG-1439.
94
+
95
+ ### Fixed
96
+ - **`cancel_review` now actually cancels PENDING reviews.** The
97
+ pre-flight status check at `tools/write-reviews.ts` compared against
98
+ the lowercase string `'pending'`, but the backend's
99
+ `ReviewRequestStatus` enum is uppercase (`PENDING`/`APPROVED`/
100
+ `CANCELLED`). The check always failed → `api.cancelReview()` was never
101
+ invoked → every cancel returned "cannot be cancelled — status is
102
+ 'PENDING'". Now uses a defensive `.toUpperCase()` comparison so the
103
+ fix survives future backend casing flips. Closes KI-099 / ENG-1438.
104
+
105
+ ### Internal
106
+ - Test fixtures in `src/__tests__/unit/test-helpers.ts` and
107
+ `src/__tests__/e2e/fixtures/rockhopper-api-fixtures.ts` updated to use
108
+ the real backend's uppercase `ReviewRequestStatus` enum values. Prior
109
+ fixtures used lowercase, which masked KI-099 by being bug-symmetric
110
+ with the broken code.
111
+
112
+ ### Fixed (bundled — same casing-bug class as KI-099)
113
+ - **`file-overview` prompt no longer mis-classifies APPROVED + CANCELLED
114
+ reviews as pending.** `prompts/index.ts:180` filtered with lowercase
115
+ `r.status !== 'approved' && r.status !== 'rejected'`, but
116
+ `ReviewRequestStatus` is uppercase and contains no `'rejected'` value
117
+ (`PENDING`/`APPROVED`/`CANCELLED` only). Result: all non-pending
118
+ reviews were silently counted as pending in the prompt output. Now
119
+ filters with positive intent — `r.status?.toUpperCase() === 'PENDING'`.
120
+ Sibling fix to KI-099; same casing-bug class but in a different
121
+ surface (prompt vs. tool).
122
+
123
+ ### Tooling
124
+ - **`npm run lint` now works.** The `lint` script referenced
125
+ `eslint src/` but `eslint` was missing from `devDependencies`, so any
126
+ fresh install silently produced `sh: eslint: command not found`. Added
127
+ `@eslint/js`, `eslint`, `globals`, and `typescript-eslint` as devDeps
128
+ and shipped a flat-config `eslint.config.js` that mirrors the
129
+ `mcp-gateway` repo's setup. Test files relax
130
+ `@typescript-eslint/no-explicit-any` (mock-stub casts) while production
131
+ code keeps the rule on. Lint is clean across `src/`.
132
+
133
+ ## [0.6.0] — 2026-05-13
134
+
135
+ > Released to npm as `0.6.0`. The release branch was prepared with
136
+ > `package.json` at `0.5.0` (set in [PR #37](https://github.com/Rockhopper-Co/mcp-server/pull/37)),
137
+ > but the `release:minor` script ran on top of that and produced `0.6.0`
138
+ > via `npm version minor`. No code differs between the `0.5.0` manifest
139
+ > and the `0.6.0` published artifact — the bump is purely a version-string
140
+ > change. Content below is what shipped.
8
141
 
9
142
  ### Changed
10
143
  - **`resources/list` no longer enumerates per-file instances.** The 4
package/README.md CHANGED
@@ -6,17 +6,41 @@ MCP (Model Context Protocol) server for Rockhopper. Lets AI tools like Claude, C
6
6
 
7
7
  - Node.js 18+
8
8
  - A Rockhopper account with at least one enrolled file
9
- - A Personal Access Token (PAT) from Rockhopper
9
+ - (Optional) A Personal Access Token only required for headless / CI / scripted setups
10
10
 
11
- ## Setup
11
+ ## Authentication
12
12
 
13
- ### 1. Create a Personal Access Token
13
+ The server supports two auth modes. **OAuth (recommended)** is the default — no token to copy and paste. **PAT** stays available for headless scenarios.
14
14
 
15
- In the Rockhopper web app, go to **Settings > Personal Access Tokens** and create a new token. Choose `read-only` scope for read access or `read-write` if you also want to add comments/reviews.
15
+ ### OAuth (recommended)
16
16
 
17
- Copy the tokenit's shown only once.
17
+ On first launch, the server prints a short verification code to stderr and a URL to visit. Sign in once in your browser the resulting bearer token is stored in your OS keychain (Keychain on macOS, Credential Manager on Windows, libsecret on Linux). Subsequent launches pick the token up silently.
18
18
 
19
- ### 2. Install
19
+ Nothing to configure — just launch the server with no `ROCKHOPPER_TOKEN` set:
20
+
21
+ ```bash
22
+ npx @rockhopper-co/mcp-server
23
+ ```
24
+
25
+ You'll see (on stderr):
26
+
27
+ ```
28
+ Rockhopper — sign in to authorize this MCP client.
29
+ Open: https://app.rockhopper.co/device?user_code=ABCD2345
30
+ (or visit https://app.rockhopper.co/device and enter code: ABCD2345)
31
+ ```
32
+
33
+ Tokens default to a 60-minute lifetime. When yours expires, the next launch silently re-runs the device flow.
34
+
35
+ > **Linux**: requires `libsecret` to be installed (`apt-get install libsecret-1-dev` on Debian/Ubuntu, `dnf install libsecret` on Fedora). If unavailable, fall back to the PAT path below.
36
+
37
+ ### Personal Access Token (headless / CI)
38
+
39
+ For non-interactive setups, generate a PAT in the Rockhopper web app under **Settings > Personal Access Tokens** (`read-only` or `read-write` scope) and set it as `ROCKHOPPER_TOKEN`. PATs take precedence over OAuth — if `ROCKHOPPER_TOKEN` is set, the device-grant flow is skipped.
40
+
41
+ ## Setup
42
+
43
+ ### 1. Install
20
44
 
21
45
  ```bash
22
46
  npm install -g @rockhopper-co/mcp-server
@@ -28,7 +52,7 @@ Or run directly with npx:
28
52
  npx @rockhopper-co/mcp-server
29
53
  ```
30
54
 
31
- ### 3. Configure your AI tool
55
+ ### 2. Configure your AI tool
32
56
 
33
57
  #### Claude Desktop / Claude Code
34
58
 
@@ -41,7 +65,6 @@ Add to your MCP config (`~/.claude/mcp.json` or Claude Desktop settings):
41
65
  "command": "npx",
42
66
  "args": ["-y", "@rockhopper-co/mcp-server"],
43
67
  "env": {
44
- "ROCKHOPPER_TOKEN": "rh_pat_your_token_here",
45
68
  "ROCKHOPPER_API_URL": "https://api.rockhopper.co"
46
69
  }
47
70
  }
@@ -49,6 +72,8 @@ Add to your MCP config (`~/.claude/mcp.json` or Claude Desktop settings):
49
72
  }
50
73
  ```
51
74
 
75
+ Leave `ROCKHOPPER_TOKEN` out to use OAuth (recommended). Set it if you want PAT auth instead.
76
+
52
77
  #### Cursor
53
78
 
54
79
  Add to `.cursor/mcp.json` in your project:
@@ -60,7 +85,6 @@ Add to `.cursor/mcp.json` in your project:
60
85
  "command": "npx",
61
86
  "args": ["-y", "@rockhopper-co/mcp-server"],
62
87
  "env": {
63
- "ROCKHOPPER_TOKEN": "rh_pat_your_token_here",
64
88
  "ROCKHOPPER_API_URL": "https://api.rockhopper.co"
65
89
  }
66
90
  }
@@ -72,7 +96,7 @@ Add to `.cursor/mcp.json` in your project:
72
96
 
73
97
  | Variable | Required | Default | Description |
74
98
  |----------|----------|---------|-------------|
75
- | `ROCKHOPPER_TOKEN` | Yes | — | Personal Access Token (starts with `rh_pat_`) |
99
+ | `ROCKHOPPER_TOKEN` | No | — | Personal Access Token (starts with `rh_pat_`). When unset, OAuth device-grant flow runs on first launch. |
76
100
  | `ROCKHOPPER_API_URL` | No | `https://api.rockhopper.co` | Rockhopper API base URL |
77
101
 
78
102
  ## Postman
@@ -1,4 +1,4 @@
1
- import type { CellHistoryEntry, EnrolledFile, FileChat, FileVersion, ReviewActivity, ReviewRequest, Team, UnattributedChange, UserSummary } from './types.js';
1
+ import type { CellHistoryEntry, EnrolledFile, FileChat, FileVersion, PaginatedUnattributedResponse, ReviewActivity, ReviewRequest, Team, UnattributedChange, UserSummary } from './types.js';
2
2
  export interface ApiClientConfig {
3
3
  baseUrl: string;
4
4
  token: string;
@@ -7,15 +7,32 @@ export declare class ApiClient {
7
7
  private readonly baseUrl;
8
8
  private readonly token;
9
9
  constructor(config: ApiClientConfig);
10
+ /**
11
+ * KI-096: optional `responseSchema` validates the response shape with
12
+ * zod. When supplied, drift between backend's actual response and the
13
+ * mcp-server's declared type fails LOUDLY with a `ZodError` (wrapped
14
+ * here in an Error with the path that drifted) instead of silently
15
+ * rendering `undefined` in tool formatters. Opt-in per call site so
16
+ * existing methods stay untouched until a sweep migrates them.
17
+ */
10
18
  private request;
11
19
  getMe(): Promise<UserSummary>;
12
20
  getTeam(teamId: number): Promise<Team>;
13
21
  listEnrolledFiles(params?: {
14
22
  search?: string;
23
+ matchIn?: 'name' | 'comments' | 'versions' | 'all';
15
24
  }): Promise<EnrolledFile[]>;
16
25
  getEnrolledFile(fileMsId: string): Promise<EnrolledFile>;
17
26
  getFileVersions(fileMsId: string): Promise<FileVersion[]>;
18
27
  getFileVersion(versionInternalId: number): Promise<FileVersion>;
28
+ /**
29
+ * KI-096: passes `?format=mcp` to opt into the backend's normalized
30
+ * projection `{versionId, value, changedBy, changedAt}` (added by
31
+ * backend PR #478). Default `format` (omitted) returns the raw CTE
32
+ * row shape the frontend cell-history popover consumes — we never
33
+ * call that path. Zod-parses the response so future drift between
34
+ * backend and mcp-server contracts fails loudly.
35
+ */
19
36
  getCellHistory(fileMsId: string, sheetName: string, cellAddress: string): Promise<CellHistoryEntry[]>;
20
37
  getFileComments(fileMsId: string): Promise<FileChat[]>;
21
38
  getComment(chatId: number): Promise<FileChat>;
@@ -29,6 +46,14 @@ export declare class ApiClient {
29
46
  message: string;
30
47
  versionInternalId: number;
31
48
  }): Promise<FileChat>;
49
+ /**
50
+ * KI-096: zod-parses the response. Backend PR #478 fixed
51
+ * `PATCH /file-chat/:chatId` to return the updated entity (was
52
+ * UpdateResult) AND to persist `resolved` (was silently dropped).
53
+ * The schema check pins both fixes — if either regresses, the parse
54
+ * fails with a clear message instead of the formatter rendering
55
+ * `Comment undefined marked as resolved.`
56
+ */
32
57
  resolveComment(chatId: number): Promise<FileChat>;
33
58
  getReviewsForVersion(versionId: number): Promise<ReviewRequest[]>;
34
59
  getReviewsForLatestVersion(fileMsId: string): Promise<ReviewRequest[]>;
@@ -43,10 +68,28 @@ export declare class ApiClient {
43
68
  approveReview(reviewId: number, body: {
44
69
  notes?: string;
45
70
  }): Promise<ReviewRequest>;
46
- getUnattributedChanges(fileMsId: string, params?: {
47
- sheetName?: string;
48
- status?: string;
49
- }): Promise<UnattributedChange[]>;
71
+ /**
72
+ * Sheet-filtered unattributed changes. Returns ALL rows for the given
73
+ * sheet on the file (no pagination — sheet filter inherently bounds
74
+ * result size). Use this when the caller already knows which sheet to
75
+ * inspect; use {@link getUnattributedChangesPaginated} for the file-wide
76
+ * view.
77
+ */
78
+ getUnattributedChangesBySheet(fileMsId: string, sheetName: string): Promise<UnattributedChange[]>;
79
+ /**
80
+ * Cursor-paginated file-wide unattributed changes (KI-097).
81
+ *
82
+ * Hits the non-shadowable `GET /unattributed-changes/paginated/:fileMsId`
83
+ * route added by backend PR #475 (KI-102). The legacy `:fileMsId/v2`
84
+ * route is shadowed by `:fileMsId/:sheetName` route ordering and returns
85
+ * an empty array; do not call it from here.
86
+ *
87
+ * Pass `cursor` returned by a previous call to fetch the next page.
88
+ * Snapshot TTL is 30 minutes — older cursors cause the backend to return
89
+ * HTTP 410 GONE with `{ resyncRequired: { code: 'SNAPSHOT_EXPIRED' } }`,
90
+ * which surfaces here as a thrown RockhopperApiError.
91
+ */
92
+ getUnattributedChangesPaginated(fileMsId: string, cursor?: string): Promise<PaginatedUnattributedResponse>;
50
93
  createVersion(body: {
51
94
  enrolledFileMsId: string;
52
95
  version: {
@@ -60,6 +103,14 @@ export declare class ApiClient {
60
103
  description: string;
61
104
  }): Promise<FileVersion>;
62
105
  cancelReview(reviewId: number): Promise<ReviewRequest>;
106
+ /**
107
+ * KI-096: zod-parses the response. Backend PR #478 fixed
108
+ * `PATCH /enrolled-files/:fileMsId` to return the updated entity
109
+ * (was UpdateResult typed as Promise<any>). The schema check pins
110
+ * the entity contract — if it regresses, the parse fails with a
111
+ * clear message instead of `rename_file` rendering
112
+ * `File renamed to 'undefined' (id: undefined).`
113
+ */
63
114
  updateEnrolledFile(fileMsId: string, body: {
64
115
  name?: string;
65
116
  }): Promise<EnrolledFile>;
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { CellHistoryEntryArraySchema, EnrolledFileSchema, FileChatSchema, } from './zod-schemas.js';
1
2
  export class ApiClient {
2
3
  baseUrl;
3
4
  token;
@@ -5,7 +6,15 @@ export class ApiClient {
5
6
  this.baseUrl = config.baseUrl.replace(/\/+$/, '');
6
7
  this.token = config.token;
7
8
  }
8
- async request(path, init) {
9
+ /**
10
+ * KI-096: optional `responseSchema` validates the response shape with
11
+ * zod. When supplied, drift between backend's actual response and the
12
+ * mcp-server's declared type fails LOUDLY with a `ZodError` (wrapped
13
+ * here in an Error with the path that drifted) instead of silently
14
+ * rendering `undefined` in tool formatters. Opt-in per call site so
15
+ * existing methods stay untouched until a sweep migrates them.
16
+ */
17
+ async request(path, init, responseSchema) {
9
18
  const url = `${this.baseUrl}${path}`;
10
19
  const response = await fetch(url, {
11
20
  ...init,
@@ -19,7 +28,21 @@ export class ApiClient {
19
28
  const body = await response.text().catch(() => '');
20
29
  throw new Error(`Rockhopper API ${response.status}: ${response.statusText} — ${body}`);
21
30
  }
22
- return response.json();
31
+ const json = await response.json();
32
+ if (responseSchema) {
33
+ const parsed = responseSchema.safeParse(json);
34
+ if (!parsed.success) {
35
+ // Surface a useful diagnostic — the formatter would otherwise show
36
+ // `undefined`, hiding the contract break. Include the path of the
37
+ // first issue so the cause is obvious from the error message.
38
+ const first = parsed.error.issues[0];
39
+ throw new Error(`Rockhopper API response failed schema check at ${path}: ` +
40
+ `${first?.path.join('.') || '<root>'} — ${first?.message} ` +
41
+ `(${parsed.error.issues.length} issue(s) total)`);
42
+ }
43
+ return parsed.data;
44
+ }
45
+ return json;
23
46
  }
24
47
  // --- Users ---
25
48
  async getMe() {
@@ -34,6 +57,8 @@ export class ApiClient {
34
57
  const query = new URLSearchParams();
35
58
  if (params?.search)
36
59
  query.set('search', params.search);
60
+ if (params?.matchIn)
61
+ query.set('matchIn', params.matchIn);
37
62
  const qs = query.toString();
38
63
  return this.request(`/enrolled-files${qs ? `?${qs}` : ''}`);
39
64
  }
@@ -47,9 +72,21 @@ export class ApiClient {
47
72
  async getFileVersion(versionInternalId) {
48
73
  return this.request(`/file-versions/file/version/${versionInternalId}`);
49
74
  }
75
+ /**
76
+ * KI-096: passes `?format=mcp` to opt into the backend's normalized
77
+ * projection `{versionId, value, changedBy, changedAt}` (added by
78
+ * backend PR #478). Default `format` (omitted) returns the raw CTE
79
+ * row shape the frontend cell-history popover consumes — we never
80
+ * call that path. Zod-parses the response so future drift between
81
+ * backend and mcp-server contracts fails loudly.
82
+ */
50
83
  async getCellHistory(fileMsId, sheetName, cellAddress) {
51
- const query = new URLSearchParams({ cell: cellAddress, sheetName });
52
- return this.request(`/file-versions/file/${fileMsId}/cell-history?${query}`);
84
+ const query = new URLSearchParams({
85
+ cell: cellAddress,
86
+ sheetName,
87
+ format: 'mcp',
88
+ });
89
+ return this.request(`/file-versions/file/${fileMsId}/cell-history?${query}`, undefined, CellHistoryEntryArraySchema);
53
90
  }
54
91
  // --- File Chat (Comments) ---
55
92
  async getFileComments(fileMsId) {
@@ -70,11 +107,19 @@ export class ApiClient {
70
107
  body: JSON.stringify(body),
71
108
  });
72
109
  }
110
+ /**
111
+ * KI-096: zod-parses the response. Backend PR #478 fixed
112
+ * `PATCH /file-chat/:chatId` to return the updated entity (was
113
+ * UpdateResult) AND to persist `resolved` (was silently dropped).
114
+ * The schema check pins both fixes — if either regresses, the parse
115
+ * fails with a clear message instead of the formatter rendering
116
+ * `Comment undefined marked as resolved.`
117
+ */
73
118
  async resolveComment(chatId) {
74
119
  return this.request(`/file-chat/${chatId}`, {
75
120
  method: 'PATCH',
76
121
  body: JSON.stringify({ resolved: true }),
77
- });
122
+ }, FileChatSchema);
78
123
  }
79
124
  // --- Reviews ---
80
125
  async getReviewsForVersion(versionId) {
@@ -102,11 +147,33 @@ export class ApiClient {
102
147
  });
103
148
  }
104
149
  // --- Unattributed Changes ---
105
- async getUnattributedChanges(fileMsId, params) {
106
- let path = `/unattributed-changes/${fileMsId}`;
107
- if (params?.sheetName) {
108
- path += `/${params.sheetName}`;
109
- }
150
+ /**
151
+ * Sheet-filtered unattributed changes. Returns ALL rows for the given
152
+ * sheet on the file (no pagination — sheet filter inherently bounds
153
+ * result size). Use this when the caller already knows which sheet to
154
+ * inspect; use {@link getUnattributedChangesPaginated} for the file-wide
155
+ * view.
156
+ */
157
+ async getUnattributedChangesBySheet(fileMsId, sheetName) {
158
+ const path = `/unattributed-changes/${fileMsId}/${encodeURIComponent(sheetName)}`;
159
+ return this.request(path);
160
+ }
161
+ /**
162
+ * Cursor-paginated file-wide unattributed changes (KI-097).
163
+ *
164
+ * Hits the non-shadowable `GET /unattributed-changes/paginated/:fileMsId`
165
+ * route added by backend PR #475 (KI-102). The legacy `:fileMsId/v2`
166
+ * route is shadowed by `:fileMsId/:sheetName` route ordering and returns
167
+ * an empty array; do not call it from here.
168
+ *
169
+ * Pass `cursor` returned by a previous call to fetch the next page.
170
+ * Snapshot TTL is 30 minutes — older cursors cause the backend to return
171
+ * HTTP 410 GONE with `{ resyncRequired: { code: 'SNAPSHOT_EXPIRED' } }`,
172
+ * which surfaces here as a thrown RockhopperApiError.
173
+ */
174
+ async getUnattributedChangesPaginated(fileMsId, cursor) {
175
+ const qs = cursor ? `?cursor=${encodeURIComponent(cursor)}` : '';
176
+ const path = `/unattributed-changes/paginated/${fileMsId}${qs}`;
110
177
  return this.request(path);
111
178
  }
112
179
  // --- Version lifecycle ---
@@ -130,11 +197,19 @@ export class ApiClient {
130
197
  });
131
198
  }
132
199
  // --- File metadata update ---
200
+ /**
201
+ * KI-096: zod-parses the response. Backend PR #478 fixed
202
+ * `PATCH /enrolled-files/:fileMsId` to return the updated entity
203
+ * (was UpdateResult typed as Promise<any>). The schema check pins
204
+ * the entity contract — if it regresses, the parse fails with a
205
+ * clear message instead of `rename_file` rendering
206
+ * `File renamed to 'undefined' (id: undefined).`
207
+ */
133
208
  async updateEnrolledFile(fileMsId, body) {
134
209
  return this.request(`/enrolled-files/${fileMsId}`, {
135
210
  method: 'PATCH',
136
211
  body: JSON.stringify(body),
137
- });
212
+ }, EnrolledFileSchema);
138
213
  }
139
214
  }
140
215
  //# sourceMappingURL=api-client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAiBA,MAAM,OAAO,SAAS;IACH,OAAO,CAAS;IAChB,KAAK,CAAS;IAE/B,YAAY,MAAuB;QACjC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,CAAC;IAEO,KAAK,CAAC,OAAO,CAAI,IAAY,EAAE,IAAkB;QACvD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,GAAG,IAAI;YACP,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;gBACrC,cAAc,EAAE,kBAAkB;gBAClC,GAAG,IAAI,EAAE,OAAO;aACjB;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACnD,MAAM,IAAI,KAAK,CACb,kBAAkB,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,MAAM,IAAI,EAAE,CACtE,CAAC;QACJ,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,EAAgB,CAAC;IACvC,CAAC;IAED,gBAAgB;IAEhB,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,CAAC,OAAO,CAAc,WAAW,CAAC,CAAC;IAChD,CAAC;IAED,gBAAgB;IAEhB,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,OAAO,IAAI,CAAC,OAAO,CAAO,UAAU,MAAM,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,yBAAyB;IAEzB,KAAK,CAAC,iBAAiB,CAAC,MAEvB;QACC,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAC;QACpC,IAAI,MAAM,EAAE,MAAM;YAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACvD,MAAM,EAAE,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC,OAAO,CACjB,kBAAkB,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACvC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,QAAgB;QACpC,OAAO,IAAI,CAAC,OAAO,CAAe,mBAAmB,QAAQ,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,wBAAwB;IAExB,KAAK,CAAC,eAAe,CAAC,QAAgB;QACpC,OAAO,IAAI,CAAC,OAAO,CAAgB,uBAAuB,QAAQ,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,iBAAyB;QAC5C,OAAO,IAAI,CAAC,OAAO,CACjB,+BAA+B,iBAAiB,EAAE,CACnD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,cAAc,CAClB,QAAgB,EAChB,SAAiB,EACjB,WAAmB;QAEnB,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC,OAAO,CACjB,uBAAuB,QAAQ,iBAAiB,KAAK,EAAE,CACxD,CAAC;IACJ,CAAC;IAED,+BAA+B;IAE/B,KAAK,CAAC,eAAe,CAAC,QAAgB;QACpC,OAAO,IAAI,CAAC,OAAO,CAAa,cAAc,QAAQ,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAc;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAW,qBAAqB,MAAM,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,IAKnB;QACC,OAAO,IAAI,CAAC,OAAO,CAAW,YAAY,EAAE;YAC1C,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,cAAc,CAClB,MAAc,EACd,IAAoD;QAEpD,OAAO,IAAI,CAAC,OAAO,CAAW,cAAc,MAAM,UAAU,EAAE;YAC5D,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,MAAc;QACjC,OAAO,IAAI,CAAC,OAAO,CAAW,cAAc,MAAM,EAAE,EAAE;YACpD,MAAM,EAAE,OAAO;YACf,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;SACzC,CAAC,CAAC;IACL,CAAC;IAED,kBAAkB;IAElB,KAAK,CAAC,oBAAoB,CACxB,SAAiB;QAEjB,OAAO,IAAI,CAAC,OAAO,CACjB,qBAAqB,SAAS,WAAW,CAC1C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,0BAA0B,CAC9B,QAAgB;QAEhB,OAAO,IAAI,CAAC,OAAO,CACjB,kBAAkB,QAAQ,0BAA0B,CACrD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,QAAgB;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAgB,qBAAqB,QAAQ,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,KAAK,CAAC,mBAAmB,CACvB,QAAgB;QAEhB,OAAO,IAAI,CAAC,OAAO,CACjB,qBAAqB,QAAQ,aAAa,CAC3C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,IAKzB;QACC,OAAO,IAAI,CAAC,OAAO,CAAgB,mBAAmB,EAAE;YACtD,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,QAAgB,EAChB,IAAwB;QAExB,OAAO,IAAI,CAAC,OAAO,CACjB,qBAAqB,QAAQ,UAAU,EACvC;YACE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CACF,CAAC;IACJ,CAAC;IAED,+BAA+B;IAE/B,KAAK,CAAC,sBAAsB,CAC1B,QAAgB,EAChB,MAAgD;QAEhD,IAAI,IAAI,GAAG,yBAAyB,QAAQ,EAAE,CAAC;QAC/C,IAAI,MAAM,EAAE,SAAS,EAAE,CAAC;YACtB,IAAI,IAAI,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACjC,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAuB,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,4BAA4B;IAE5B,KAAK,CAAC,aAAa,CAAC,IAQnB;QACC,OAAO,IAAI,CAAC,OAAO,CAAc,gBAAgB,EAAE;YACjD,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,cAAc,CAClB,QAAgB,EAChB,IAA6B;QAE7B,OAAO,IAAI,CAAC,OAAO,CACjB,oCAAoC,QAAQ,EAAE,EAC9C;YACE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CACF,CAAC;IACJ,CAAC;IAED,2BAA2B;IAE3B,KAAK,CAAC,YAAY,CAAC,QAAgB;QACjC,OAAO,IAAI,CAAC,OAAO,CAAgB,qBAAqB,QAAQ,EAAE,EAAE;YAClE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;SAC9C,CAAC,CAAC;IACL,CAAC;IAED,+BAA+B;IAE/B,KAAK,CAAC,kBAAkB,CACtB,QAAgB,EAChB,IAAuB;QAEvB,OAAO,IAAI,CAAC,OAAO,CAAe,mBAAmB,QAAQ,EAAE,EAAE;YAC/D,MAAM,EAAE,OAAO;YACf,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAC;IACL,CAAC;CACF"}
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * ENG-1444 / KI-081 — RFC 8628 device-grant client.
3
+ *
4
+ * Talks to the backend's `POST /auth/device/{code,token}` endpoints
5
+ * (introduced in ENG-1384 PR 1, `Rockhopper-Co/backend#473`). Used by
6
+ * the mcp-server CLI when no PAT env var is set and no OAuth bundle
7
+ * is stored in the OS keychain.
8
+ *
9
+ * Surfaces a single entrypoint, `runDeviceGrantFlow`, that:
10
+ *
11
+ * 1. Calls `/auth/device/code` to get a (deviceCode, userCode) pair.
12
+ * 2. Emits the user-facing `userCode` + verification URI to stderr
13
+ * (LLM clients pick this up via stdout's stderr passthrough).
14
+ * 3. Polls `/auth/device/token` at the server-specified interval,
15
+ * respecting RFC 8628 § 3.5 `slow_down` (bumps interval +5s) and
16
+ * `authorization_pending` (continues polling).
17
+ * 4. Resolves with the access-token bundle on success, rejects on
18
+ * `access_denied` / `expired_token` / network failure.
19
+ *
20
+ * No external HTTP dependency — uses globalThis.fetch (Node 18+).
21
+ * `fetchImpl` + `sleep` + `onUserCode` are injectable for tests.
22
+ */
23
+ export interface DeviceCodeResponse {
24
+ deviceCode: string;
25
+ userCode: string;
26
+ verificationUri: string;
27
+ verificationUriComplete: string;
28
+ expiresIn: number;
29
+ interval: number;
30
+ }
31
+ export interface DeviceTokenSuccess {
32
+ accessToken: string;
33
+ tokenType: 'Bearer';
34
+ expiresIn: number;
35
+ refreshToken?: string;
36
+ }
37
+ export interface DeviceGrantFlowOptions {
38
+ baseUrl: string;
39
+ clientId: string;
40
+ /** Override for tests. Defaults to globalThis.fetch. */
41
+ fetchImpl?: typeof fetch;
42
+ /** Override for tests. Defaults to setTimeout-based sleep. */
43
+ sleep?: (ms: number) => Promise<void>;
44
+ /** Called once with the user code + verification URI. Defaults to stderr. */
45
+ onUserCode?: (info: {
46
+ userCode: string;
47
+ verificationUri: string;
48
+ verificationUriComplete: string;
49
+ }) => void;
50
+ }
51
+ export declare class DeviceGrantError extends Error {
52
+ readonly code: 'access_denied' | 'expired_token' | 'network_error' | 'unknown';
53
+ constructor(code: 'access_denied' | 'expired_token' | 'network_error' | 'unknown', message: string);
54
+ }
55
+ /**
56
+ * Issue a fresh device code + user code pair.
57
+ */
58
+ export declare function requestDeviceCode(opts: Pick<DeviceGrantFlowOptions, 'baseUrl' | 'clientId' | 'fetchImpl'>): Promise<DeviceCodeResponse>;
59
+ /**
60
+ * Internal: one polling round-trip. Returns the token bundle on
61
+ * success, or a sentinel describing why the server isn't ready yet.
62
+ */
63
+ export declare function pollOnce(opts: Pick<DeviceGrantFlowOptions, 'baseUrl' | 'clientId' | 'fetchImpl'>, deviceCode: string): Promise<{
64
+ kind: 'success';
65
+ bundle: DeviceTokenSuccess;
66
+ } | {
67
+ kind: 'pending';
68
+ } | {
69
+ kind: 'slow_down';
70
+ }>;
71
+ /**
72
+ * Full flow — request code, print to stderr, poll until success or
73
+ * fatal error. Returns the access-token bundle on success.
74
+ *
75
+ * `interval` (seconds, server-supplied) becomes the poll cadence.
76
+ * `slow_down` responses bump the interval by RFC's recommended +5s.
77
+ * Total wall time is bounded by the server-supplied `expiresIn`.
78
+ */
79
+ export declare function runDeviceGrantFlow(opts: DeviceGrantFlowOptions): Promise<DeviceTokenSuccess>;
80
+ //# sourceMappingURL=device-grant-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-grant-client.d.ts","sourceRoot":"","sources":["../../src/auth/device-grant-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,uBAAuB,EAAE,MAAM,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,6EAA6E;IAC7E,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;QACxB,uBAAuB,EAAE,MAAM,CAAC;KACjC,KAAK,IAAI,CAAC;CACZ;AAED,qBAAa,gBAAiB,SAAQ,KAAK;aAEvB,IAAI,EAChB,eAAe,GACf,eAAe,GACf,eAAe,GACf,SAAS;gBAJG,IAAI,EAChB,eAAe,GACf,eAAe,GACf,eAAe,GACf,SAAS,EACb,OAAO,EAAE,MAAM;CAKlB;AAkBD;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC,GACvE,OAAO,CAAC,kBAAkB,CAAC,CAuB7B;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC,EACxE,UAAU,EAAE,MAAM,GACjB,OAAO,CACN;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CACxB,CAsDA;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAgC7B"}