@cr8rcho/alkahest 0.1.76 → 0.1.77
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.
- package/dist/core/mapFetch.d.ts +14 -0
- package/dist/core/mapFetch.js +24 -0
- package/dist/core/mapFetch.js.map +1 -0
- package/dist/mcp/server.d.ts +15 -1
- package/dist/mcp/server.js +257 -222
- package/dist/mcp/server.js.map +1 -1
- package/package.json +8 -1
package/dist/mcp/server.js
CHANGED
|
@@ -14,46 +14,66 @@ import { listProjects } from "../core/listProjects.js";
|
|
|
14
14
|
import { listHistory } from "../core/history.js";
|
|
15
15
|
import { findProjectRoot } from "../core/project.js";
|
|
16
16
|
import { checkForUpdate, cachedUpdateStatus } from "../core/version.js";
|
|
17
|
+
import { fetchPublishedMap } from "../core/mapFetch.js";
|
|
18
|
+
// Re-exported so the hosted repo's /api/mcp route needs no direct SDK dependency (ADR-095, hosted repo).
|
|
19
|
+
export { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
17
20
|
const require = createRequire(import.meta.url);
|
|
18
21
|
const pkg = require("../../package.json");
|
|
19
|
-
|
|
20
|
-
* MCP server that lets agents (Claude Code/Codex/Cursor) query the product map (ALKAHEST.md §7).
|
|
21
|
-
* No LLM key required — reasoning is done by the calling agent. Tools provide only deterministic structure.
|
|
22
|
-
* Default target is the server's working directory (cwd). Each tool's `path` can point to a different project.
|
|
23
|
-
*/
|
|
24
|
-
export function buildServer() {
|
|
22
|
+
export function buildServer(remote) {
|
|
25
23
|
const server = new McpServer({ name: "alkahest", version: pkg.version });
|
|
26
24
|
const rootOf = (path) => resolve(path ?? process.cwd());
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
25
|
+
/** Thread the connector's token/api into a core call's params (no-op for local stdio). */
|
|
26
|
+
const withAuth = (params) => remote ? { ...params, token: remote.token, api: remote.api } : params;
|
|
27
|
+
/** The product map: the local checkout's map.json, or (remote) the published bytes. */
|
|
28
|
+
const getMap = async (path, project, mapSlug) => remote ? fetchPublishedMap({ api: remote.api, token: remote.token, project, mapSlug }) : loadOrScan(rootOf(path));
|
|
29
|
+
const noMapMsg = remote
|
|
30
|
+
? "No published code map. Pass `project` (a slug from list_projects), and `map` when the project has several code maps."
|
|
31
|
+
: "No screens, or unsupported project.";
|
|
32
|
+
/** Remote connectors have no env/config to edit — steer those hints to the connector URL + `project` arg. */
|
|
33
|
+
const hinted = (local) => remote
|
|
34
|
+
? {
|
|
35
|
+
...local,
|
|
36
|
+
no_token: "The connector URL is missing its token — recreate it at alkahest.app → API tokens.",
|
|
37
|
+
no_api: "The connector's API base is misconfigured.",
|
|
38
|
+
no_slug: "Pass `project` — a slug from list_projects.",
|
|
39
|
+
invalid_token: "The token in the connector URL is invalid or revoked — create a new one at alkahest.app → API tokens and update the connector.",
|
|
40
|
+
}
|
|
41
|
+
: local;
|
|
42
|
+
if (!remote)
|
|
43
|
+
server.registerTool("scan", {
|
|
44
|
+
title: "Scan project",
|
|
45
|
+
description: "Statically analyze a React/Next project to create/update a product map (.alkahest/map.json). " +
|
|
46
|
+
"Extracts screens, transitions between screens, and the API/data calls each screen makes. Returns a result summary (counts). " +
|
|
47
|
+
"The map is viewed on the hosted viewer — run the publish tool for a shareable link.",
|
|
48
|
+
inputSchema: { path: z.string().optional().describe("Project root (default: cwd)") },
|
|
49
|
+
}, async ({ path }) => {
|
|
50
|
+
const result = runScan(rootOf(path));
|
|
51
|
+
if (!result)
|
|
52
|
+
return text("No screens found. Only Next app-router (page.* under app/ or src/app/) is supported.");
|
|
53
|
+
const m = result.map;
|
|
54
|
+
return json({
|
|
55
|
+
framework: m.meta.framework,
|
|
56
|
+
router: m.meta.router,
|
|
57
|
+
screens: m.screens.length,
|
|
58
|
+
resources: m.resources.length,
|
|
59
|
+
transitions: m.transitions.length,
|
|
60
|
+
calls: m.calls.length,
|
|
61
|
+
mapPath: result.outFile,
|
|
62
|
+
});
|
|
46
63
|
});
|
|
47
|
-
});
|
|
48
64
|
server.registerTool("overview", {
|
|
49
65
|
title: "Product map overview",
|
|
50
66
|
description: "Full product map overview: list of screens (route/title/feature count) and list of resources (label/number of calling screens). " +
|
|
51
67
|
"Auto-scans if map.json is missing. Call this first to grasp the product structure at a glance.",
|
|
52
|
-
inputSchema: {
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
inputSchema: {
|
|
69
|
+
path: z.string().optional(),
|
|
70
|
+
project: z.string().optional().describe("Which project (slug) — remote connectors read the PUBLISHED code map (slugs from list_projects). Ignored locally."),
|
|
71
|
+
map: z.string().optional().describe("Which code map when the project has several (remote connectors only; default: the oldest)"),
|
|
72
|
+
},
|
|
73
|
+
}, async ({ path, project, map: mapSlug }) => {
|
|
74
|
+
const map = await getMap(path, project, mapSlug);
|
|
55
75
|
if (!map)
|
|
56
|
-
return text(
|
|
76
|
+
return text(noMapMsg);
|
|
57
77
|
return json({
|
|
58
78
|
framework: map.meta.framework,
|
|
59
79
|
router: map.meta.router,
|
|
@@ -76,11 +96,16 @@ export function buildServer() {
|
|
|
76
96
|
title: "Screen detail",
|
|
77
97
|
description: "Full structure of one screen: UI features, outgoing/incoming transitions, called resources (API/data), components, and source location. " +
|
|
78
98
|
"The agent can use this data to write a summary or PRD itself. Specify the screen by id/route/title.",
|
|
79
|
-
inputSchema: {
|
|
80
|
-
|
|
81
|
-
|
|
99
|
+
inputSchema: {
|
|
100
|
+
screen: z.string().describe("screen id / route / title"),
|
|
101
|
+
path: z.string().optional(),
|
|
102
|
+
project: z.string().optional().describe("Which project (slug) — remote connectors read the PUBLISHED code map (slugs from list_projects). Ignored locally."),
|
|
103
|
+
map: z.string().optional().describe("Which code map when the project has several (remote connectors only; default: the oldest)"),
|
|
104
|
+
},
|
|
105
|
+
}, async ({ screen, path, project, map: mapSlug }) => {
|
|
106
|
+
const map = await getMap(path, project, mapSlug);
|
|
82
107
|
if (!map)
|
|
83
|
-
return text(
|
|
108
|
+
return text(noMapMsg);
|
|
84
109
|
const s = matchScreen(map, screen);
|
|
85
110
|
if (!s)
|
|
86
111
|
return text(`Screen not found: ${screen}`);
|
|
@@ -90,11 +115,16 @@ export function buildServer() {
|
|
|
90
115
|
title: "Resource callers (impact)",
|
|
91
116
|
description: "Returns the screens that call a specific resource (API endpoint/data). For understanding data dependencies and change impact. " +
|
|
92
117
|
"Specify the resource by id ('GET /api/orders') or a path fragment ('/api/orders').",
|
|
93
|
-
inputSchema: {
|
|
94
|
-
|
|
95
|
-
|
|
118
|
+
inputSchema: {
|
|
119
|
+
resource: z.string(),
|
|
120
|
+
path: z.string().optional(),
|
|
121
|
+
project: z.string().optional().describe("Which project (slug) — remote connectors read the PUBLISHED code map (slugs from list_projects). Ignored locally."),
|
|
122
|
+
map: z.string().optional().describe("Which code map when the project has several (remote connectors only; default: the oldest)"),
|
|
123
|
+
},
|
|
124
|
+
}, async ({ resource, path, project, map: mapSlug }) => {
|
|
125
|
+
const map = await getMap(path, project, mapSlug);
|
|
96
126
|
if (!map)
|
|
97
|
-
return text(
|
|
127
|
+
return text(noMapMsg);
|
|
98
128
|
const q = resource.toLowerCase();
|
|
99
129
|
const matched = map.resources.filter((r) => r.id.toLowerCase() === q || (r.path ?? "").toLowerCase().includes(q) || r.label.toLowerCase().includes(q));
|
|
100
130
|
return json(matched.map((r) => ({
|
|
@@ -105,108 +135,112 @@ export function buildServer() {
|
|
|
105
135
|
})));
|
|
106
136
|
});
|
|
107
137
|
// ---- write-back tools: the agent saves its prose into map.json; publish shows it on the hosted viewer ----
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
if (!remote)
|
|
139
|
+
server.registerTool("set_summary", {
|
|
140
|
+
title: "Set screen summary",
|
|
141
|
+
description: "Save a one-line, PM-friendly summary ('what the user does here') onto a screen in map.json — it appears " +
|
|
142
|
+
"in the screen's panel on the hosted viewer after the next publish. Write the summary yourself from get_screen data.",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
screen: z.string().describe("screen id / route / title"),
|
|
145
|
+
summary: z.string().describe("a 1-2 sentence summary in the user's language"),
|
|
146
|
+
path: z.string().optional(),
|
|
147
|
+
},
|
|
148
|
+
}, async ({ screen, summary, path }) => writeField(rootOf(path), screen, (s) => { s.summary = summary; }));
|
|
149
|
+
if (!remote)
|
|
150
|
+
server.registerTool("set_prd", {
|
|
151
|
+
title: "Set screen PRD",
|
|
152
|
+
description: "Save a PRD/requirements markdown onto a screen in map.json — it appears in the screen's panel on the " +
|
|
153
|
+
"hosted viewer (rendered) after the next publish. Write the PRD yourself from get_screen / who_calls data.",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
screen: z.string().describe("screen id / route / title"),
|
|
156
|
+
prd: z.string().describe("PRD/requirements as markdown"),
|
|
157
|
+
path: z.string().optional(),
|
|
158
|
+
},
|
|
159
|
+
}, async ({ screen, prd, path }) => writeField(rootOf(path), screen, (s) => { s.prd = prd; }));
|
|
128
160
|
// ---- publish: upload the map to the hosted viewer for a shareable link ----
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
161
|
+
if (!remote)
|
|
162
|
+
server.registerTool("publish", {
|
|
163
|
+
title: "Publish to hosted viewer",
|
|
164
|
+
description: "Upload this project's product map (.alkahest/map.json) to the hosted viewer (alkahest.app) and return a " +
|
|
165
|
+
"shareable link anyone can open — no install, no login to view. Only map.json is uploaded; source code never " +
|
|
166
|
+
"leaves the machine. Run 'scan' first if the map is missing. Auth uses an API token from the ALKAHEST_TOKEN " +
|
|
167
|
+
"env var (set it in this server's MCP config) or a prior 'alkahest login'.",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
170
|
+
name: z.string().optional().describe("Project name for the link (first publish only; defaults to folder name)"),
|
|
171
|
+
slug: z.string().optional().describe("Update an existing project by slug (else resolved from the checkout/creds)"),
|
|
172
|
+
map: z.string().optional().describe("Which code map to publish to (a project can hold several; omit when there's one). Passing a new slug creates that code map. List them with the maps tool."),
|
|
173
|
+
},
|
|
174
|
+
}, async ({ path, name, slug, map }) => {
|
|
175
|
+
const res = await publishMap(rootOf(path), { name, slug, mapSlug: map, source: "mcp" });
|
|
176
|
+
if (!res.ok) {
|
|
177
|
+
const hints = {
|
|
178
|
+
no_map: "Run the scan tool first to build .alkahest/map.json.",
|
|
179
|
+
no_token: "Set ALKAHEST_TOKEN in this MCP server's config (get a token at alkahest.app → Account).",
|
|
180
|
+
no_api: "Set ALKAHEST_API_URL in this MCP server's config.",
|
|
181
|
+
plan_limit: "Free plan project limit reached — upgrade to Pro for more.",
|
|
182
|
+
invalid_token: "The API token is invalid or revoked — create a new one at alkahest.app → Account.",
|
|
183
|
+
client_too_old: "This alkahest is too old to publish — run 'alkahest update'.",
|
|
184
|
+
no_workspace: "This account has no workspace yet, and a first publish needs one to hold the new project. Ask the user to create one at alkahest.app/home, then call publish again — there is nothing to retry until they do.",
|
|
185
|
+
ambiguous_map: "List the project's code maps with the maps tool, then call publish again with `map` set to one of them (or a new slug to create one).",
|
|
186
|
+
ambiguous_project: "This checkout has no linked project and an existing one looks like it — do NOT create a duplicate. Pick a candidate's slug below (or use list_projects) and call publish again with `slug` set, or pass a deliberately new `name` to create a fresh project.",
|
|
187
|
+
};
|
|
188
|
+
const hint = hints[res.code ?? ""] ? ` ${hints[res.code ?? ""]}` : "";
|
|
189
|
+
// Carry the structured map list (the edge function returns it) so the agent can pick without re-listing.
|
|
190
|
+
const maps = res.maps?.length ? ` Maps: ${JSON.stringify(res.maps)}` : "";
|
|
191
|
+
// Carry slug-less-publish candidates so the agent can re-publish with the right slug.
|
|
192
|
+
const cands = res.candidates?.length
|
|
193
|
+
? ` Candidates: ${JSON.stringify(res.candidates.map((c) => ({ slug: c.slug, name: c.projectName, workspace: c.workspace, map: c.mapSlug })))}`
|
|
194
|
+
: "";
|
|
195
|
+
return text(`Publish failed (${res.code}): ${res.message}.${hint}${maps}${cands}`);
|
|
196
|
+
}
|
|
197
|
+
const v = await cachedUpdateStatus().catch(() => null);
|
|
198
|
+
// Needs tail (cloud ADR-032): the server counts what's waiting on the token's user in this
|
|
199
|
+
// project (unresolved decision questions + assigned issues). Surface it with an explicit
|
|
200
|
+
// relay hint so agent-driven users hear about blocked decisions in their terminal.
|
|
201
|
+
const waiting = (res.needs?.decisions ?? 0) + (res.needs?.assigned ?? 0);
|
|
202
|
+
return json({
|
|
203
|
+
ok: true,
|
|
204
|
+
slug: res.slug,
|
|
205
|
+
url: res.viewerUrl ?? res.mapUrl,
|
|
206
|
+
created: res.created,
|
|
207
|
+
...(res.needs && waiting > 0
|
|
208
|
+
? {
|
|
209
|
+
needs: res.needs,
|
|
210
|
+
needsHint: `Tell the user: ${[
|
|
211
|
+
res.needs.decisions > 0 ? `${res.needs.decisions} decision${res.needs.decisions === 1 ? "" : "s"}` : null,
|
|
212
|
+
res.needs.assigned > 0 ? `${res.needs.assigned} assigned issue${res.needs.assigned === 1 ? "" : "s"}` : null,
|
|
213
|
+
].filter(Boolean).join(" and ")} waiting on them${res.needs.url ? ` at ${res.needs.url}` : ""}.`,
|
|
214
|
+
}
|
|
215
|
+
: {}),
|
|
216
|
+
...(v?.behind
|
|
217
|
+
? { updateAvailable: `${v.current} → ${v.latest}`, updateHint: "Tell the user to run: alkahest update" }
|
|
218
|
+
: {}),
|
|
219
|
+
});
|
|
186
220
|
});
|
|
187
|
-
});
|
|
188
221
|
// ---- check_version: let the agent tell the user whether to update ----
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
"
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
222
|
+
if (!remote)
|
|
223
|
+
server.registerTool("check_version", {
|
|
224
|
+
title: "Check for alkahest updates",
|
|
225
|
+
description: "Report the installed alkahest version vs the latest published on npm, so you can tell the user whether their " +
|
|
226
|
+
"alkahest is current. If behind, tell them to run 'alkahest update' — you can't update through MCP (the CLI " +
|
|
227
|
+
"updates itself and this MCP server must be restarted to pick it up). No project access; just a version check.",
|
|
228
|
+
inputSchema: {},
|
|
229
|
+
}, async () => {
|
|
230
|
+
const s = await checkForUpdate();
|
|
231
|
+
return json({
|
|
232
|
+
current: s.current,
|
|
233
|
+
latest: s.latest,
|
|
234
|
+
behind: s.behind,
|
|
235
|
+
action: s.behind
|
|
236
|
+
? "Out of date — tell the user to run: alkahest update (then restart this MCP server)."
|
|
237
|
+
: s.latest
|
|
238
|
+
? "Up to date."
|
|
239
|
+
: s.reachable
|
|
240
|
+
? "Nothing published on npm to compare against."
|
|
241
|
+
: "Couldn't reach the npm registry (offline/proxy?) — version status unknown.",
|
|
242
|
+
});
|
|
208
243
|
});
|
|
209
|
-
});
|
|
210
244
|
// ---- comments: read map comments and act on them in-editor ----
|
|
211
245
|
server.registerTool("comments", {
|
|
212
246
|
title: "Map comments",
|
|
@@ -222,19 +256,19 @@ export function buildServer() {
|
|
|
222
256
|
},
|
|
223
257
|
}, async ({ path, open, project }) => {
|
|
224
258
|
const root = rootOf(path);
|
|
225
|
-
const res = await pullComments(root, { open, slug: project });
|
|
259
|
+
const res = await pullComments(root, withAuth({ open, slug: project }));
|
|
226
260
|
if (!res.ok) {
|
|
227
|
-
const hints = {
|
|
261
|
+
const hints = hinted({
|
|
228
262
|
no_token: "Set ALKAHEST_TOKEN in this MCP server's config (token from alkahest.app → Account).",
|
|
229
263
|
no_api: "Set ALKAHEST_API_URL in this MCP server's config.",
|
|
230
264
|
no_slug: "Pass `project` — a slug from list_projects — or set ALKAHEST_PROJECT in this MCP server's config.",
|
|
231
265
|
invalid_token: "The API token is invalid or revoked — create a new one at alkahest.app → Account.",
|
|
232
266
|
not_found: "No accessible project for this slug.",
|
|
233
|
-
};
|
|
267
|
+
});
|
|
234
268
|
const hint = hints[res.code ?? ""] ? ` ${hints[res.code ?? ""]}` : "";
|
|
235
269
|
return text(`Couldn't read comments (${res.code}): ${res.message}.${hint}`);
|
|
236
270
|
}
|
|
237
|
-
const map = loadMap(res.root ?? root);
|
|
271
|
+
const map = remote ? await getMap(undefined, res.slug) : loadMap(res.root ?? root);
|
|
238
272
|
const comments = map ? enrichComments(res.comments ?? [], map) : (res.comments ?? []);
|
|
239
273
|
return json({ ok: true, slug: res.slug, count: comments.length, comments });
|
|
240
274
|
});
|
|
@@ -248,13 +282,13 @@ export function buildServer() {
|
|
|
248
282
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
249
283
|
},
|
|
250
284
|
}, async ({ id, resolved, path }) => {
|
|
251
|
-
const res = await resolveComment(rootOf(path), id, resolved === undefined ? true : resolved);
|
|
285
|
+
const res = await resolveComment(rootOf(path), id, resolved === undefined ? true : resolved, withAuth({}));
|
|
252
286
|
if (!res.ok) {
|
|
253
|
-
const hints = {
|
|
287
|
+
const hints = hinted({
|
|
254
288
|
no_token: "Set ALKAHEST_TOKEN in this MCP server's config.",
|
|
255
289
|
forbidden: "Only the comment author or project owner can resolve it.",
|
|
256
290
|
not_found: "No comment with that id.",
|
|
257
|
-
};
|
|
291
|
+
});
|
|
258
292
|
const hint = hints[res.code ?? ""] ? ` ${hints[res.code ?? ""]}` : "";
|
|
259
293
|
return text(`Resolve failed (${res.code}): ${res.message}.${hint}`);
|
|
260
294
|
}
|
|
@@ -273,19 +307,19 @@ export function buildServer() {
|
|
|
273
307
|
},
|
|
274
308
|
}, async ({ node, body, path, project }) => {
|
|
275
309
|
const root = findProjectRoot(rootOf(path));
|
|
276
|
-
const map = loadOrScan(root);
|
|
310
|
+
const map = remote ? await getMap(undefined, project) : loadOrScan(root);
|
|
277
311
|
if (!map)
|
|
278
|
-
return text("No map for this project — run the scan/publish tools first.");
|
|
312
|
+
return text(remote ? noMapMsg : "No map for this project — run the scan/publish tools first.");
|
|
279
313
|
const n = resolveNode(map, node);
|
|
280
314
|
if (!n)
|
|
281
315
|
return text(`No node matches '${node}'. Use the overview tool to list screens/resources.`);
|
|
282
|
-
const res = await postComment(root, { node_key: n.node_key, anchor_kind: n.anchor_kind, anchor_label: n.anchor_label, body, slug: project });
|
|
316
|
+
const res = await postComment(root, withAuth({ node_key: n.node_key, anchor_kind: n.anchor_kind, anchor_label: n.anchor_label, body, slug: project }));
|
|
283
317
|
if (!res.ok) {
|
|
284
|
-
const hints = {
|
|
318
|
+
const hints = hinted({
|
|
285
319
|
no_token: "Set ALKAHEST_TOKEN in this MCP server's config.",
|
|
286
320
|
no_slug: "Pass `project` — a slug from list_projects — or set ALKAHEST_PROJECT in this MCP server's config.",
|
|
287
321
|
forbidden: "Only the project owner or a collaborator can comment.",
|
|
288
|
-
};
|
|
322
|
+
});
|
|
289
323
|
return text(`Add comment failed (${res.code}): ${res.message}.${hints[res.code ?? ""] ? " " + hints[res.code ?? ""] : ""}`);
|
|
290
324
|
}
|
|
291
325
|
return json({ ok: true, id: res.comment?.id, node_key: n.node_key, anchor_label: n.anchor_label });
|
|
@@ -300,49 +334,50 @@ export function buildServer() {
|
|
|
300
334
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
301
335
|
},
|
|
302
336
|
}, async ({ id, body, path }) => {
|
|
303
|
-
const res = await postComment(rootOf(path), { parent_id: id, body });
|
|
337
|
+
const res = await postComment(rootOf(path), withAuth({ parent_id: id, body }));
|
|
304
338
|
if (!res.ok) {
|
|
305
|
-
const hints = {
|
|
339
|
+
const hints = hinted({
|
|
306
340
|
no_token: "Set ALKAHEST_TOKEN in this MCP server's config.",
|
|
307
341
|
not_found: "No comment with that id (parent).",
|
|
308
342
|
forbidden: "Only the project owner or a collaborator can comment.",
|
|
309
|
-
};
|
|
343
|
+
});
|
|
310
344
|
return text(`Reply failed (${res.code}): ${res.message}.${hints[res.code ?? ""] ? " " + hints[res.code ?? ""] : ""}`);
|
|
311
345
|
}
|
|
312
346
|
return json({ ok: true, id: res.comment?.id, parent_id: id });
|
|
313
347
|
});
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
"
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
348
|
+
if (!remote)
|
|
349
|
+
server.registerTool("comment_to_issue", {
|
|
350
|
+
title: "File map comments as a GitHub issue",
|
|
351
|
+
description: "Group one or more map comments (ids from the comments tool) into a SINGLE GitHub issue and link it back onto each. " +
|
|
352
|
+
"Creates the issue with the local `gh` CLI (must be installed and authenticated; it runs in the project's git repo), " +
|
|
353
|
+
"then records the issue URL on the comments so the hosted viewer shows a 'tracked' badge. Use this to turn feedback " +
|
|
354
|
+
"into tracked work. Needs an API token; owner or collaborator only. Pass force:true to re-file comments that are " +
|
|
355
|
+
"already linked to an issue (creates a new one).",
|
|
356
|
+
inputSchema: {
|
|
357
|
+
ids: z.array(z.string()).min(1).describe("Comment ids to group into one issue (from the comments tool)"),
|
|
358
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
359
|
+
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
360
|
+
title: z.string().optional().describe("Issue title (else derived from the comments)"),
|
|
361
|
+
repo: z.string().optional().describe("Target GitHub repo owner/repo (else gh's default for the repo)"),
|
|
362
|
+
force: z.boolean().optional().describe("File even if some selected comments are already tracked"),
|
|
363
|
+
},
|
|
364
|
+
}, async ({ ids, path, title, repo, force, project }) => {
|
|
365
|
+
const res = await fileCommentsIssue(rootOf(path), ids, { title, repo, force, slug: project });
|
|
366
|
+
if (!res.ok) {
|
|
367
|
+
const hints = {
|
|
368
|
+
no_token: "Set ALKAHEST_TOKEN in this MCP server's config.",
|
|
369
|
+
no_slug: "Pass `project` — a slug from list_projects — or set ALKAHEST_PROJECT in this MCP server's config.",
|
|
370
|
+
already_tracked: "Some comments already have an issue — pass force:true to file a new one.",
|
|
371
|
+
gh_failed: "Install and authenticate the GitHub CLI (`gh auth login`) for this repo.",
|
|
372
|
+
forbidden: "Only the project owner or a collaborator can file issues.",
|
|
373
|
+
not_found: "One or more ids don't exist — list them with the comments tool.",
|
|
374
|
+
};
|
|
375
|
+
return text(`File issue failed (${res.code}): ${res.message}.${hints[res.code ?? ""] ? " " + hints[res.code ?? ""] : ""}`);
|
|
376
|
+
}
|
|
377
|
+
return json({ ok: true, issue_url: res.issue_url, ids: res.ids, title: res.title });
|
|
378
|
+
});
|
|
344
379
|
// ---- issues: the Issue Map — a map-shaped issue tracker on the hosted viewer ----
|
|
345
|
-
const issueHints = {
|
|
380
|
+
const issueHints = hinted({
|
|
346
381
|
no_token: "Set ALKAHEST_TOKEN in this MCP server's config (token from alkahest.app → Account).",
|
|
347
382
|
no_api: "Set ALKAHEST_API_URL in this MCP server's config.",
|
|
348
383
|
no_slug: "Pass `project` — a slug from list_projects — or set ALKAHEST_PROJECT in this MCP server's config.",
|
|
@@ -350,7 +385,7 @@ export function buildServer() {
|
|
|
350
385
|
forbidden: "Only the project owner or a collaborator can write issues.",
|
|
351
386
|
not_found: "Not found — list ids with the issues tool, or the project's issue maps with the maps tool.",
|
|
352
387
|
ambiguous_map: "List the project's issue maps with the maps tool, then retry with `map` set to one (or create one with create_map).",
|
|
353
|
-
};
|
|
388
|
+
});
|
|
354
389
|
// `maps` (present on ambiguous_map / unknown-slug) is appended as JSON so the agent can pick a map
|
|
355
390
|
// without a second round-trip to the maps tool.
|
|
356
391
|
const issueFail = (what, code, message, maps) => text(`${what} failed (${code}): ${message}.${issueHints[code ?? ""] ? " " + issueHints[code ?? ""] : ""}${maps?.length ? ` Maps: ${JSON.stringify(maps)}` : ""}`);
|
|
@@ -372,7 +407,7 @@ export function buildServer() {
|
|
|
372
407
|
q: z.string().optional().describe("Filter issues by title/body substring (server-side). Edges/links stay unfiltered, so blockedBy may name issues outside the filtered list."),
|
|
373
408
|
},
|
|
374
409
|
}, async ({ path, open, map, q, project }) => {
|
|
375
|
-
const res = await pullIssues(rootOf(path), { mapSlug: map, q, slug: project });
|
|
410
|
+
const res = await pullIssues(rootOf(path), withAuth({ mapSlug: map, q, slug: project }));
|
|
376
411
|
if (!res.ok || !res.graph)
|
|
377
412
|
return issueFail("Read issues", res.code, res.message, res.maps);
|
|
378
413
|
const states = deriveIssueStates(res.graph);
|
|
@@ -422,7 +457,7 @@ export function buildServer() {
|
|
|
422
457
|
target_key: target,
|
|
423
458
|
}
|
|
424
459
|
: {};
|
|
425
|
-
const res = await createIssue(rootOf(path), { title, type, status, body, priority, due_on, assignee_id, props, parent_id, mapSlug: map, slug: project, ...targetFields });
|
|
460
|
+
const res = await createIssue(rootOf(path), withAuth({ title, type, status, body, priority, due_on, assignee_id, props, parent_id, mapSlug: map, slug: project, ...targetFields }));
|
|
426
461
|
if (!res.ok || !res.issue)
|
|
427
462
|
return issueFail("Add issue", res.code, res.message, res.maps);
|
|
428
463
|
return json({ ok: true, issue: res.issue });
|
|
@@ -463,7 +498,7 @@ export function buildServer() {
|
|
|
463
498
|
path: z.string().optional().describe("Project root (default: cwd — used only to find your token/API)"),
|
|
464
499
|
},
|
|
465
500
|
}, async ({ status, project, q, path }) => {
|
|
466
|
-
const res = await pullTasks(rootOf(path), { status, project, q });
|
|
501
|
+
const res = await pullTasks(rootOf(path), withAuth({ status, project, q }));
|
|
467
502
|
if (!res.ok || !res.tasks)
|
|
468
503
|
return issueFail("List tasks", res.code, res.message);
|
|
469
504
|
return json({ ok: true, count: res.tasks.length, tasks: res.tasks });
|
|
@@ -510,7 +545,7 @@ export function buildServer() {
|
|
|
510
545
|
path: z.string().optional().describe("Project root (default: cwd — a linked checkout auto-tags its project)"),
|
|
511
546
|
},
|
|
512
547
|
}, async ({ title, body, project, workspace, tags, due_on, dedup_key, note_mode, note, skill, path }) => {
|
|
513
|
-
const res = await createTask(rootOf(path), { title, body, slug: project, workspace, tags, due_on, dedup_key, note_mode, note, skill });
|
|
548
|
+
const res = await createTask(rootOf(path), withAuth({ title, body, slug: project, workspace, tags, due_on, dedup_key, note_mode, note, skill }));
|
|
514
549
|
if (!res.ok || !res.task) {
|
|
515
550
|
const wsHint = res.workspaces?.length ? ` Workspaces: ${JSON.stringify(res.workspaces)}` : "";
|
|
516
551
|
return issueFail("Add task", res.code, `${res.message ?? ""}${wsHint}`);
|
|
@@ -531,7 +566,7 @@ export function buildServer() {
|
|
|
531
566
|
path: z.string().optional().describe("Project root (default: cwd — used only to find your token/API)"),
|
|
532
567
|
},
|
|
533
568
|
}, async ({ id, reopen, path }) => {
|
|
534
|
-
const res = await completeTask(rootOf(path), { id, reopen });
|
|
569
|
+
const res = await completeTask(rootOf(path), withAuth({ id, reopen }));
|
|
535
570
|
if (!res.ok || !res.task)
|
|
536
571
|
return issueFail(reopen ? "Reopen task" : "Complete task", res.code, res.message);
|
|
537
572
|
return json({ ok: true, task: res.task });
|
|
@@ -556,7 +591,7 @@ export function buildServer() {
|
|
|
556
591
|
path: z.string().optional().describe("Project root (default: cwd — used only to find your token/API)"),
|
|
557
592
|
},
|
|
558
593
|
}, async ({ id, title, body, due_on, tags, note_mode, note, skill, path }) => {
|
|
559
|
-
const res = await updateTask(rootOf(path), {
|
|
594
|
+
const res = await updateTask(rootOf(path), withAuth({
|
|
560
595
|
id,
|
|
561
596
|
title,
|
|
562
597
|
body: body === "" ? null : body,
|
|
@@ -565,7 +600,7 @@ export function buildServer() {
|
|
|
565
600
|
note_mode: note_mode === "" ? null : note_mode,
|
|
566
601
|
note: note === "" ? null : note,
|
|
567
602
|
skill: skill === "" ? null : skill,
|
|
568
|
-
});
|
|
603
|
+
}));
|
|
569
604
|
if (!res.ok || !res.task)
|
|
570
605
|
return issueFail("Update task", res.code, res.message);
|
|
571
606
|
return json({ ok: true, task: res.task });
|
|
@@ -586,7 +621,7 @@ export function buildServer() {
|
|
|
586
621
|
path: z.string().optional().describe("Project root (default: cwd — used only to find your token/API)"),
|
|
587
622
|
},
|
|
588
623
|
}, async ({ path }) => {
|
|
589
|
-
const res = await pullSkills(rootOf(path));
|
|
624
|
+
const res = await pullSkills(rootOf(path), withAuth({}));
|
|
590
625
|
if (!res.ok || !res.skills)
|
|
591
626
|
return issueFail("List skills", res.code, res.message);
|
|
592
627
|
return json({ ok: true, count: res.skills.length, skills: res.skills });
|
|
@@ -613,7 +648,7 @@ export function buildServer() {
|
|
|
613
648
|
path: z.string().optional().describe("Project root (default: cwd — used only to find your token/API)"),
|
|
614
649
|
},
|
|
615
650
|
}, async ({ name, body, default_for, rename_from, path }) => {
|
|
616
|
-
const res = await saveSkill(rootOf(path), { name, body, default_for, rename_from });
|
|
651
|
+
const res = await saveSkill(rootOf(path), withAuth({ name, body, default_for, rename_from }));
|
|
617
652
|
if (!res.ok || !res.skill)
|
|
618
653
|
return issueFail("Add skill", res.code, res.message);
|
|
619
654
|
return json({ ok: true, skill: res.skill });
|
|
@@ -634,7 +669,7 @@ export function buildServer() {
|
|
|
634
669
|
path: z.string().optional().describe("Project root (default: cwd — used only to find your token/API)"),
|
|
635
670
|
},
|
|
636
671
|
}, async ({ task, open, path }) => {
|
|
637
|
-
const res = await pullTaskComments(rootOf(path), { task, open });
|
|
672
|
+
const res = await pullTaskComments(rootOf(path), withAuth({ task, open }));
|
|
638
673
|
if (!res.ok || !res.comments)
|
|
639
674
|
return issueFail("Read task comments", res.code, res.message);
|
|
640
675
|
return json({ ok: true, count: res.comments.length, comments: res.comments });
|
|
@@ -652,7 +687,7 @@ export function buildServer() {
|
|
|
652
687
|
path: z.string().optional().describe("Project root (default: cwd — used only to find your token/API)"),
|
|
653
688
|
},
|
|
654
689
|
}, async ({ task, body, path }) => {
|
|
655
|
-
const res = await postTaskComment(rootOf(path), { task_id: task, body, kind: "question" });
|
|
690
|
+
const res = await postTaskComment(rootOf(path), withAuth({ task_id: task, body, kind: "question" }));
|
|
656
691
|
if (!res.ok || !res.comment)
|
|
657
692
|
return issueFail("Ask task", res.code, res.message);
|
|
658
693
|
return json({ ok: true, comment: res.comment, note: "Question posted — re-check with task_comments, then resolve_task_comment once answered." });
|
|
@@ -673,7 +708,7 @@ export function buildServer() {
|
|
|
673
708
|
path: z.string().optional().describe("Project root (default: cwd — used only to find your token/API)"),
|
|
674
709
|
},
|
|
675
710
|
}, async ({ task, parent, body, kind, path }) => {
|
|
676
|
-
const res = await postTaskComment(rootOf(path), { task_id: task, parent, body, kind });
|
|
711
|
+
const res = await postTaskComment(rootOf(path), withAuth({ task_id: task, parent, body, kind }));
|
|
677
712
|
if (!res.ok || !res.comment)
|
|
678
713
|
return issueFail("Comment on task", res.code, res.message);
|
|
679
714
|
return json({ ok: true, comment: res.comment });
|
|
@@ -690,7 +725,7 @@ export function buildServer() {
|
|
|
690
725
|
path: z.string().optional().describe("Project root (default: cwd — used only to find your token/API)"),
|
|
691
726
|
},
|
|
692
727
|
}, async ({ id, resolved, path }) => {
|
|
693
|
-
const res = await resolveTaskComment(rootOf(path), { id, resolved });
|
|
728
|
+
const res = await resolveTaskComment(rootOf(path), withAuth({ id, resolved }));
|
|
694
729
|
if (!res.ok)
|
|
695
730
|
return issueFail("Resolve task comment", res.code, res.message);
|
|
696
731
|
return json({ ok: true, id: res.id, resolved: res.resolved });
|
|
@@ -713,9 +748,9 @@ export function buildServer() {
|
|
|
713
748
|
}, async ({ q, path, project }) => {
|
|
714
749
|
const root = rootOf(path);
|
|
715
750
|
const [notesRes, issuesRes, tasksRes] = await Promise.all([
|
|
716
|
-
pullNotes(root, { q, bodies: "excerpt", slug: project }),
|
|
717
|
-
pullIssues(root, { q, slug: project }),
|
|
718
|
-
pullTasks(root, { q, status: "all", project }),
|
|
751
|
+
pullNotes(root, withAuth({ q, bodies: "excerpt", slug: project })),
|
|
752
|
+
pullIssues(root, withAuth({ q, slug: project })),
|
|
753
|
+
pullTasks(root, withAuth({ q, status: "all", project })),
|
|
719
754
|
]);
|
|
720
755
|
const notes = notesRes.ok && notesRes.maps
|
|
721
756
|
? notesRes.maps.flatMap((m) => m.notes.map((n) => ({ slug: n.slug, title: n.title, map: m.slug, folder: n.folder ?? null, excerpt: n.body ?? null })))
|
|
@@ -750,7 +785,7 @@ export function buildServer() {
|
|
|
750
785
|
full_bodies: z.boolean().optional().describe("Return complete bodies instead of 240-char excerpts (heavy on a big wiki)"),
|
|
751
786
|
},
|
|
752
787
|
}, async ({ path, q, map, full_bodies, project }) => {
|
|
753
|
-
const res = await pullNotes(rootOf(path), { mapSlug: map, q, bodies: full_bodies ? undefined : "excerpt", slug: project });
|
|
788
|
+
const res = await pullNotes(rootOf(path), withAuth({ mapSlug: map, q, bodies: full_bodies ? undefined : "excerpt", slug: project }));
|
|
754
789
|
if (!res.ok || !res.maps)
|
|
755
790
|
return issueFail("Read notes", res.code, res.message, res.mapList);
|
|
756
791
|
return json({ ok: true, project: res.project, count: res.maps.reduce((n, m) => n + m.notes.length, 0), maps: res.maps });
|
|
@@ -766,7 +801,7 @@ export function buildServer() {
|
|
|
766
801
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
767
802
|
},
|
|
768
803
|
}, async ({ note, map, path, project }) => {
|
|
769
|
-
const res = await getNote(rootOf(path), { note, mapSlug: map, slug: project });
|
|
804
|
+
const res = await getNote(rootOf(path), withAuth({ note, mapSlug: map, slug: project }));
|
|
770
805
|
if (!res.ok || !res.note)
|
|
771
806
|
return issueFail("Get note", res.code, res.message, res.mapList);
|
|
772
807
|
const { ok: _ok, code: _code, message: _message, mapList: _ml, ...rest } = res;
|
|
@@ -790,7 +825,7 @@ export function buildServer() {
|
|
|
790
825
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
791
826
|
},
|
|
792
827
|
}, async ({ title, body, note_slug, folder, props, map, path, project }) => {
|
|
793
|
-
const res = await createNote(rootOf(path), { title, body, note_slug, folder, props, mapSlug: map, slug: project });
|
|
828
|
+
const res = await createNote(rootOf(path), withAuth({ title, body, note_slug, folder, props, mapSlug: map, slug: project }));
|
|
794
829
|
if (!res.ok || !res.note)
|
|
795
830
|
return issueFail("Add note", res.code, res.message, res.maps);
|
|
796
831
|
return json({ ok: true, note: res.note });
|
|
@@ -811,7 +846,7 @@ export function buildServer() {
|
|
|
811
846
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
812
847
|
},
|
|
813
848
|
}, async ({ from, to, remove, path, project }) => {
|
|
814
|
-
const res = await linkNotes(rootOf(path), { from, to, remove, slug: project });
|
|
849
|
+
const res = await linkNotes(rootOf(path), withAuth({ from, to, remove, slug: project }));
|
|
815
850
|
if (!res.ok)
|
|
816
851
|
return issueFail(remove ? "Unlink notes" : "Link notes", res.code, res.message, res.maps);
|
|
817
852
|
return json({ ok: true, ...(remove ? { removed: `${from} → ${to}` } : { linked: `${from} → ${to}` }) });
|
|
@@ -829,7 +864,7 @@ export function buildServer() {
|
|
|
829
864
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
830
865
|
},
|
|
831
866
|
}, async ({ note, map, path, project }) => {
|
|
832
|
-
const res = await mapNote(rootOf(path), { noteRef: note, mapSlug: map, slug: project });
|
|
867
|
+
const res = await mapNote(rootOf(path), withAuth({ noteRef: note, mapSlug: map, slug: project }));
|
|
833
868
|
if (!res.ok)
|
|
834
869
|
return issueFail("Move note", res.code, res.message, res.maps);
|
|
835
870
|
return json({ ok: true, note: res.note, map: res.map });
|
|
@@ -859,7 +894,7 @@ export function buildServer() {
|
|
|
859
894
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
860
895
|
},
|
|
861
896
|
}, async ({ note, title, body, new_slug, folder, props, delete: del, reason, restore, map, path, project }) => {
|
|
862
|
-
const res = await updateNote(rootOf(path), { note, title, body, new_slug, folder, props, delete: del, reason, restore, mapSlug: map, slug: project });
|
|
897
|
+
const res = await updateNote(rootOf(path), withAuth({ note, title, body, new_slug, folder, props, delete: del, reason, restore, mapSlug: map, slug: project }));
|
|
863
898
|
const what = del ? "Delete note" : restore ? "Restore note" : "Update note";
|
|
864
899
|
if (!res.ok)
|
|
865
900
|
return issueFail(what, res.code, res.message, res.maps);
|
|
@@ -899,7 +934,7 @@ export function buildServer() {
|
|
|
899
934
|
if (!define?.length && !remove?.length) {
|
|
900
935
|
return json({ ok: false, error: "bad_request", message: "Pass `define` (definitions to register) and/or `remove` (keys to unregister)." });
|
|
901
936
|
}
|
|
902
|
-
const res = await editPropDefs(rootOf(path), { defs: define, remove, mapSlug: map, slug: project });
|
|
937
|
+
const res = await editPropDefs(rootOf(path), withAuth({ defs: define, remove, mapSlug: map, slug: project }));
|
|
903
938
|
if (!res.ok)
|
|
904
939
|
return issueFail("Edit note props", res.code, res.message, res.maps);
|
|
905
940
|
return json({ ok: true, added: res.added ?? 0, merged: res.merged ?? 0, removed: res.removed ?? 0, skipped: res.skipped ?? 0 });
|
|
@@ -916,7 +951,7 @@ export function buildServer() {
|
|
|
916
951
|
type: z.enum(["code", "issue", "note"]).optional().describe("Restrict to one type (default: all)"),
|
|
917
952
|
},
|
|
918
953
|
}, async ({ path, type, project }) => {
|
|
919
|
-
const res = await listMaps(rootOf(path), { type, slug: project });
|
|
954
|
+
const res = await listMaps(rootOf(path), withAuth({ type, slug: project }));
|
|
920
955
|
if (!res.ok || !res.maps)
|
|
921
956
|
return issueFail("List maps", res.code, res.message);
|
|
922
957
|
return json({ ok: true, slug: res.slug, count: res.maps.length, maps: res.maps });
|
|
@@ -932,10 +967,10 @@ export function buildServer() {
|
|
|
932
967
|
"(screens/resources counts) so you can match a local scan by structure. Needs an API token; no project context.",
|
|
933
968
|
inputSchema: {},
|
|
934
969
|
}, async () => {
|
|
935
|
-
const res = await listProjects({});
|
|
970
|
+
const res = await listProjects(withAuth({}));
|
|
936
971
|
if (!res.ok || !res.projects) {
|
|
937
|
-
const
|
|
938
|
-
return text(`List projects failed (${res.code}): ${res.message}.${
|
|
972
|
+
const h = hinted({ no_token: "Set ALKAHEST_TOKEN in this MCP server's config." })[res.code ?? ""];
|
|
973
|
+
return text(`List projects failed (${res.code}): ${res.message}.${h ? ` ${h}` : ""}`);
|
|
939
974
|
}
|
|
940
975
|
return json({
|
|
941
976
|
ok: true,
|
|
@@ -966,7 +1001,7 @@ export function buildServer() {
|
|
|
966
1001
|
limit: z.number().optional().describe("Max versions (default 50)"),
|
|
967
1002
|
},
|
|
968
1003
|
}, async ({ path, map, limit, project }) => {
|
|
969
|
-
const res = await listHistory(rootOf(path), { map, limit, slug: project });
|
|
1004
|
+
const res = await listHistory(rootOf(path), withAuth({ map, limit, slug: project }));
|
|
970
1005
|
if (!res.ok || !res.versions)
|
|
971
1006
|
return issueFail("History", res.code, res.message);
|
|
972
1007
|
// Newest first; include count deltas vs the previous version so the agent needn't recompute.
|
|
@@ -1000,7 +1035,7 @@ export function buildServer() {
|
|
|
1000
1035
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
1001
1036
|
},
|
|
1002
1037
|
}, async ({ slug, type, name, path, project }) => {
|
|
1003
|
-
const res = await createMap(rootOf(path), { mapSlug: slug, type, mapName: name, slug: project });
|
|
1038
|
+
const res = await createMap(rootOf(path), withAuth({ mapSlug: slug, type, mapName: name, slug: project }));
|
|
1004
1039
|
if (!res.ok || !res.map)
|
|
1005
1040
|
return issueFail("Create map", res.code, res.message);
|
|
1006
1041
|
return json({ ok: true, map: res.map });
|
|
@@ -1050,7 +1085,7 @@ export function buildServer() {
|
|
|
1050
1085
|
set.target_key = target;
|
|
1051
1086
|
}
|
|
1052
1087
|
}
|
|
1053
|
-
const res = await updateIssue(rootOf(path), { id, ...(del ? { delete: true } : { set }) });
|
|
1088
|
+
const res = await updateIssue(rootOf(path), withAuth({ id, ...(del ? { delete: true } : { set }) }));
|
|
1054
1089
|
if (!res.ok)
|
|
1055
1090
|
return issueFail("Update issue", res.code, res.message);
|
|
1056
1091
|
return json(res.deleted ? { ok: true, deleted: true, id } : { ok: true, issue: res.issue });
|
|
@@ -1069,7 +1104,7 @@ export function buildServer() {
|
|
|
1069
1104
|
},
|
|
1070
1105
|
}, async ({ from, to, kind, remove, path }) => {
|
|
1071
1106
|
const edge = [{ to, kind: kind ?? "blocks" }];
|
|
1072
|
-
const res = await updateIssue(rootOf(path), { id: from, ...(remove ? { remove_edges: edge } : { add_edges: edge }) });
|
|
1107
|
+
const res = await updateIssue(rootOf(path), withAuth({ id: from, ...(remove ? { remove_edges: edge } : { add_edges: edge }) }));
|
|
1073
1108
|
if (!res.ok)
|
|
1074
1109
|
return issueFail("Link issues", res.code, res.message);
|
|
1075
1110
|
return json({ ok: true, from, to, kind: kind ?? "blocks", removed: Boolean(remove) });
|
|
@@ -1088,7 +1123,7 @@ export function buildServer() {
|
|
|
1088
1123
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
1089
1124
|
},
|
|
1090
1125
|
}, async ({ issue, map, remove, path, project }) => {
|
|
1091
|
-
const res = await mapIssue(rootOf(path), { issueId: issue, mapSlug: map, remove, slug: project });
|
|
1126
|
+
const res = await mapIssue(rootOf(path), withAuth({ issueId: issue, mapSlug: map, remove, slug: project }));
|
|
1092
1127
|
if (!res.ok)
|
|
1093
1128
|
return issueFail(remove ? "Unmap issue" : "Map issue", res.code, res.message, res.maps);
|
|
1094
1129
|
return json({ ok: true, issue: res.issue, map: res.map, member: res.member });
|
|
@@ -1108,7 +1143,7 @@ export function buildServer() {
|
|
|
1108
1143
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
1109
1144
|
},
|
|
1110
1145
|
}, async ({ issue, open, path, project }) => {
|
|
1111
|
-
const res = await pullIssueComments(rootOf(path), { issue, open, slug: project });
|
|
1146
|
+
const res = await pullIssueComments(rootOf(path), withAuth({ issue, open, slug: project }));
|
|
1112
1147
|
if (!res.ok || !res.comments)
|
|
1113
1148
|
return issueFail("Read issue comments", res.code, res.message);
|
|
1114
1149
|
return json({ ok: true, count: res.comments.length, comments: res.comments });
|
|
@@ -1129,7 +1164,7 @@ export function buildServer() {
|
|
|
1129
1164
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
1130
1165
|
},
|
|
1131
1166
|
}, async ({ issue, body, mention, path }) => {
|
|
1132
|
-
const res = await postIssueComment(rootOf(path), { issue_id: issue, body, kind: "question", mention });
|
|
1167
|
+
const res = await postIssueComment(rootOf(path), withAuth({ issue_id: issue, body, kind: "question", mention }));
|
|
1133
1168
|
if (!res.ok || !res.comment)
|
|
1134
1169
|
return issueFail("Ask issue", res.code, res.message);
|
|
1135
1170
|
return json({ ok: true, comment: res.comment, note: "Question posted — the issue is now awaiting the user's decision. Re-check with issue_comments, then resolve_issue_question once answered." });
|
|
@@ -1149,7 +1184,7 @@ export function buildServer() {
|
|
|
1149
1184
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
1150
1185
|
},
|
|
1151
1186
|
}, async ({ issue, parent, body, kind, mention, path }) => {
|
|
1152
|
-
const res = await postIssueComment(rootOf(path), { issue_id: issue, parent, body, kind, mention });
|
|
1187
|
+
const res = await postIssueComment(rootOf(path), withAuth({ issue_id: issue, parent, body, kind, mention }));
|
|
1153
1188
|
if (!res.ok || !res.comment)
|
|
1154
1189
|
return issueFail("Reply on issue", res.code, res.message);
|
|
1155
1190
|
return json({ ok: true, comment: res.comment });
|
|
@@ -1165,7 +1200,7 @@ export function buildServer() {
|
|
|
1165
1200
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
1166
1201
|
},
|
|
1167
1202
|
}, async ({ id, resolved, path }) => {
|
|
1168
|
-
const res = await resolveIssueComment(rootOf(path), { id, resolved });
|
|
1203
|
+
const res = await resolveIssueComment(rootOf(path), withAuth({ id, resolved }));
|
|
1169
1204
|
if (!res.ok)
|
|
1170
1205
|
return issueFail("Resolve question", res.code, res.message);
|
|
1171
1206
|
return json({ ok: true, id: res.id, resolved: res.resolved });
|
|
@@ -1189,7 +1224,7 @@ export function buildServer() {
|
|
|
1189
1224
|
// Resolve the terminal status to move to (explicit, else the project's first terminal status).
|
|
1190
1225
|
let target = status;
|
|
1191
1226
|
if (!target) {
|
|
1192
|
-
const g = await pullIssues(root, { slug: project });
|
|
1227
|
+
const g = await pullIssues(root, withAuth({ slug: project }));
|
|
1193
1228
|
if (!g.ok || !g.graph)
|
|
1194
1229
|
return issueFail("Complete issue", g.code, g.message, g.maps);
|
|
1195
1230
|
const terminal = [...terminalStatuses(g.graph.issue_config)];
|
|
@@ -1197,10 +1232,10 @@ export function buildServer() {
|
|
|
1197
1232
|
return text("Complete issue failed: this project's issue_config has no terminal status. Set one, or use update_issue with an explicit status.");
|
|
1198
1233
|
target = terminal[0];
|
|
1199
1234
|
}
|
|
1200
|
-
const upd = await updateIssue(root, { id, set: { status: target } });
|
|
1235
|
+
const upd = await updateIssue(root, withAuth({ id, set: { status: target } }));
|
|
1201
1236
|
if (!upd.ok)
|
|
1202
1237
|
return issueFail("Complete issue", upd.code, upd.message);
|
|
1203
|
-
const cmt = await postIssueComment(root, { issue_id: id, body: result, kind: "result" });
|
|
1238
|
+
const cmt = await postIssueComment(root, withAuth({ issue_id: id, body: result, kind: "result" }));
|
|
1204
1239
|
if (!cmt.ok)
|
|
1205
1240
|
return issueFail("Complete issue (result note)", cmt.code, cmt.message);
|
|
1206
1241
|
return json({
|