@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/index.cjs CHANGED
@@ -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.4",
31320
+ version: "0.4.2",
31224
31321
  private: false,
31225
31322
  description: "MCP (Model Context Protocol) server for Beans issue tracker",
31226
31323
  author: {
@@ -31275,18 +31372,18 @@ var package_default = {
31275
31372
  devDependencies: {
31276
31373
  "@modelcontextprotocol/sdk": "^1.27.1",
31277
31374
  "@octokit/rest": "^22.0.1",
31278
- "@types/node": "^20.19.0",
31279
- "@vitest/coverage-v8": "^4.0.18",
31280
- "@vitest/ui": "4.0.18",
31375
+ "@types/node": "25.5.0",
31376
+ "@vitest/coverage-v8": "^4.1.0",
31377
+ "@vitest/ui": "4.1.0",
31281
31378
  husky: "^9.1.7",
31282
- "lint-staged": "^16.2.7",
31379
+ "lint-staged": "^16.3.3",
31283
31380
  ora: "^9.3.0",
31284
- oxfmt: "^0.35.0",
31285
- oxlint: "^1.50.0",
31286
- "oxlint-tsgolint": "^0.15.0",
31381
+ oxfmt: "^0.40.0",
31382
+ oxlint: "^1.55.0",
31383
+ "oxlint-tsgolint": "^0.16.0",
31287
31384
  tsup: "8.5.1",
31288
31385
  typescript: "^5.9.3",
31289
- vitest: "4.0.18",
31386
+ vitest: "4.1.0",
31290
31387
  zod: "4.3.6",
31291
31388
  zx: "^8.8.5"
31292
31389
  },
@@ -31302,6 +31399,45 @@ var package_default = {
31302
31399
  };
31303
31400
 
31304
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
+ }
31305
31441
  async function getBeanById(backend, beanId) {
31306
31442
  try {
31307
31443
  const beans = await backend.list();
@@ -31321,7 +31457,35 @@ function initHandler(backend) {
31321
31457
  };
31322
31458
  }
31323
31459
  function viewHandler(backend) {
31324
- 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
+ }
31325
31489
  }
31326
31490
  function createHandler(backend) {
31327
31491
  return async (input) => makeTextAndStructured({ bean: await backend.create(input) });
@@ -31357,17 +31521,56 @@ function updateHandler(backend) {
31357
31521
  clearParent: input.clearParent,
31358
31522
  blocking: input.blocking,
31359
31523
  blockedBy: input.blockedBy,
31360
- body: input.body
31524
+ body: input.body,
31525
+ bodyAppend: input.bodyAppend,
31526
+ bodyReplace: input.bodyReplace,
31527
+ ifMatch: input.ifMatch
31361
31528
  })
31362
31529
  });
31363
31530
  }
31364
31531
  function deleteHandler(backend) {
31365
- return async ({ beanId, force }) => {
31366
- const bean = await getBeanById(backend, beanId);
31367
- if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31368
- 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]));
31369
31543
  }
31370
- 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
+ });
31371
31574
  };
31372
31575
  }
