@selfagency/beans-mcp 0.1.4 → 0.4.2

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/README.md CHANGED
@@ -12,6 +12,14 @@ MCP (Model Context Protocol) server for [Beans](https://github.com/hmans/beans)
12
12
  npx @selfagency/beans-mcp /path/to/workspace
13
13
  ```
14
14
 
15
+ ### Versioning
16
+
17
+ `@selfagency/beans-mcp` tracks upstream [Beans](https://github.com/hmans/beans) versions.
18
+ For example, Beans `v0.4.2` maps to `@selfagency/beans-mcp@0.4.2`.
19
+
20
+ At startup, the server compares its own package version against the installed `beans`
21
+ CLI version. If they differ, it prints a warning to stderr and continues startup.
22
+
15
23
  ### Parameters
16
24
 
17
25
  - `--workspace-root` or positional arg: Workspace root path
@@ -23,12 +31,12 @@ npx @selfagency/beans-mcp /path/to/workspace
23
31
  ## Summary of public MCP tools
24
32
 
25
33
  - `beans_init` — Initialize the workspace (optional `prefix`).
26
- - `beans_view` — Fetch full bean details by `beanId`.
34
+ - `beans_view` — Fetch full bean details by `beanId` or `beanIds`.
27
35
  - `beans_create` — Create a new bean (title/type + optional fields).
28
- - `beans_update` — Consolidated metadata updates (status/type/priority/parent/clearParent/blocking/blockedBy).
29
- - `beans_delete` — Delete a bean (`beanId`, optional `force`).
36
+ - `beans_update` — Consolidated metadata + body updates (status/type/priority/parent/clearParent/blocking/blockedBy/body/bodyAppend/bodyReplace) plus optional optimistic concurrency hint (`ifMatch`).
37
+ - `beans_delete` — Delete one or many beans (`beanId` or `beanIds`, optional `force`).
30
38
  - `beans_reopen` — Reopen a completed or scrapped bean to an active status.
31
- - `beans_query` — Unified list/search/filter/sort/llm_context/open_config operations.
39
+ - `beans_query` — Unified list/search/filter/sort/ready/llm_context/open_config operations.
32
40
  - `beans_bean_file` — Read/edit/create/delete files under `.beans`.
33
41
  - `beans_output` — Read extension output logs or show guidance.
34
42
 
@@ -37,6 +45,7 @@ npx @selfagency/beans-mcp /path/to/workspace
37
45
  - The `beans_query` tool is intentionally broad: prefer it for listing, searching, filtering or sorting beans, and for generating Copilot instructions (`operation: 'llm_context'`).
38
46
  - All file and log operations validate paths to keep them within the workspace or the VS Code log directory.
39
47
  - `beans_update` replaces many fine-grained update tools; callers should use it to keep the public tool surface small and predictable.
48
+ - Version mismatches are warning-only and non-blocking by design.
40
49
 
41
50
  ## Examples
42
51
 
@@ -62,6 +71,12 @@ Request:
62
71
  { "beanId": "bean-abc" }
63
72
  ```
64
73
 
74
+ Request (multiple beans):
75
+
76
+ ```json
77
+ { "beanIds": ["bean-abc", "bean-def"] }
78
+ ```
79
+
65
80
  Response (structuredContent):
66
81
 
67
82
  ```json
@@ -114,10 +129,26 @@ Request (change status and add blocking):
114
129
  {
115
130
  "beanId": "bean-abc",
116
131
  "status": "in-progress",
117
- "blocking": ["bean-def"]
132
+ "blocking": ["bean-def"],
133
+ "ifMatch": "etag-value"
118
134
  }
119
135
  ```
120
136
 
137
+ Request (atomic body modifications):
138
+
139
+ ```json
140
+ {
141
+ "beanId": "bean-abc",
142
+ "bodyReplace": [
143
+ { "old": "- [ ] Task 1", "new": "- [x] Task 1" },
144
+ { "old": "- [ ] Task 2", "new": "- [x] Task 2" }
145
+ ],
146
+ "bodyAppend": "## Summary\n\nAll checklist items completed."
147
+ }
148
+ ```
149
+
150
+ > Note: `body` (full replacement) cannot be combined with `bodyAppend` or `bodyReplace` in the same request.
151
+
121
152
  Response (structuredContent):
122
153
 
123
154
  ```json
@@ -144,6 +175,26 @@ Response:
144
175
  { "deleted": true, "beanId": "bean-old" }
145
176
  ```
146
177
 
178
+ Batch request:
179
+
180
+ ```json
181
+ { "beanIds": ["bean-old", "bean-older"], "force": false }
182
+ ```
183
+
184
+ Batch response (summary):
185
+
186
+ ```json
187
+ {
188
+ "requestedCount": 2,
189
+ "deletedCount": 2,
190
+ "failedCount": 0,
191
+ "results": [
192
+ { "beanId": "bean-old", "deleted": true },
193
+ { "beanId": "bean-older", "deleted": true }
194
+ ]
195
+ }
196
+ ```
197
+
147
198
  ### beans_reopen
148
199
 
149
200
  Request:
@@ -199,6 +250,12 @@ Sort (modes: `status-priority-type-title`, `updated`, `created`, `id`):
199
250
  { "operation": "sort", "mode": "updated" }
200
251
  ```
201
252
 
253
+ Ready (actionable beans only):
254
+
255
+ ```json
256
+ { "operation": "ready" }
257
+ ```
258
+
202
259
  LLM context (generate Copilot instructions; optional write-to-workspace):
203
260
 
204
261
  ```json
@@ -211,7 +268,7 @@ Response (structuredContent):
211
268
  {
212
269
  "graphqlSchema": "...",
213
270
  "generatedInstructions": "...",
214
- "instructionsPath": "/workspace/.github/instructions/tasks.instructions.md"
271
+ "instructionsPath": "/workspace/.github/instructions/beans-prime.instructions.md"
215
272
  }
216
273
  ```
217
274
 
@@ -22747,7 +22747,7 @@ var init_utils = __esm({
22747
22747
  });
22748
22748
 
22749
22749
  // src/internal/graphql.ts
22750
- var LIST_BEANS_QUERY, CREATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION, DELETE_BEAN_MUTATION;
22750
+ var LIST_BEANS_QUERY, CREATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION_WITH_IF_MATCH, DELETE_BEAN_MUTATION;
22751
22751
  var init_graphql = __esm({
22752
22752
  "src/internal/graphql.ts"() {
22753
22753
  "use strict";
@@ -22765,6 +22765,11 @@ var init_graphql = __esm({
22765
22765
  mutation($id: ID!, $input: UpdateBeanInput!) {
22766
22766
  updateBean(id: $id, input: $input) { id slug path title body status type priority tags parentId blockingIds blockedByIds createdAt updatedAt etag }
22767
22767
  }
22768
+ `;
22769
+ UPDATE_BEAN_MUTATION_WITH_IF_MATCH = `
22770
+ mutation($id: ID!, $input: UpdateBeanInput!, $ifMatch: String!) {
22771
+ updateBean(id: $id, input: $input, ifMatch: $ifMatch) { id slug path title body status type priority tags parentId blockingIds blockedByIds createdAt updatedAt etag }
22772
+ }
22768
22773
  `;
22769
22774
  DELETE_BEAN_MUTATION = `
22770
22775
  mutation($id: ID!) {
@@ -22922,10 +22927,50 @@ Output: ${stdout.slice(0, 1e3)}`
22922
22927
  if (updates.body !== void 0) {
22923
22928
  updateInput.body = updates.body;
22924
22929
  }
22925
- const { data, errors } = await this.executeGraphQL(UPDATE_BEAN_MUTATION, {
22926
- id: beanId,
22927
- input: updateInput
22928
- });
22930
+ const bodyMod = {};
22931
+ if (updates.bodyAppend !== void 0) {
22932
+ bodyMod.append = updates.bodyAppend;
22933
+ }
22934
+ if (Array.isArray(updates.bodyReplace) && updates.bodyReplace.length > 0) {
22935
+ bodyMod.replace = updates.bodyReplace;
22936
+ }
22937
+ if (Object.keys(bodyMod).length > 0) {
22938
+ updateInput.bodyMod = bodyMod;
22939
+ }
22940
+ let data;
22941
+ let errors;
22942
+ if (updates.ifMatch) {
22943
+ try {
22944
+ const res = await this.executeGraphQL(UPDATE_BEAN_MUTATION_WITH_IF_MATCH, {
22945
+ id: beanId,
22946
+ input: updateInput,
22947
+ ifMatch: updates.ifMatch
22948
+ });
22949
+ data = res.data;
22950
+ errors = res.errors;
22951
+ } catch (error48) {
22952
+ const message = error48.message || "";
22953
+ const unsupportedIfMatch = /unknown argument.*ifMatch|unknown field.*ifMatch|ifMatch.*not defined|field .*updateBean.* argument .*ifMatch/i.test(
22954
+ message
22955
+ );
22956
+ if (!unsupportedIfMatch) {
22957
+ throw error48;
22958
+ }
22959
+ const fallback = await this.executeGraphQL(UPDATE_BEAN_MUTATION, {
22960
+ id: beanId,
22961
+ input: updateInput
22962
+ });
22963
+ data = fallback.data;
22964
+ errors = fallback.errors;
22965
+ }
22966
+ } else {
22967
+ const res = await this.executeGraphQL(UPDATE_BEAN_MUTATION, {
22968
+ id: beanId,
22969
+ input: updateInput
22970
+ });
22971
+ data = res.data;
22972
+ errors = res.errors;
22973
+ }
22929
22974
  if (errors && errors.length > 0) {
22930
22975
  throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
22931
22976
  }
@@ -22945,6 +22990,21 @@ Output: ${stdout.slice(0, 1e3)}`
22945
22990
  const content = await (0, import_promises.readFile)(configPath, "utf8");
22946
22991
  return { configPath, content };
22947
22992
  }
22993
+ async primeInstructions() {
22994
+ const { stdout } = await execFileAsync(this.cliPath, ["prime"], {
22995
+ cwd: this.workspaceRoot,
22996
+ env: this.getSafeEnv(),
22997
+ maxBuffer: 10 * 1024 * 1024,
22998
+ timeout: 3e4
22999
+ });
23000
+ return stdout.trim();
23001
+ }
23002
+ async writeInstructions(instructions) {
23003
+ const instructionsPath = (0, import_node_path2.join)(this.workspaceRoot, ".github", "instructions", "beans-prime.instructions.md");
23004
+ await (0, import_promises.mkdir)((0, import_node_path2.dirname)(instructionsPath), { recursive: true });
23005
+ await (0, import_promises.writeFile)(instructionsPath, instructions, "utf8");
23006
+ return instructionsPath;
23007
+ }
22948
23008
  async graphqlSchema() {
22949
23009
  const { stdout } = await execFileAsync(this.cliPath, ["graphql", "--schema"], {
22950
23010
  cwd: this.workspaceRoot,
@@ -31072,6 +31132,10 @@ var EMPTY_COMPLETION_RESULT = {
31072
31132
  }
31073
31133
  };
31074
31134
 
31135
+ // src/server/BeansMcpServer.ts
31136
+ var import_node_child_process2 = require("child_process");
31137
+ var import_node_util2 = require("util");
31138
+
31075
31139
  // src/internal/queryHelpers.ts
31076
31140
  function sortBeansInternal(beans, mode) {
31077
31141
  const sorted = [...beans];
@@ -31127,15 +31191,23 @@ async function handleQueryOperation(backend, params) {
31127
31191
  const { operation, mode, statuses, types, search, tags, writeToWorkspaceInstructions, includeClosed } = params;
31128
31192
  if (operation === "llm_context") {
31129
31193
  const graphqlSchema = typeof backend.graphqlSchema === "function" ? await backend.graphqlSchema() : "";
31130
- const instructionsPath = writeToWorkspaceInstructions && typeof backend.writeInstructions === "function" ? await backend.writeInstructions("") : null;
31194
+ let generatedInstructions = "";
31195
+ if (typeof backend.primeInstructions === "function") {
31196
+ try {
31197
+ generatedInstructions = await backend.primeInstructions();
31198
+ } catch {
31199
+ generatedInstructions = "";
31200
+ }
31201
+ }
31202
+ const instructionsPath = writeToWorkspaceInstructions && typeof backend.writeInstructions === "function" ? await backend.writeInstructions(generatedInstructions) : null;
31131
31203
  return {
31132
31204
  content: [
31133
31205
  {
31134
31206
  type: "text",
31135
- text: JSON.stringify({ graphqlSchema, generatedInstructions: "", instructionsPath }, null, 2)
31207
+ text: JSON.stringify({ graphqlSchema, generatedInstructions, instructionsPath }, null, 2)
31136
31208
  }
31137
31209
  ],
31138
- structuredContent: { graphqlSchema, generatedInstructions: "", instructionsPath }
31210
+ structuredContent: { graphqlSchema, generatedInstructions, instructionsPath }
31139
31211
  };
31140
31212
  }
31141
31213
  if (operation === "open_config") {
@@ -31181,6 +31253,31 @@ async function handleQueryOperation(backend, params) {
31181
31253
  structuredContent: { query: search, count: beans2.length, beans: beans2 }
31182
31254
  };
31183
31255
  }
31256
+ if (operation === "ready") {
31257
+ const allBeans = await backend.list();
31258
+ const byId = new Map(allBeans.map((bean) => [bean.id, bean]));
31259
+ const candidates = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
31260
+ const readyBeans = candidates.filter((bean) => {
31261
+ if (bean.status !== "todo") {
31262
+ return false;
31263
+ }
31264
+ const blockedBy = bean.blockedByIds || [];
31265
+ if (blockedBy.length === 0) {
31266
+ return true;
31267
+ }
31268
+ return blockedBy.every((blockerId) => {
31269
+ const blocker = byId.get(blockerId);
31270
+ if (!blocker) {
31271
+ return false;
31272
+ }
31273
+ return blocker.status === "completed" || blocker.status === "scrapped";
31274
+ });
31275
+ });
31276
+ return {
31277
+ content: [{ type: "text", text: JSON.stringify({ count: readyBeans.length, beans: readyBeans }, null, 2) }],
31278
+ structuredContent: { count: readyBeans.length, beans: readyBeans }
31279
+ };
31280
+ }
31184
31281
  const beans = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
31185
31282
  const sorted = sortBeansInternal(beans, mode ?? "status-priority-type-title");
31186
31283
  return {
@@ -31203,7 +31300,7 @@ init_utils();
31203
31300
  // package.json
31204
31301
  var package_default = {
31205
31302
  name: "@selfagency/beans-mcp",
31206
- version: "0.1.4",
31303
+ version: "0.4.2",
31207
31304
  private: false,
31208
31305
  description: "MCP (Model Context Protocol) server for Beans issue tracker",
31209
31306
  author: {
@@ -31258,18 +31355,18 @@ var package_default = {
31258
31355
  devDependencies: {
31259
31356
  "@modelcontextprotocol/sdk": "^1.27.1",
31260
31357
  "@octokit/rest": "^22.0.1",
31261
- "@types/node": "^20.19.0",
31262
- "@vitest/coverage-v8": "^4.0.18",
31263
- "@vitest/ui": "4.0.18",
31358
+ "@types/node": "25.5.0",
31359
+ "@vitest/coverage-v8": "^4.1.0",
31360
+ "@vitest/ui": "4.1.0",
31264
31361
  husky: "^9.1.7",
31265
- "lint-staged": "^16.2.7",
31362
+ "lint-staged": "^16.3.3",
31266
31363
  ora: "^9.3.0",
31267
- oxfmt: "^0.35.0",
31268
- oxlint: "^1.50.0",
31269
- "oxlint-tsgolint": "^0.15.0",
31364
+ oxfmt: "^0.40.0",
31365
+ oxlint: "^1.55.0",
31366
+ "oxlint-tsgolint": "^0.16.0",
31270
31367
  tsup: "8.5.1",
31271
31368
  typescript: "^5.9.3",
31272
- vitest: "4.0.18",
31369
+ vitest: "4.1.0",
31273
31370
  zod: "4.3.6",
31274
31371
  zx: "^8.8.5"
31275
31372
  },
@@ -31285,6 +31382,45 @@ var package_default = {
31285
31382
  };
31286
31383
 
31287
31384
  // src/server/BeansMcpServer.ts
31385
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
31386
+ var PACKAGE_VERSION = package_default.version ?? "0.0.0-dev";
31387
+ function getSafeCliEnv(env) {
31388
+ const whitelist = ["PATH", "HOME", "USER", "LANG", "LC_ALL", "LC_CTYPE", "SHELL"];
31389
+ const safeEnv = {};
31390
+ for (const key of whitelist) {
31391
+ if (env[key]) {
31392
+ safeEnv[key] = env[key];
31393
+ }
31394
+ }
31395
+ for (const key in env) {
31396
+ if (key.startsWith("BEANS_")) {
31397
+ safeEnv[key] = env[key];
31398
+ }
31399
+ }
31400
+ return safeEnv;
31401
+ }
31402
+ function extractVersionFromOutput(output) {
31403
+ const trimmed = output.trim();
31404
+ if (!trimmed) {
31405
+ return null;
31406
+ }
31407
+ const match = trimmed.match(/(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
31408
+ return match?.[1] ?? null;
31409
+ }
31410
+ async function detectBeansCliVersion(cliPath, workspaceRoot) {
31411
+ try {
31412
+ const { stdout, stderr } = await execFileAsync2(cliPath, ["version"], {
31413
+ cwd: workspaceRoot,
31414
+ env: getSafeCliEnv(process.env),
31415
+ maxBuffer: 1024 * 1024,
31416
+ timeout: 5e3
31417
+ });
31418
+ return extractVersionFromOutput(`${stdout}
31419
+ ${stderr}`);
31420
+ } catch {
31421
+ return null;
31422
+ }
31423
+ }
31288
31424
  async function getBeanById(backend, beanId) {
31289
31425
  try {
31290
31426
  const beans = await backend.list();
@@ -31304,7 +31440,35 @@ function initHandler(backend) {
31304
31440
  };
31305
31441
  }
31306
31442
  function viewHandler(backend) {
31307
- return async ({ beanId }) => makeTextAndStructured({ bean: await getBeanById(backend, beanId) });
31443
+ return async ({ beanId, beanIds }) => {
31444
+ const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
31445
+ if (ids.length === 0) {
31446
+ throw new Error("Either beanId or beanIds must be provided");
31447
+ }
31448
+ if (ids.length === 1) {
31449
+ const bean = await getBeanById(backend, ids[0]);
31450
+ return makeTextAndStructured({ bean });
31451
+ }
31452
+ const beans = await backend.list();
31453
+ const byId = new Map(beans.map((b) => [b.id, b]));
31454
+ const found = ids.map((id) => byId.get(id)).filter(Boolean);
31455
+ const missingBeanIds = ids.filter((id) => !byId.has(id));
31456
+ return makeTextAndStructured({ beans: found, missingBeanIds, count: found.length, requestedCount: ids.length });
31457
+ };
31458
+ }
31459
+ async function checkVersionCompatibility(cliPath, workspaceRoot, detector) {
31460
+ const detectedBeansVersion = await detector(cliPath, workspaceRoot);
31461
+ if (!detectedBeansVersion) {
31462
+ console.error(
31463
+ `[beans-mcp] warning: unable to determine Beans CLI version from \`${cliPath}\`; proceeding without version compatibility checks.`
31464
+ );
31465
+ return;
31466
+ }
31467
+ if (detectedBeansVersion !== PACKAGE_VERSION) {
31468
+ console.error(
31469
+ `[beans-mcp] warning: version mismatch detected (beans=${detectedBeansVersion}, beans-mcp=${PACKAGE_VERSION}); continuing startup.`
31470
+ );
31471
+ }
31308
31472
  }
