@selfagency/beans-mcp 0.1.3 → 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.
Files changed (59) hide show
  1. package/README.md +63 -6
  2. package/{dist/beans-mcp-server.cjs → beans-mcp-server.cjs} +269 -34
  3. package/{dist/index.cjs → index.cjs} +269 -34
  4. package/{dist/index.d.ts → index.d.ts} +19 -1
  5. package/{dist/index.js → index.js} +269 -34
  6. package/package.json +28 -64
  7. package/.beans.yml +0 -6
  8. package/.claude/settings.local.json +0 -18
  9. package/.editorconfig +0 -13
  10. package/.github/dependabot.yml +0 -11
  11. package/.github/workflows/release.yml +0 -235
  12. package/.github/workflows/test.yml +0 -84
  13. package/.husky/pre-commit +0 -1
  14. package/.nvmrc +0 -1
  15. package/.oxfmtrc.json +0 -11
  16. package/.oxlintrc.json +0 -37
  17. package/.vscode/settings.json +0 -3
  18. package/CHANGELOG.md +0 -160
  19. package/CONTRIBUTING.md +0 -139
  20. package/LICENSE.txt +0 -21
  21. package/codeql/codeql-custom-queries-actions/README.md +0 -14
  22. package/codeql/codeql-custom-queries-actions/codeql-pack.lock.yml +0 -32
  23. package/codeql/codeql-custom-queries-actions/codeql-pack.yml +0 -7
  24. package/codeql/codeql-custom-queries-actions/qlpack.yml +0 -6
  25. package/codeql/codeql-custom-queries-actions/queries/github-script-without-tojson.ql +0 -18
  26. package/codeql/codeql-custom-queries-actions/queries/strict-external-action-pinning.ql +0 -18
  27. package/codeql/codeql-custom-queries-javascript/README.md +0 -14
  28. package/codeql/codeql-custom-queries-javascript/codeql-pack.lock.yml +0 -30
  29. package/codeql/codeql-custom-queries-javascript/codeql-pack.yml +0 -7
  30. package/codeql/codeql-custom-queries-javascript/qlpack.yml +0 -6
  31. package/codeql/codeql-custom-queries-javascript/queries/child-process-shell-apis.ql +0 -26
  32. package/codeql/codeql-custom-queries-javascript/queries/innerhtml-assignment.ql +0 -24
  33. package/dist/README.md +0 -307
  34. package/dist/beans-mcp-server.cjs.map +0 -1
  35. package/dist/index.cjs.map +0 -1
  36. package/dist/index.js.map +0 -1
  37. package/dist/package.json +0 -43
  38. package/pnpm-workspace.yaml +0 -2
  39. package/scripts/release.js +0 -433
  40. package/scripts/write-dist-package.js +0 -53
  41. package/src/cli.ts +0 -14
  42. package/src/index.ts +0 -21
  43. package/src/internal/graphql.ts +0 -33
  44. package/src/internal/queryHelpers.ts +0 -157
  45. package/src/server/BeansMcpServer.ts +0 -623
  46. package/src/server/backend.ts +0 -364
  47. package/src/test/BeansMcpServer.test.ts +0 -514
  48. package/src/test/handlers.unit.test.ts +0 -201
  49. package/src/test/parseCliArgs.test.ts +0 -69
  50. package/src/test/protocol.e2e.test.ts +0 -884
  51. package/src/test/queryHelpers.test.ts +0 -524
  52. package/src/test/startBeansMcpServer.test.ts +0 -146
  53. package/src/test/tools-integration.test.ts +0 -912
  54. package/src/test/utils.test.ts +0 -81
  55. package/src/types.ts +0 -46
  56. package/src/utils.ts +0 -20
  57. package/tsconfig.json +0 -24
  58. package/tsup.config.ts +0 -42
  59. package/vitest.config.ts +0 -18
@@ -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,
@@ -31089,6 +31149,10 @@ var EMPTY_COMPLETION_RESULT = {
31089
31149
  }
31090
31150
  };
31091
31151
 
31152
+ // src/server/BeansMcpServer.ts
31153
+ var import_node_child_process2 = require("child_process");
31154
+ var import_node_util2 = require("util");
31155
+
31092
31156
  // src/internal/queryHelpers.ts