31373
31576
  function queryHandler(backend) {
@@ -31428,7 +31631,12 @@ function registerTools(server, backend) {
31428
31631
  {
31429
31632
  title: "View Bean",
31430
31633
  description: "Fetch full bean details by ID.",
31431
- 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
+ }),
31432
31640
  annotations: {
31433
31641
  readOnlyHint: true,
31434
31642
  destructiveHint: false,
@@ -31517,8 +31725,21 @@ function registerTools(server, backend) {
31517
31725
  clearParent: external_exports3.boolean().optional(),
31518
31726
  blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31519
31727
  blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31520
- body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional()
31521
- }),
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
+ ),
31522
31743
  annotations: {
31523
31744
  readOnlyHint: false,
31524
31745
  destructiveHint: false,
@@ -31534,8 +31755,11 @@ function registerTools(server, backend) {
31534
31755
  title: "Delete Bean",
31535
31756
  description: "Delete a bean (intended for draft/scrapped beans).",
31536
31757
  inputSchema: external_exports3.object({
31537
- 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(),
31538
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"
31539
31763
  }),
31540
31764
  annotations: {
31541
31765
  readOnlyHint: false,
@@ -31552,7 +31776,7 @@ function registerTools(server, backend) {
31552
31776
  title: "Query Beans",
31553
31777
  description: "Unified query tool for refresh, filter, search, and sort operations.",
31554
31778
  inputSchema: external_exports3.object({
31555
- 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"),
31556
31780
  mode: external_exports3.enum(["status-priority-type-title", "updated", "created", "id"]).optional(),
31557
31781
  statuses: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
31558
31782
  types: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
@@ -31634,6 +31858,12 @@ var MutableBackend = class {
31634
31858
  openConfig() {
31635
31859
  return this.inner.openConfig();
31636
31860
  }
31861
+ primeInstructions() {
31862
+ return this.inner.primeInstructions?.() ?? Promise.resolve("");
31863
+ }
31864
+ writeInstructions(instructions) {
31865
+ return this.inner.writeInstructions?.(instructions) ?? Promise.resolve(null);
31866
+ }
31637
31867
  graphqlSchema() {
31638
31868
  return this.inner.graphqlSchema();
31639
31869
  }
@@ -31671,7 +31901,7 @@ async function createBeansMcpServer(opts) {
31671
31901
  const backend = opts.backend || new BeansCliBackend2(opts.workspaceRoot, opts.cliPath || "beans", opts.logDir);
31672
31902
  const server = new McpServer({
31673
31903
  name: opts.name || "beans-mcp-server",
31674
- version: opts.version || "0.1.0"
31904
+ version: opts.version || PACKAGE_VERSION
31675
31905
  });
31676
31906
  registerTools(server, backend);
31677
31907
  return { server, backend };
@@ -31743,17 +31973,17 @@ function parseCliArgs(argv) {
31743
31973
  }
31744
31974
  return { workspaceRoot, workspaceExplicit, cliPath, port, logDir };
31745
31975
  }
31746
- async function startBeansMcpServer(argv, _resolveRoots) {
31976
+ async function startBeansMcpServer(argv, _resolveRoots, _detectBeansVersion) {
31747
31977
  const { BeansCliBackend: BeansCliBackend2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
31748
31978
  const { StdioServerTransport: StdioServerTransport2 } = await Promise.resolve().then(() => (init_stdio2(), stdio_exports));
31749
31979
  const { workspaceRoot, workspaceExplicit, cliPath, port, logDir } = parseCliArgs(argv);
31980
+ let effectiveWorkspaceRoot = workspaceRoot;
31750
31981
  process.env.BEANS_VSCODE_MCP_PORT = String(port);
31751
31982
  process.env.BEANS_MCP_PORT = String(port);
31752
31983
  try {
31753
- const version2 = package_default.version ?? "0.0.0-dev";
31754
31984
  const workspaceLabel = workspaceExplicit ? workspaceRoot : "(auto from roots)";
31755
31985
  console.error(
31756
- `[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})`
31757
31987
  );
31758
31988
  } catch {
31759
31989
  }
@@ -31770,13 +32000,17 @@ async function startBeansMcpServer(argv, _resolveRoots) {
31770
32000
  const resolver = _resolveRoots ?? resolveWorkspaceFromRoots;
31771
32001
  const rootPath = await resolver(server);
31772
32002
  if (rootPath) {
31773
- mutable.setInner(new BeansCliBackend2(rootPath, cliPath));
32003
+ mutable.setInner(new BeansCliBackend2(rootPath, cliPath, logDir));
32004
+ effectiveWorkspaceRoot = rootPath;
31774
32005
  try {
31775
32006
  console.error(`[beans-mcp] workspace resolved from roots: ${rootPath}`);
31776
32007
  } catch {
31777
32008
  }
31778
32009
  }
31779
32010
  }
32011
+ const beansVersionDetector = _detectBeansVersion ?? detectBeansCliVersion;
32012
+ void checkVersionCompatibility(cliPath, effectiveWorkspaceRoot, beansVersionDetector).catch(() => {
32013
+ });
31780
32014
  }
31781
32015
 
31782
32016
  // src/index.ts
package/index.d.ts CHANGED
@@ -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.