31309
31473
  function createHandler(backend) {
31310
31474
  return async (input) => makeTextAndStructured({ bean: await backend.create(input) });
@@ -31340,17 +31504,56 @@ function updateHandler(backend) {
31340
31504
  clearParent: input.clearParent,
31341
31505
  blocking: input.blocking,
31342
31506
  blockedBy: input.blockedBy,
31343
- body: input.body
31507
+ body: input.body,
31508
+ bodyAppend: input.bodyAppend,
31509
+ bodyReplace: input.bodyReplace,
31510
+ ifMatch: input.ifMatch
31344
31511
  })
31345
31512
  });
31346
31513
  }
31347
31514
  function deleteHandler(backend) {
31348
- return async ({ beanId, force }) => {
31349
- const bean = await getBeanById(backend, beanId);
31350
- if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31351
- throw new Error("Only draft and scrapped beans are deletable unless force=true");
31515
+ return async ({ beanId, beanIds, force }) => {
31516
+ const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
31517
+ if (ids.length === 0) {
31518
+ throw new Error("Either beanId or beanIds must be provided");
31519
+ }
31520
+ if (ids.length === 1) {
31521
+ const bean = await getBeanById(backend, ids[0]);
31522
+ if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31523
+ throw new Error("Only draft and scrapped beans are deletable unless force=true");
31524
+ }
31525
+ return makeTextAndStructured(await backend.delete(ids[0]));
31352
31526
  }
31353
- return makeTextAndStructured(await backend.delete(beanId));
31527
+ const beans = await backend.list();
31528
+ const byId = new Map(beans.map((b) => [b.id, b]));
31529
+ const results = [];
31530
+ for (const id of ids) {
31531
+ const bean = byId.get(id);
31532
+ if (!bean) {
31533
+ results.push({ beanId: id, deleted: false, error: "Bean not found" });
31534
+ continue;
31535
+ }
31536
+ if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31537
+ results.push({
31538
+ beanId: id,
31539
+ deleted: false,
31540
+ error: "Only draft and scrapped beans are deletable unless force=true"
31541
+ });
31542
+ continue;
31543
+ }
31544
+ try {
31545
+ await backend.delete(id);
31546
+ results.push({ beanId: id, deleted: true });
31547
+ } catch (error48) {
31548
+ results.push({ beanId: id, deleted: false, error: error48.message });
31549
+ }
31550
+ }
31551
+ return makeTextAndStructured({
31552
+ results,
31553
+ requestedCount: ids.length,
31554
+ deletedCount: results.filter((r) => r.deleted).length,
31555
+ failedCount: results.filter((r) => !r.deleted).length
31556
+ });
31354
31557
  };