31093
31157
  function sortBeansInternal(beans, mode) {
31094
31158
  const sorted = [...beans];
@@ -31144,15 +31208,23 @@ async function handleQueryOperation(backend, params) {
31144
31208
  const { operation, mode, statuses, types, search, tags, writeToWorkspaceInstructions, includeClosed } = params;
31145
31209
  if (operation === "llm_context") {
31146
31210
  const graphqlSchema = typeof backend.graphqlSchema === "function" ? await backend.graphqlSchema() : "";
31147
- const instructionsPath = writeToWorkspaceInstructions && typeof backend.writeInstructions === "function" ? await backend.writeInstructions("") : null;
31211
+ let generatedInstructions = "";
31212
+ if (typeof backend.primeInstructions === "function") {
31213
+ try {
31214
+ generatedInstructions = await backend.primeInstructions();
31215
+ } catch {
31216
+ generatedInstructions = "";
31217
+ }
31218
+ }
31219
+ const instructionsPath = writeToWorkspaceInstructions && typeof backend.writeInstructions === "function" ? await backend.writeInstructions(generatedInstructions) : null;
31148
31220
  return {
31149
31221
  content: [
31150
31222
  {
31151
31223
  type: "text",
31152
- text: JSON.stringify({ graphqlSchema, generatedInstructions: "", instructionsPath }, null, 2)
31224
+ text: JSON.stringify({ graphqlSchema, generatedInstructions, instructionsPath }, null, 2)
31153
31225
  }
31154
31226
  ],
31155
- structuredContent: { graphqlSchema, generatedInstructions: "", instructionsPath }
31227
+ structuredContent: { graphqlSchema, generatedInstructions, instructionsPath }
31156
31228
  };
31157
31229
  }
31158
31230
  if (operation === "open_config") {
@@ -31198,6 +31270,31 @@ async function handleQueryOperation(backend, params) {
31198
31270
  structuredContent: { query: search, count: beans2.length, beans: beans2 }
31199
31271
  };
31200
31272
  }
31273
+ if (operation === "ready") {
31274
+ const allBeans = await backend.list();
31275
+ const byId = new Map(allBeans.map((bean) => [bean.id, bean]));
31276
+ const candidates = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
31277
+ const readyBeans = candidates.filter((bean) => {
31278
+ if (bean.status !== "todo") {
31279
+ return false;
31280
+ }
31281
+ const blockedBy = bean.blockedByIds || [];
31282
+ if (blockedBy.length === 0) {
31283
+ return true;
31284
+ }
31285
+ return blockedBy.every((blockerId) => {
31286
+ const blocker = byId.get(blockerId);
31287
+ if (!blocker) {
31288
+ return false;
31289
+ }
31290
+ return blocker.status === "completed" || blocker.status === "scrapped";
31291
+ });
31292
+ });
31293
+ return {
31294
+ content: [{ type: "text", text: JSON.stringify({ count: readyBeans.length, beans: readyBeans }, null, 2) }],
31295
+ structuredContent: { count: readyBeans.length, beans: readyBeans }
31296
+ };
31297
+ }
31201
31298
  const beans = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
31202
31299
  const sorted = sortBeansInternal(beans, mode ?? "status-priority-type-title");
31203
31300
  return {
@@ -31220,7 +31317,7 @@ init_utils();
31220
31317
  // package.json
31221
31318
  var package_default = {
31222
31319
  name: "@selfagency/beans-mcp",
31223
- version: "0.1.3",
31320
+ version: "0.4.2",
31224
31321
  private: false,
31225
31322
  description: "MCP (Model Context Protocol) server for Beans issue tracker",
31226
31323
  author: {
@@ -31236,6 +31333,7 @@ var package_default = {
31236
31333
  type: "git",
31237
31334
  url: "git+https://github.com/selfagency/beans-mcp.git"
31238
31335
  },
31336
+ mcpName: "io.github.selfagency/beans-mcp",
31239
31337
  keywords: [
31240
31338
  "beans",
31241
31339
  "mcp",
@@ -31274,18 +31372,18 @@ var package_default = {
31274
31372
  devDependencies: {
31275
31373
  "@modelcontextprotocol/sdk": "^1.27.1",
31276
31374
  "@octokit/rest": "^22.0.1",
31277
- "@types/node": "^20.19.0",
31278
- "@vitest/coverage-v8": "^4.0.18",
31279
- "@vitest/ui": "4.0.18",
31375
+ "@types/node": "25.5.0",
31376
+ "@vitest/coverage-v8": "^4.1.0",
31377
+ "@vitest/ui": "4.1.0",
31280
31378
  husky: "^9.1.7",
31281
- "lint-staged": "^16.2.7",
31379
+ "lint-staged": "^16.3.3",
31282
31380
  ora: "^9.3.0",
31283
- oxfmt: "^0.35.0",
31284
- oxlint: "^1.50.0",
31285
- "oxlint-tsgolint": "^0.15.0",
31381
+ oxfmt: "^0.40.0",
31382
+ oxlint: "^1.55.0",
31383
+ "oxlint-tsgolint": "^0.16.0",
31286
31384
  tsup: "8.5.1",
31287
31385
  typescript: "^5.9.3",
31288
- vitest: "4.0.18",
31386
+ vitest: "4.1.0",
31289
31387
  zod: "4.3.6",
31290
31388
  zx: "^8.8.5"
31291
31389
  },
@@ -31301,6 +31399,45 @@ var package_default = {
31301
31399
  };
31302
31400
 
31303
31401
  // src/server/BeansMcpServer.ts
31402
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
31403
+ var PACKAGE_VERSION = package_default.version ?? "0.0.0-dev";
31404
+ function getSafeCliEnv(env) {
31405
+ const whitelist = ["PATH", "HOME", "USER", "LANG", "LC_ALL", "LC_CTYPE", "SHELL"];
31406
+ const safeEnv = {};
31407
+ for (const key of whitelist) {
31408
+ if (env[key]) {
31409
+ safeEnv[key] = env[key];
31410
+ }
31411
+ }
31412
+ for (const key in env) {
31413
+ if (key.startsWith("BEANS_")) {
31414
+ safeEnv[key] = env[key];
31415
+ }
31416
+ }
31417
+ return safeEnv;
31418
+ }
31419
+ function extractVersionFromOutput(output) {
31420
+ const trimmed = output.trim();
31421
+ if (!trimmed) {
31422
+ return null;
31423
+ }
31424
+ const match = trimmed.match(/(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
31425
+ return match?.[1] ?? null;
31426
+ }
31427
+ async function detectBeansCliVersion(cliPath, workspaceRoot) {
31428
+ try {
31429
+ const { stdout, stderr } = await execFileAsync2(cliPath, ["version"], {
31430
+ cwd: workspaceRoot,
31431
+ env: getSafeCliEnv(process.env),
31432
+ maxBuffer: 1024 * 1024,
31433
+ timeout: 5e3
31434
+ });
31435
+ return extractVersionFromOutput(`${stdout}
31436
+ ${stderr}`);
31437
+ } catch {
31438
+ return null;
31439
+ }
31440
+ }
31304
31441
  async function getBeanById(backend, beanId) {
31305
31442
  try {
31306
31443
  const beans = await backend.list();
@@ -31320,7 +31457,35 @@ function initHandler(backend) {
31320
31457
  };
31321
31458
  }
31322
31459
  function viewHandler(backend) {
31323
- return async ({ beanId }) => makeTextAndStructured({ bean: await getBeanById(backend, beanId) });
31460
+ return async ({ beanId, beanIds }) => {
31461
+ const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
31462
+ if (ids.length === 0) {
31463
+ throw new Error("Either beanId or beanIds must be provided");
31464
+ }
31465
+ if (ids.length === 1) {
31466
+ const bean = await getBeanById(backend, ids[0]);
31467
+ return makeTextAndStructured({ bean });
31468
+ }
31469
+ const beans = await backend.list();
31470
+ const byId = new Map(beans.map((b) => [b.id, b]));
31471
+ const found = ids.map((id) => byId.get(id)).filter(Boolean);
31472
+ const missingBeanIds = ids.filter((id) => !byId.has(id));
31473
+ return makeTextAndStructured({ beans: found, missingBeanIds, count: found.length, requestedCount: ids.length });
31474
+ };
31475
+ }
31476
+ async function checkVersionCompatibility(cliPath, workspaceRoot, detector) {
31477
+ const detectedBeansVersion = await detector(cliPath, workspaceRoot);
31478
+ if (!detectedBeansVersion) {
31479
+ console.error(
31480
+ `[beans-mcp] warning: unable to determine Beans CLI version from \`${cliPath}\`; proceeding without version compatibility checks.`
31481
+ );
31482
+ return;
31483
+ }
31484
+ if (detectedBeansVersion !== PACKAGE_VERSION) {
31485
+ console.error(
31486
+ `[beans-mcp] warning: version mismatch detected (beans=${detectedBeansVersion}, beans-mcp=${PACKAGE_VERSION}); continuing startup.`
31487
+ );
31488
+ }
31324
31489
  }
31325
31490
  function createHandler(backend) {
31326
31491
  return async (input) => makeTextAndStructured({ bean: await backend.create(input) });
@@ -31356,17 +31521,56 @@ function updateHandler(backend) {
31356
31521
  clearParent: input.clearParent,
31357
31522
  blocking: input.blocking,
31358
31523
  blockedBy: input.blockedBy,
31359
- body: input.body
31524
+ body: input.body,
31525
+ bodyAppend: input.bodyAppend,
31526
+ bodyReplace: input.bodyReplace,
31527
+ ifMatch: input.ifMatch
31360
31528
  })
31361
31529
  });
31362
31530
  }
31363
31531
  function deleteHandler(backend) {
31364
- return async ({ beanId, force }) => {
31365
- const bean = await getBeanById(backend, beanId);
31366
- if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31367
- throw new Error("Only draft and scrapped beans are deletable unless force=true");
31532
+ return async ({ beanId, beanIds, force }) => {
31533
+ const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
31534
+ if (ids.length === 0) {
31535
+ throw new Error("Either beanId or beanIds must be provided");
31536
+ }
31537
+ if (ids.length === 1) {
31538
+ const bean = await getBeanById(backend, ids[0]);
31539
+ if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31540
+ throw new Error("Only draft and scrapped beans are deletable unless force=true");
31541
+ }
31542
+ return makeTextAndStructured(await backend.delete(ids[0]));
31368
31543
  }
31369
- return makeTextAndStructured(await backend.delete(beanId));
31544
+ const beans = await backend.list();
31545
+ const byId = new Map(beans.map((b) => [b.id, b]));
31546
+ const results = [];
31547
+ for (const id of ids) {
31548
+ const bean = byId.get(id);
31549
+ if (!bean) {
31550
+ results.push({ beanId: id, deleted: false, error: "Bean not found" });
31551
+ continue;
31552
+ }
31553
+ if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31554
+ results.push({
31555
+ beanId: id,
31556
+ deleted: false,
31557
+ error: "Only draft and scrapped beans are deletable unless force=true"
31558
+ });
31559
+ continue;
31560
+ }
31561
+ try {
31562
+ await backend.delete(id);
31563
+ results.push({ beanId: id, deleted: true });
31564
+ } catch (error48) {
31565
+ results.push({ beanId: id, deleted: false, error: error48.message });
31566
+ }
31567
+ }
31568
+ return makeTextAndStructured({
31569
+ results,
31570
+ requestedCount: ids.length,
31571
+ deletedCount: results.filter((r) => r.deleted).length,
31572
+ failedCount: results.filter((r) => !r.deleted).length
31573
+ });
31370
31574
  };
31371
31575
  }
31372
31576
  function queryHandler(backend) {
@@ -31427,7 +31631,12 @@ function registerTools(server, backend) {
31427
31631
  {
31428
31632
  title: "View Bean",
31429
31633
  description: "Fetch full bean details by ID.",
31430
- inputSchema: external_exports3.object({ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH) }),
31634
+ inputSchema: external_exports3.object({
31635
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31636
+ beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional()
31637
+ }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31638
+ message: "Either beanId or beanIds must be provided"
31639
+ }),
31431
31640
  annotations: {
31432
31641
  readOnlyHint: true,
31433
31642
  destructiveHint: false,
@@ -31516,8 +31725,21 @@ function registerTools(server, backend) {
31516
31725
  clearParent: external_exports3.boolean().optional(),
31517
31726
  blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31518
31727
  blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31519
- body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional()
31520
- }),
31728
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31729
+ bodyAppend: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31730
+ bodyReplace: external_exports3.array(
31731
+ external_exports3.object({
31732
+ old: external_exports3.string().max(MAX_DESCRIPTION_LENGTH),
31733
+ new: external_exports3.string().max(MAX_DESCRIPTION_LENGTH)
31734
+ })
31735
+ ).optional(),
31736
+ ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
31737
+ }).refine(
31738
+ (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
31739
+ {
31740
+ message: "body cannot be combined with bodyAppend/bodyReplace"
31741
+ }
31742
+ ),
31521
31743
  annotations: {
31522
31744
  readOnlyHint: false,
31523
31745
  destructiveHint: false,
@@ -31533,8 +31755,11 @@ function registerTools(server, backend) {
31533
31755
  title: "Delete Bean",
31534
31756
  description: "Delete a bean (intended for draft/scrapped beans).",
31535
31757
  inputSchema: external_exports3.object({
31536
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
31758
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31759
+ beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional(),
31537
31760
  force: external_exports3.boolean().default(false)
31761
+ }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31762
+ message: "Either beanId or beanIds must be provided"
31538
31763
  }),
31539
31764
  annotations: {
31540
31765
  readOnlyHint: false,
@@ -31551,7 +31776,7 @@ function registerTools(server, backend) {
31551
31776
  title: "Query Beans",
31552
31777
  description: "Unified query tool for refresh, filter, search, and sort operations.",
31553
31778
  inputSchema: external_exports3.object({
31554
- operation: external_exports3.enum(["refresh", "filter", "search", "sort", "llm_context", "open_config"]).default("refresh"),
31779
+ operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config"]).default("refresh"),
31555
31780
  mode: external_exports3.enum(["status-priority-type-title", "updated", "created", "id"]).optional(),
31556
31781
  statuses: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
31557
31782
  types: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
@@ -31633,6 +31858,12 @@ var MutableBackend = class {
31633
31858
  openConfig() {
31634
31859
  return this.inner.openConfig();
31635
31860
  }
31861
+ primeInstructions() {
31862
+ return this.inner.primeInstructions?.() ?? Promise.resolve("");
31863
+ }
31864
+ writeInstructions(instructions) {
31865
+ return this.inner.writeInstructions?.(instructions) ?? Promise.resolve(null);
31866
+ }
31636
31867
  graphqlSchema() {
31637
31868
  return this.inner.graphqlSchema();
31638
31869
  }
@@ -31670,7 +31901,7 @@ async function createBeansMcpServer(opts) {
31670
31901
  const backend = opts.backend || new BeansCliBackend2(opts.workspaceRoot, opts.cliPath || "beans", opts.logDir);
31671
31902
  const server = new McpServer({
31672
31903
  name: opts.name || "beans-mcp-server",
31673
- version: opts.version || "0.1.0"
31904
+ version: opts.version || PACKAGE_VERSION
31674
31905
  });
31675
31906
  registerTools(server, backend);
31676
31907
  return { server, backend };
@@ -31742,17 +31973,17 @@ function parseCliArgs(argv) {
31742
31973
  }
31743
31974
  return { workspaceRoot, workspaceExplicit, cliPath, port, logDir };
31744
31975
  }
31745
- async function startBeansMcpServer(argv, _resolveRoots) {
31976
+ async function startBeansMcpServer(argv, _resolveRoots, _detectBeansVersion) {
31746
31977
  const { BeansCliBackend: BeansCliBackend2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
31747
31978
  const { StdioServerTransport: StdioServerTransport2 } = await Promise.resolve().then(() => (init_stdio2(), stdio_exports));
31748
31979
  const { workspaceRoot, workspaceExplicit, cliPath, port, logDir } = parseCliArgs(argv);
31980
+ let effectiveWorkspaceRoot = workspaceRoot;
31749
31981
  process.env.BEANS_VSCODE_MCP_PORT = String(port);
31750
31982
  process.env.BEANS_MCP_PORT = String(port);
31751
31983
  try {
31752
- const version2 = package_default.version ?? "0.0.0-dev";
31753
31984
  const workspaceLabel = workspaceExplicit ? workspaceRoot : "(auto from roots)";
31754
31985
  console.error(
31755
- `[beans-mcp] v${version2} starting (port=${port}, workspace=${workspaceLabel}, cli=${cliPath}, logDir=${logDir})`
31986
+ `[beans-mcp] v${PACKAGE_VERSION} starting (port=${port}, workspace=${workspaceLabel}, cli=${cliPath}, logDir=${logDir})`
31756
31987
  );
31757
31988
  } catch {
31758
31989
  }
@@ -31769,13 +32000,17 @@ async function startBeansMcpServer(argv, _resolveRoots) {
31769
32000
  const resolver = _resolveRoots ?? resolveWorkspaceFromRoots;
31770
32001
  const rootPath = await resolver(server);
31771
32002
  if (rootPath) {
31772
- mutable.setInner(new BeansCliBackend2(rootPath, cliPath));
32003
+ mutable.setInner(new BeansCliBackend2(rootPath, cliPath, logDir));
32004
+ effectiveWorkspaceRoot = rootPath;
31773
32005
  try {
31774
32006
  console.error(`[beans-mcp] workspace resolved from roots: ${rootPath}`);
31775
32007
  } catch {
31776
32008
  }
31777
32009
  }
31778
32010
  }
32011
+ const beansVersionDetector = _detectBeansVersion ?? detectBeansCliVersion;
32012
+ void checkVersionCompatibility(cliPath, effectiveWorkspaceRoot, beansVersionDetector).catch(() => {
32013
+ });
31779
32014
  }
31780
32015
 
31781
32016
  // src/index.ts
@@ -69,12 +69,20 @@ interface BackendInterface {
69
69
  blocking?: string[];
70
70
  blockedBy?: string[];
71
71
  body?: string;
72
+ bodyAppend?: string;
73
+ bodyReplace?: Array<{
74
+ old: string;
75
+ new: string;
76
+ }>;
77
+ ifMatch?: string;
72
78
  }): Promise<BeanRecord>;
73
79
  delete(beanId: string): Promise<Record<string, unknown>>;
74
80
  openConfig(): Promise<{
75
81
  configPath: string;
76
82
  content: string;
77
83
  }>;
84
+ primeInstructions?(): Promise<string>;
85
+ writeInstructions?(instructions: string): Promise<string | null>;
78
86
  graphqlSchema(): Promise<string>;
79
87
  readOutputLog(options?: {
80
88
  lines?: number;
@@ -146,12 +154,20 @@ declare class BeansCliBackend implements BackendInterface {
146
154
  blocking?: string[];
147
155
  blockedBy?: string[];
148
156
  body?: string;
157
+ bodyAppend?: string;
158
+ bodyReplace?: Array<{
159
+ old: string;
160
+ new: string;
161
+ }>;
162
+ ifMatch?: string;
149
163
  }): Promise<BeanRecord>;
150
164
  delete(beanId: string): Promise<Record<string, unknown>>;
151
165
  openConfig(): Promise<{
152
166
  configPath: string;
153
167
  content: string;
154
168
  }>;
169
+ primeInstructions(): Promise<string>;
170
+ writeInstructions(instructions: string): Promise<string>;
155
171
  graphqlSchema(): Promise<string>;
156
172
  readOutputLog(options?: {
157
173
  lines?: number;
@@ -202,7 +218,9 @@ declare function parseCliArgs(argv: string[]): {
202
218
  };
203
219
  declare function startBeansMcpServer(argv: string[],
204
220
  /** For testing only: override the roots resolver so tests can cover the setInner branch. */
205
- _resolveRoots?: (server: McpServer) => Promise<string | null>): Promise<void>;
221
+ _resolveRoots?: (server: McpServer) => Promise<string | null>,
222
+ /** For testing only: override Beans CLI version detection. */
223
+ _detectBeansVersion?: (cliPath: string, workspaceRoot: string) => Promise<string | null>): Promise<void>;
206
224
 
207
225
  /**
208
226
  * Check whether `target` is contained within `root` after resolving both paths.