31355
31558
  }
31356
31559
  function queryHandler(backend) {
@@ -31411,7 +31614,12 @@ function registerTools(server, backend) {
31411
31614
  {
31412
31615
  title: "View Bean",
31413
31616
  description: "Fetch full bean details by ID.",
31414
- inputSchema: external_exports3.object({ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH) }),
31617
+ inputSchema: external_exports3.object({
31618
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31619
+ beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional()
31620
+ }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31621
+ message: "Either beanId or beanIds must be provided"
31622
+ }),
31415
31623
  annotations: {
31416
31624
  readOnlyHint: true,
31417
31625
  destructiveHint: false,
@@ -31500,8 +31708,21 @@ function registerTools(server, backend) {
31500
31708
  clearParent: external_exports3.boolean().optional(),
31501
31709
  blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31502
31710
  blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31503
- body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional()
31504
- }),
31711
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31712
+ bodyAppend: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31713
+ bodyReplace: external_exports3.array(
31714
+ external_exports3.object({
31715
+ old: external_exports3.string().max(MAX_DESCRIPTION_LENGTH),
31716
+ new: external_exports3.string().max(MAX_DESCRIPTION_LENGTH)
31717
+ })
31718
+ ).optional(),
31719
+ ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
31720
+ }).refine(
31721
+ (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
31722
+ {
31723
+ message: "body cannot be combined with bodyAppend/bodyReplace"
31724
+ }
31725
+ ),
31505
31726
  annotations: {
31506
31727
  readOnlyHint: false,
31507
31728
  destructiveHint: false,
@@ -31517,8 +31738,11 @@ function registerTools(server, backend) {
31517
31738
  title: "Delete Bean",
31518
31739
  description: "Delete a bean (intended for draft/scrapped beans).",
31519
31740
  inputSchema: external_exports3.object({
31520
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
31741
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31742
+ beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional(),
31521
31743
  force: external_exports3.boolean().default(false)
31744
+ }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31745
+ message: "Either beanId or beanIds must be provided"
31522
31746
  }),
31523
31747
  annotations: {
31524
31748
  readOnlyHint: false,
@@ -31535,7 +31759,7 @@ function registerTools(server, backend) {
31535
31759
  title: "Query Beans",
31536
31760
  description: "Unified query tool for refresh, filter, search, and sort operations.",
31537
31761
  inputSchema: external_exports3.object({
31538
- operation: external_exports3.enum(["refresh", "filter", "search", "sort", "llm_context", "open_config"]).default("refresh"),
31762
+ operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config"]).default("refresh"),
31539
31763
  mode: external_exports3.enum(["status-priority-type-title", "updated", "created", "id"]).optional(),
31540
31764
  statuses: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
31541
31765
  types: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
@@ -31617,6 +31841,12 @@ var MutableBackend = class {
31617
31841
  openConfig() {
31618
31842
  return this.inner.openConfig();
31619
31843
  }
31844
+ primeInstructions() {
31845
+ return this.inner.primeInstructions?.() ?? Promise.resolve("");
31846
+ }
31847
+ writeInstructions(instructions) {
31848
+ return this.inner.writeInstructions?.(instructions) ?? Promise.resolve(null);
31849
+ }
31620
31850
  graphqlSchema() {
31621
31851
  return this.inner.graphqlSchema();
31622
31852
  }
@@ -31654,7 +31884,7 @@ async function createBeansMcpServer(opts) {
31654
31884
  const backend = opts.backend || new BeansCliBackend2(opts.workspaceRoot, opts.cliPath || "beans", opts.logDir);
31655
31885
  const server = new McpServer({
31656
31886
  name: opts.name || "beans-mcp-server",
31657
- version: opts.version || "0.1.0"
31887
+ version: opts.version || PACKAGE_VERSION
31658
31888
  });
31659
31889
  registerTools(server, backend);
31660
31890
  return { server, backend };
@@ -31726,17 +31956,17 @@ function parseCliArgs(argv) {
31726
31956
  }
31727
31957
  return { workspaceRoot, workspaceExplicit, cliPath, port, logDir };
31728
31958
  }
31729
- async function startBeansMcpServer(argv, _resolveRoots) {
31959
+ async function startBeansMcpServer(argv, _resolveRoots, _detectBeansVersion) {
31730
31960
  const { BeansCliBackend: BeansCliBackend2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
31731
31961
  const { StdioServerTransport: StdioServerTransport2 } = await Promise.resolve().then(() => (init_stdio2(), stdio_exports));
31732
31962
  const { workspaceRoot, workspaceExplicit, cliPath, port, logDir } = parseCliArgs(argv);
31963
+ let effectiveWorkspaceRoot = workspaceRoot;
31733
31964
  process.env.BEANS_VSCODE_MCP_PORT = String(port);
31734
31965
  process.env.BEANS_MCP_PORT = String(port);
31735
31966
  try {
31736
- const version2 = package_default.version ?? "0.0.0-dev";
31737
31967
  const workspaceLabel = workspaceExplicit ? workspaceRoot : "(auto from roots)";
31738
31968
  console.error(
31739
- `[beans-mcp] v${version2} starting (port=${port}, workspace=${workspaceLabel}, cli=${cliPath}, logDir=${logDir})`
31969
+ `[beans-mcp] v${PACKAGE_VERSION} starting (port=${port}, workspace=${workspaceLabel}, cli=${cliPath}, logDir=${logDir})`
31740
31970
  );
31741
31971
  } catch {
31742
31972
  }
@@ -31753,13 +31983,17 @@ async function startBeansMcpServer(argv, _resolveRoots) {
31753
31983
  const resolver = _resolveRoots ?? resolveWorkspaceFromRoots;
31754
31984
  const rootPath = await resolver(server);
31755
31985
  if (rootPath) {
31756
- mutable.setInner(new BeansCliBackend2(rootPath, cliPath));
31986
+ mutable.setInner(new BeansCliBackend2(rootPath, cliPath, logDir));
31987
+ effectiveWorkspaceRoot = rootPath;
31757
31988
  try {
31758
31989
  console.error(`[beans-mcp] workspace resolved from roots: ${rootPath}`);
31759
31990
  } catch {
31760
31991
  }
31761
31992
  }
31762
31993
  }
31994
+ const beansVersionDetector = _detectBeansVersion ?? detectBeansCliVersion;
31995
+ void checkVersionCompatibility(cliPath, effectiveWorkspaceRoot, beansVersionDetector).catch(() => {
31996
+ });
31763
31997
  }
31764
31998
 
31765
31999
  // src/cli.ts