@selfagency/beans-mcp 0.1.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14433,7 +14433,7 @@ var init_v4 = __esm({
14433
14433
  }
14434
14434
  });
14435
14435
 
14436
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
14436
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
14437
14437
  function assertCompleteRequestPrompt(request) {
14438
14438
  if (request.params.ref.type !== "ref/prompt") {
14439
14439
  throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`);
@@ -14448,7 +14448,7 @@ function assertCompleteRequestResourceTemplate(request) {
14448
14448
  }
14449
14449
  var LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, RELATED_TASK_META_KEY, JSONRPC_VERSION, AssertObjectSchema, ProgressTokenSchema, CursorSchema, TaskCreationParamsSchema, TaskMetadataSchema, RelatedTaskMetadataSchema, RequestMetaSchema, BaseRequestParamsSchema, TaskAugmentedRequestParamsSchema, isTaskAugmentedRequestParams, RequestSchema, NotificationsParamsSchema, NotificationSchema, ResultSchema, RequestIdSchema, JSONRPCRequestSchema, isJSONRPCRequest, JSONRPCNotificationSchema, isJSONRPCNotification, JSONRPCResultResponseSchema, isJSONRPCResultResponse, ErrorCode, JSONRPCErrorResponseSchema, isJSONRPCErrorResponse, JSONRPCMessageSchema, JSONRPCResponseSchema, EmptyResultSchema, CancelledNotificationParamsSchema, CancelledNotificationSchema, IconSchema, IconsSchema, BaseMetadataSchema, ImplementationSchema, FormElicitationCapabilitySchema, ElicitationCapabilitySchema, ClientTasksCapabilitySchema, ServerTasksCapabilitySchema, ClientCapabilitiesSchema, InitializeRequestParamsSchema, InitializeRequestSchema, ServerCapabilitiesSchema, InitializeResultSchema, InitializedNotificationSchema, PingRequestSchema, ProgressSchema, ProgressNotificationParamsSchema, ProgressNotificationSchema, PaginatedRequestParamsSchema, PaginatedRequestSchema, PaginatedResultSchema, TaskStatusSchema, TaskSchema, CreateTaskResultSchema, TaskStatusNotificationParamsSchema, TaskStatusNotificationSchema, GetTaskRequestSchema, GetTaskResultSchema, GetTaskPayloadRequestSchema, GetTaskPayloadResultSchema, ListTasksRequestSchema, ListTasksResultSchema, CancelTaskRequestSchema, CancelTaskResultSchema, ResourceContentsSchema, TextResourceContentsSchema, Base64Schema, BlobResourceContentsSchema, RoleSchema, AnnotationsSchema, ResourceSchema, ResourceTemplateSchema, ListResourcesRequestSchema, ListResourcesResultSchema, ListResourceTemplatesRequestSchema, ListResourceTemplatesResultSchema, ResourceRequestParamsSchema, ReadResourceRequestParamsSchema, ReadResourceRequestSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, SubscribeRequestParamsSchema, SubscribeRequestSchema, UnsubscribeRequestParamsSchema, UnsubscribeRequestSchema, ResourceUpdatedNotificationParamsSchema, ResourceUpdatedNotificationSchema, PromptArgumentSchema, PromptSchema, ListPromptsRequestSchema, ListPromptsResultSchema, GetPromptRequestParamsSchema, GetPromptRequestSchema, TextContentSchema, ImageContentSchema, AudioContentSchema, ToolUseContentSchema, EmbeddedResourceSchema, ResourceLinkSchema, ContentBlockSchema, PromptMessageSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ToolAnnotationsSchema, ToolExecutionSchema, ToolSchema, ListToolsRequestSchema, ListToolsResultSchema, CallToolResultSchema, CompatibilityCallToolResultSchema, CallToolRequestParamsSchema, CallToolRequestSchema, ToolListChangedNotificationSchema, ListChangedOptionsBaseSchema, LoggingLevelSchema, SetLevelRequestParamsSchema, SetLevelRequestSchema, LoggingMessageNotificationParamsSchema, LoggingMessageNotificationSchema, ModelHintSchema, ModelPreferencesSchema, ToolChoiceSchema, ToolResultContentSchema, SamplingContentSchema, SamplingMessageContentBlockSchema, SamplingMessageSchema, CreateMessageRequestParamsSchema, CreateMessageRequestSchema, CreateMessageResultSchema, CreateMessageResultWithToolsSchema, BooleanSchemaSchema, StringSchemaSchema, NumberSchemaSchema, UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema, LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, UntitledMultiSelectEnumSchemaSchema, TitledMultiSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema, EnumSchemaSchema, PrimitiveSchemaDefinitionSchema, ElicitRequestFormParamsSchema, ElicitRequestURLParamsSchema, ElicitRequestParamsSchema, ElicitRequestSchema, ElicitationCompleteNotificationParamsSchema, ElicitationCompleteNotificationSchema, ElicitResultSchema, ResourceTemplateReferenceSchema, PromptReferenceSchema, CompleteRequestParamsSchema, CompleteRequestSchema, CompleteResultSchema, RootSchema, ListRootsRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, ClientRequestSchema, ClientNotificationSchema, ClientResultSchema, ServerRequestSchema, ServerNotificationSchema, ServerResultSchema, McpError, UrlElicitationRequiredError;
14450
14450
  var init_types = __esm({
14451
- "node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js"() {
14451
+ "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js"() {
14452
14452
  "use strict";
14453
14453
  init_v4();
14454
14454
  LATEST_PROTOCOL_VERSION = "2025-11-25";
@@ -14460,10 +14460,9 @@ var init_types = __esm({
14460
14460
  CursorSchema = string2();
14461
14461
  TaskCreationParamsSchema = looseObject({
14462
14462
  /**
14463
- * Time in milliseconds to keep task results available after completion.
14464
- * If null, the task has unlimited lifetime until manually cleaned up.
14463
+ * Requested duration in milliseconds to retain task from creation.
14465
14464
  */
14466
- ttl: union([number2(), _null3()]).optional(),
14465
+ ttl: number2().optional(),
14467
14466
  /**
14468
14467
  * Time in milliseconds to wait between task status requests.
14469
14468
  */
@@ -14762,7 +14761,11 @@ var init_types = __esm({
14762
14761
  /**
14763
14762
  * Present if the client supports task creation.
14764
14763
  */
14765
- tasks: ClientTasksCapabilitySchema.optional()
14764
+ tasks: ClientTasksCapabilitySchema.optional(),
14765
+ /**
14766
+ * Extensions that the client supports. Keys are extension identifiers (vendor-prefix/extension-name).
14767
+ */
14768
+ extensions: record(string2(), AssertObjectSchema).optional()
14766
14769
  });
14767
14770
  InitializeRequestParamsSchema = BaseRequestParamsSchema.extend({
14768
14771
  /**
@@ -14823,7 +14826,11 @@ var init_types = __esm({
14823
14826
  /**
14824
14827
  * Present if the server supports task creation.
14825
14828
  */
14826
- tasks: ServerTasksCapabilitySchema.optional()
14829
+ tasks: ServerTasksCapabilitySchema.optional(),
14830
+ /**
14831
+ * Extensions that the server supports. Keys are extension identifiers (vendor-prefix/extension-name).
14832
+ */
14833
+ extensions: record(string2(), AssertObjectSchema).optional()
14827
14834
  });
14828
14835
  InitializeResultSchema = ResultSchema.extend({
14829
14836
  /**
@@ -15015,6 +15022,12 @@ var init_types = __esm({
15015
15022
  * The MIME type of this resource, if known.
15016
15023
  */
15017
15024
  mimeType: optional(string2()),
15025
+ /**
15026
+ * The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
15027
+ *
15028
+ * This can be used by Hosts to display file sizes and estimate context window usage.
15029
+ */
15030
+ size: optional(number2()),
15018
15031
  /**
15019
15032
  * Optional annotations for the client.
15020
15033
  */
@@ -22747,7 +22760,7 @@ var init_utils = __esm({
22747
22760
  });
22748
22761
 
22749
22762
  // src/internal/graphql.ts
22750
- var LIST_BEANS_QUERY, CREATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION, DELETE_BEAN_MUTATION;
22763
+ var LIST_BEANS_QUERY, CREATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION_WITH_IF_MATCH, DELETE_BEAN_MUTATION, LIST_BEANS_TIMESTAMPS_QUERY;
22751
22764
  var init_graphql = __esm({
22752
22765
  "src/internal/graphql.ts"() {
22753
22766
  "use strict";
@@ -22765,11 +22778,21 @@ var init_graphql = __esm({
22765
22778
  mutation($id: ID!, $input: UpdateBeanInput!) {
22766
22779
  updateBean(id: $id, input: $input) { id slug path title body status type priority tags parentId blockingIds blockedByIds createdAt updatedAt etag }
22767
22780
  }
22781
+ `;
22782
+ UPDATE_BEAN_MUTATION_WITH_IF_MATCH = `
22783
+ mutation($id: ID!, $input: UpdateBeanInput!, $ifMatch: String!) {
22784
+ updateBean(id: $id, input: $input, ifMatch: $ifMatch) { id slug path title body status type priority tags parentId blockingIds blockedByIds createdAt updatedAt etag }
22785
+ }
22768
22786
  `;
22769
22787
  DELETE_BEAN_MUTATION = `
22770
22788
  mutation($id: ID!) {
22771
22789
  deleteBean(id: $id)
22772
22790
  }
22791
+ `;
22792
+ LIST_BEANS_TIMESTAMPS_QUERY = `
22793
+ query {
22794
+ beans { id updatedAt }
22795
+ }
22773
22796
  `;
22774
22797
  }
22775
22798
  });
@@ -22792,12 +22815,26 @@ var init_backend = __esm({
22792
22815
  init_graphql();
22793
22816
  init_utils();
22794
22817
  execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
22795
- BeansCliBackend = class {
22818
+ BeansCliBackend = class _BeansCliBackend {
22796
22819
  constructor(workspaceRoot, cliPath, logDir) {
22797
22820
  this.workspaceRoot = workspaceRoot;
22798
22821
  this.cliPath = cliPath;
22799
22822
  this.logDir = logDir;
22800
22823
  }
22824
+ // ---------------------------------------------------------------------------
22825
+ // Cache
22826
+ // ---------------------------------------------------------------------------
22827
+ /** Full unfiltered records keyed by bean ID, stored under the fixed cache key `'all'`. */
22828
+ _cache = /* @__PURE__ */ new Map();
22829
+ /** Last time the unfiltered cache entry `'all'` was fetched (ms). */
22830
+ _cacheTime = /* @__PURE__ */ new Map();
22831
+ /** Short-circuit TTL: skip even the timestamp check within this window (ms). */
22832
+ static BURST_TTL_MS = 5e3;
22833
+ /** Invalidate the unfiltered list cache so the next call does a full fetch. */
22834
+ invalidateCache() {
22835
+ this._cache.delete("all");
22836
+ this._cacheTime.delete("all");
22837
+ }
22801
22838
  /**
22802
22839
  * Returns a safe environment for executing the Beans CLI,
22803
22840
  * whitelisting only necessary variables.
@@ -22821,7 +22858,7 @@ var init_backend = __esm({
22821
22858
  return (0, import_node_path2.resolve)(this.workspaceRoot, ".beans");
22822
22859
  }
22823
22860
  resolveBeanFilePath(relativePath) {
22824
- const cleaned = relativePath.trim().replace(/^\/+/, "");
22861
+ const cleaned = relativePath.trim().replace(/^\/+/, "").replace(/^\.beans(?:[\\/]|$)/, "");
22825
22862
  if (!cleaned) {
22826
22863
  throw new Error("Path is required");
22827
22864
  }
@@ -22879,10 +22916,48 @@ Output: ${stdout.slice(0, 1e3)}`
22879
22916
  if (options?.search) {
22880
22917
  filter.search = options.search;
22881
22918
  }
22919
+ const isCacheable = !filter.status && !filter.type && !filter.search;
22920
+ const cacheKey = "all";
22921
+ if (isCacheable) {
22922
+ const lastFetch = this._cacheTime.get(cacheKey) ?? 0;
22923
+ const cached2 = this._cache.get(cacheKey);
22924
+ const age = Date.now() - lastFetch;
22925
+ if (cached2 && age < _BeansCliBackend.BURST_TTL_MS) {
22926
+ return Array.from(cached2.values());
22927
+ }
22928
+ if (cached2) {
22929
+ try {
22930
+ const { data: tsData } = await this.executeGraphQL(
22931
+ LIST_BEANS_TIMESTAMPS_QUERY
22932
+ );
22933
+ const timestamps = tsData.beans;
22934
+ let dirty = timestamps.length !== cached2.size;
22935
+ if (!dirty) {
22936
+ for (const { id, updatedAt } of timestamps) {
22937
+ const existing = cached2.get(id);
22938
+ if (!existing || existing.updatedAt !== updatedAt) {
22939
+ dirty = true;
22940
+ break;
22941
+ }
22942
+ }
22943
+ }
22944
+ if (!dirty) {
22945
+ this._cacheTime.set(cacheKey, Date.now());
22946
+ return Array.from(cached2.values());
22947
+ }
22948
+ } catch {
22949
+ }
22950
+ }
22951
+ }
22882
22952
  const { data, errors } = await this.executeGraphQL(LIST_BEANS_QUERY, { filter });
22883
22953
  if (errors && errors.length > 0) {
22884
22954
  throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
22885
22955
  }
22956
+ if (isCacheable) {
22957
+ const byId = new Map(data.beans.map((b) => [b.id, b]));
22958
+ this._cache.set(cacheKey, byId);
22959
+ this._cacheTime.set(cacheKey, Date.now());
22960
+ }
22886
22961
  return data.beans;
22887
22962
  }
22888
22963
  async create(input) {
@@ -22891,7 +22966,7 @@ Output: ${stdout.slice(0, 1e3)}`
22891
22966
  type: input.type,
22892
22967
  status: input.status,
22893
22968
  priority: input.priority,
22894
- body: input.description,
22969
+ body: input.body ?? input.description,
22895
22970
  parent: input.parent
22896
22971
  };
22897
22972
  const { data, errors } = await this.executeGraphQL(CREATE_BEAN_MUTATION, {
@@ -22900,6 +22975,7 @@ Output: ${stdout.slice(0, 1e3)}`
22900
22975
  if (errors && errors.length > 0) {
22901
22976
  throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
22902
22977
  }
22978
+ this.invalidateCache();
22903
22979
  return data.createBean;
22904
22980
  }
22905
22981
  async update(beanId, updates) {
@@ -22922,13 +22998,54 @@ Output: ${stdout.slice(0, 1e3)}`
22922
22998
  if (updates.body !== void 0) {
22923
22999
  updateInput.body = updates.body;
22924
23000
  }
22925
- const { data, errors } = await this.executeGraphQL(UPDATE_BEAN_MUTATION, {
22926
- id: beanId,
22927
- input: updateInput
22928
- });
23001
+ const bodyMod = {};
23002
+ if (updates.bodyAppend !== void 0) {
23003
+ bodyMod.append = updates.bodyAppend;
23004
+ }
23005
+ if (Array.isArray(updates.bodyReplace) && updates.bodyReplace.length > 0) {
23006
+ bodyMod.replace = updates.bodyReplace;
23007
+ }
23008
+ if (Object.keys(bodyMod).length > 0) {
23009
+ updateInput.bodyMod = bodyMod;
23010
+ }
23011
+ let data;
23012
+ let errors;
23013
+ if (updates.ifMatch) {
23014
+ try {
23015
+ const res = await this.executeGraphQL(UPDATE_BEAN_MUTATION_WITH_IF_MATCH, {
23016
+ id: beanId,
23017
+ input: updateInput,
23018
+ ifMatch: updates.ifMatch
23019
+ });
23020
+ data = res.data;
23021
+ errors = res.errors;
23022
+ } catch (error48) {
23023
+ const message = error48.message || "";
23024
+ const unsupportedIfMatch = /unknown argument.*ifMatch|unknown field.*ifMatch|ifMatch.*not defined|field .*updateBean.* argument .*ifMatch/i.test(
23025
+ message
23026
+ );
23027
+ if (!unsupportedIfMatch) {
23028
+ throw error48;
23029
+ }
23030
+ const fallback = await this.executeGraphQL(UPDATE_BEAN_MUTATION, {
23031
+ id: beanId,
23032
+ input: updateInput
23033
+ });
23034
+ data = fallback.data;
23035
+ errors = fallback.errors;
23036
+ }
23037
+ } else {
23038
+ const res = await this.executeGraphQL(UPDATE_BEAN_MUTATION, {
23039
+ id: beanId,
23040
+ input: updateInput
23041
+ });
23042
+ data = res.data;
23043
+ errors = res.errors;
23044
+ }
22929
23045
  if (errors && errors.length > 0) {
22930
23046
  throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
22931
23047
  }
23048
+ this.invalidateCache();
22932
23049
  return data.updateBean;
22933
23050
  }
22934
23051
  async delete(beanId) {
@@ -22938,13 +23055,57 @@ Output: ${stdout.slice(0, 1e3)}`
22938
23055
  if (errors && errors.length > 0) {
22939
23056
  throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
22940
23057
  }
23058
+ this.invalidateCache();
22941
23059
  return { deleted: true, beanId };
22942
23060
  }
23061
+ async bulkCreate(beans, defaultParent) {
23062
+ const results = [];
23063
+ for (const item of beans) {
23064
+ try {
23065
+ const bean = await this.create({
23066
+ ...item,
23067
+ parent: item.parent ?? defaultParent
23068
+ });
23069
+ results.push({ bean });
23070
+ } catch (error48) {
23071
+ results.push({ error: error48.message });
23072
+ }
23073
+ }
23074
+ return results;
23075
+ }
23076
+ async bulkUpdate(beans, defaultParent) {
23077
+ const results = [];
23078
+ for (const { beanId, ...updates } of beans) {
23079
+ try {
23080
+ const resolvedParent = updates.parent ?? (updates.clearParent ? void 0 : defaultParent);
23081
+ const bean = await this.update(beanId, { ...updates, parent: resolvedParent });
23082
+ results.push({ beanId, bean });
23083
+ } catch (error48) {
23084
+ results.push({ beanId, error: error48.message });
23085
+ }
23086
+ }
23087
+ return results;
23088
+ }
22943
23089
  async openConfig() {
22944
23090
  const configPath = (0, import_node_path2.join)(this.workspaceRoot, ".beans.yml");
22945
23091
  const content = await (0, import_promises.readFile)(configPath, "utf8");
22946
23092
  return { configPath, content };
22947
23093
  }
23094
+ async primeInstructions() {
23095
+ const { stdout } = await execFileAsync(this.cliPath, ["prime"], {
23096
+ cwd: this.workspaceRoot,
23097
+ env: this.getSafeEnv(),
23098
+ maxBuffer: 10 * 1024 * 1024,
23099
+ timeout: 3e4
23100
+ });
23101
+ return stdout.trim();
23102
+ }
23103
+ async writeInstructions(instructions) {
23104
+ const instructionsPath = (0, import_node_path2.join)(this.workspaceRoot, ".github", "instructions", "beans-prime.instructions.md");
23105
+ await (0, import_promises.mkdir)((0, import_node_path2.dirname)(instructionsPath), { recursive: true });
23106
+ await (0, import_promises.writeFile)(instructionsPath, instructions, "utf8");
23107
+ return instructionsPath;
23108
+ }
22948
23109
  async graphqlSchema() {
22949
23110
  const { stdout } = await execFileAsync(this.cliPath, ["graphql", "--schema"], {
22950
23111
  cwd: this.workspaceRoot,
@@ -22983,6 +23144,120 @@ Output: ${stdout.slice(0, 1e3)}`
22983
23144
  linesReturned: ringBuffer.length
22984
23145
  };
22985
23146
  }
23147
+ /**
23148
+ * Split a YAML scalar value from any trailing inline comment.
23149
+ * Understands single-quoted and double-quoted YAML strings so it won't
23150
+ * mistake a `#` inside a quoted value for a comment delimiter.
23151
+ */
23152
+ splitYamlInlineComment(value) {
23153
+ let inSingle = false;
23154
+ let inDouble = false;
23155
+ for (let i = 0; i < value.length; i += 1) {
23156
+ const char = value[i];
23157
+ if (inSingle) {
23158
+ if (char === "'") {
23159
+ if (value[i + 1] === "'") {
23160
+ i += 1;
23161
+ } else {
23162
+ inSingle = false;
23163
+ }
23164
+ }
23165
+ continue;
23166
+ }
23167
+ if (inDouble) {
23168
+ if (char === "\\") {
23169
+ i += 1;
23170
+ continue;
23171
+ }
23172
+ if (char === '"') {
23173
+ inDouble = false;
23174
+ }
23175
+ continue;
23176
+ }
23177
+ if (char === "'") {
23178
+ inSingle = true;
23179
+ continue;
23180
+ }
23181
+ if (char === '"') {
23182
+ inDouble = true;
23183
+ continue;
23184
+ }
23185
+ if (char === "#" && i > 0 && /\s/.test(value[i - 1])) {
23186
+ const valuePart = value.slice(0, i).trimEnd();
23187
+ return {
23188
+ valuePart,
23189
+ commentPart: value.slice(valuePart.length)
23190
+ };
23191
+ }
23192
+ }
23193
+ return { valuePart: value, commentPart: "" };
23194
+ }
23195
+ /** Returns true when `value` looks like a YAML block scalar indicator (`>`, `|`, `>-`, `|-`, etc.) */
23196
+ isYamlBlockScalarIndicator(value) {
23197
+ return /^[>|][+-]?[0-9]*$/.test(value) || /^[>|][0-9]*[+-]?$/.test(value);
23198
+ }
23199
+ /** Escape a plain string for use inside a YAML double-quoted scalar. */
23200
+ escapeForYamlDoubleQuoted(value) {
23201
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
23202
+ }
23203
+ /**
23204
+ * Normalise a raw YAML title value to a double-quoted scalar.
23205
+ * Handles: empty, already double-quoted, single-quoted (unescaping `''`),
23206
+ * block-scalar indicators, and plain unquoted values.
23207
+ */
23208
+ normalizeFrontmatterTitleValue(value) {
23209
+ const trimmed = value.trim();
23210
+ if (trimmed === "") {
23211
+ return '""';
23212
+ }
23213
+ if (this.isYamlBlockScalarIndicator(trimmed)) {
23214
+ return value;
23215
+ }
23216
+ if (/^"(?:[^"\\]|\\[\s\S])*"$/.test(trimmed)) {
23217
+ return trimmed;
23218
+ }
23219
+ if (/^'(?:[^']|'')*'$/.test(trimmed)) {
23220
+ const inner = trimmed.slice(1, -1).replace(/''/g, "'");
23221
+ return `"${this.escapeForYamlDoubleQuoted(inner)}"`;
23222
+ }
23223
+ return `"${this.escapeForYamlDoubleQuoted(trimmed)}"`;
23224
+ }
23225
+ /**
23226
+ * Ensure every `title:` line in YAML frontmatter is double-quoted.
23227
+ * Handles already-quoted (single or double), multi-word, and special-char values.
23228
+ * Preserves inline comments and handles both LF and CRLF line endings.
23229
+ */
23230
+ quoteFrontmatterTitles(content) {
23231
+ const crlfOpen = content.startsWith("---\r\n");
23232
+ const lfOpen = content.startsWith("---\n");
23233
+ if (!crlfOpen && !lfOpen) {
23234
+ return content;
23235
+ }
23236
+ const eol = crlfOpen ? "\r\n" : "\n";
23237
+ const openEnd = `---${eol}`.length;
23238
+ const closeMarker = `${eol}---`;
23239
+ const closeIdx = content.indexOf(closeMarker, openEnd);
23240
+ if (closeIdx === -1) {
23241
+ return content;
23242
+ }
23243
+ const frontmatter = content.slice(openEnd, closeIdx);
23244
+ const rest = content.slice(closeIdx);
23245
+ const lines = frontmatter.split(eol);
23246
+ const fixedLines = lines.map((line) => {
23247
+ if (!line.startsWith("title:")) {
23248
+ return line;
23249
+ }
23250
+ const colonIdx = line.indexOf(":");
23251
+ const afterColon = line.slice(colonIdx + 1);
23252
+ const leadingSpace = afterColon.length - afterColon.trimStart().length;
23253
+ const raw = afterColon.trimStart();
23254
+ const { valuePart, commentPart } = this.splitYamlInlineComment(raw);
23255
+ const normalized = this.normalizeFrontmatterTitleValue(valuePart);
23256
+ const prefix = `title:${" ".repeat(Math.max(1, leadingSpace))}`;
23257
+ return `${prefix}${normalized}${commentPart}`;
23258
+ });
23259
+ return `---${eol}${fixedLines.join(eol)}${rest}`;
23260
+ }
22986
23261
  async readBeanFile(relativePath) {
22987
23262
  const absolutePath = this.resolveBeanFilePath(relativePath);
22988
23263
  const content = await (0, import_promises.readFile)(absolutePath, "utf8");
@@ -22990,20 +23265,22 @@ Output: ${stdout.slice(0, 1e3)}`
22990
23265
  }
22991
23266
  async editBeanFile(relativePath, content) {
22992
23267
  const absolutePath = this.resolveBeanFilePath(relativePath);
23268
+ const fixed = this.quoteFrontmatterTitles(content);
22993
23269
  await (0, import_promises.mkdir)((0, import_node_path2.dirname)(absolutePath), { recursive: true });
22994
- await (0, import_promises.writeFile)(absolutePath, content, "utf8");
22995
- return { path: absolutePath, bytes: Buffer.byteLength(content, "utf8") };
23270
+ await (0, import_promises.writeFile)(absolutePath, fixed, "utf8");
23271
+ return { path: absolutePath, bytes: Buffer.byteLength(fixed, "utf8") };
22996
23272
  }
22997
23273
  async createBeanFile(relativePath, content, options) {
22998
23274
  const absolutePath = this.resolveBeanFilePath(relativePath);
23275
+ const fixed = this.quoteFrontmatterTitles(content);
22999
23276
  await (0, import_promises.mkdir)((0, import_node_path2.dirname)(absolutePath), { recursive: true });
23000
- await (0, import_promises.writeFile)(absolutePath, content, {
23277
+ await (0, import_promises.writeFile)(absolutePath, fixed, {
23001
23278
  encoding: "utf8",
23002
23279
  flag: options?.overwrite ? "w" : "wx"
23003
23280
  });
23004
23281
  return {
23005
23282
  path: absolutePath,
23006
- bytes: Buffer.byteLength(content, "utf8"),
23283
+ bytes: Buffer.byteLength(fixed, "utf8"),
23007
23284
  created: true
23008
23285
  };
23009
23286
  }
@@ -23016,7 +23293,7 @@ Output: ${stdout.slice(0, 1e3)}`
23016
23293
  }
23017
23294
  });
23018
23295
 
23019
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
23296
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
23020
23297
  function deserializeMessage(line) {
23021
23298
  return JSONRPCMessageSchema.parse(JSON.parse(line));
23022
23299
  }
@@ -23025,7 +23302,7 @@ function serializeMessage(message) {
23025
23302
  }
23026
23303
  var ReadBuffer;
23027
23304
  var init_stdio = __esm({
23028
- "node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js"() {
23305
+ "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js"() {
23029
23306
  "use strict";
23030
23307
  init_types();
23031
23308
  ReadBuffer = class {
@@ -23051,14 +23328,14 @@ var init_stdio = __esm({
23051
23328
  }
23052
23329
  });
23053
23330
 
23054
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
23331
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
23055
23332
  var stdio_exports = {};
23056
23333
  __export(stdio_exports, {
23057
23334
  StdioServerTransport: () => StdioServerTransport
23058
23335
  });
23059
23336
  var import_node_process, StdioServerTransport;
23060
23337
  var init_stdio2 = __esm({
23061
- "node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js"() {
23338
+ "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js"() {
23062
23339
  "use strict";
23063
23340
  import_node_process = __toESM(require("process"), 1);
23064
23341
  init_stdio();
@@ -27066,7 +27343,7 @@ init_core2();
27066
27343
  // node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/mini/coerce.js
27067
27344
  init_core2();
27068
27345
 
27069
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
27346
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
27070
27347
  function isZ4Schema(s) {
27071
27348
  const schema = s;
27072
27349
  return !!schema._zod;
@@ -27210,10 +27487,10 @@ function getLiteralValue(schema) {
27210
27487
  return void 0;
27211
27488
  }
27212
27489
 
27213
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
27490
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
27214
27491
  init_types();
27215
27492
 
27216
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
27493
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
27217
27494
  function isTerminal(status) {
27218
27495
  return status === "completed" || status === "failed" || status === "cancelled";
27219
27496
  }
@@ -28502,7 +28779,7 @@ var zodToJsonSchema = (schema, options) => {
28502
28779
  return combined;
28503
28780
  };
28504
28781
 
28505
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
28782
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
28506
28783
  function mapMiniTarget(t) {
28507
28784
  if (!t)
28508
28785
  return "draft-7";
@@ -28544,7 +28821,7 @@ function parseWithCompat(schema, data) {
28544
28821
  return result.data;
28545
28822
  }
28546
28823
 
28547
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
28824
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
28548
28825
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 6e4;
28549
28826
  var Protocol = class {
28550
28827
  constructor(_options) {
@@ -28756,6 +29033,10 @@ var Protocol = class {
28756
29033
  this._progressHandlers.clear();
28757
29034
  this._taskProgressTokens.clear();
28758
29035
  this._pendingDebouncedNotifications.clear();
29036
+ for (const info of this._timeoutInfo.values()) {
29037
+ clearTimeout(info.timeoutId);
29038
+ }
29039
+ this._timeoutInfo.clear();
28759
29040
  for (const controller of this._requestHandlerAbortControllers.values()) {
28760
29041
  controller.abort();
28761
29042
  }
@@ -28886,7 +29167,9 @@ var Protocol = class {
28886
29167
  await capturedTransport?.send(errorResponse);
28887
29168
  }
28888
29169
  }).catch((error48) => this._onerror(new Error(`Failed to send response: ${error48}`))).finally(() => {
28889
- this._requestHandlerAbortControllers.delete(request.id);
29170
+ if (this._requestHandlerAbortControllers.get(request.id) === abortController) {
29171
+ this._requestHandlerAbortControllers.delete(request.id);
29172
+ }
28890
29173
  });
28891
29174
  }
28892
29175
  _onprogress(notification) {
@@ -29492,10 +29775,10 @@ function mergeCapabilities(base, additional) {
29492
29775
  return result;
29493
29776
  }
29494
29777
 
29495
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
29778
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
29496
29779
  init_types();
29497
29780
 
29498
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
29781
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
29499
29782
  var import_ajv = __toESM(require_ajv(), 1);
29500
29783
  var import_ajv_formats = __toESM(require_dist(), 1);
29501
29784
  function createDefaultAjvInstance() {
@@ -29563,7 +29846,7 @@ var AjvJsonSchemaValidator = class {
29563
29846
  }
29564
29847
  };
29565
29848
 
29566
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
29849
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
29567
29850
  init_types();
29568
29851
  var ExperimentalServerTasks = class {
29569
29852
  constructor(_server) {
@@ -29777,7 +30060,7 @@ var ExperimentalServerTasks = class {
29777
30060
  }
29778
30061
  };
29779
30062
 
29780
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
30063
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
29781
30064
  function assertToolsCallTaskCapability(requests, method, entityName) {
29782
30065
  if (!requests) {
29783
30066
  throw new Error(`${entityName} does not support task creation (required for ${method})`);
@@ -29812,7 +30095,7 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
29812
30095
  }
29813
30096
  }
29814
30097
 
29815
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
30098
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
29816
30099
  var Server = class extends Protocol {
29817
30100
  /**
29818
30101
  * Initializes this server with the given name and version information.
@@ -30192,10 +30475,10 @@ var Server = class extends Protocol {
30192
30475
  }
30193
30476
  };
30194
30477
 
30195
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30478
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30196
30479
  init_types();
30197
30480
 
30198
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
30481
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
30199
30482
  var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
30200
30483
  function isCompletable(schema) {
30201
30484
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
@@ -30209,7 +30492,7 @@ var McpZodTypeKind;
30209
30492
  McpZodTypeKind2["Completable"] = "McpCompletable";
30210
30493
  })(McpZodTypeKind || (McpZodTypeKind = {}));
30211
30494
 
30212
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
30495
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
30213
30496
  var TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
30214
30497
  function validateToolName(name) {
30215
30498
  const warnings = [];
@@ -30267,7 +30550,7 @@ function validateAndWarnToolName(name) {
30267
30550
  return result.isValid;
30268
30551
  }
30269
30552
 
30270
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
30553
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
30271
30554
  var ExperimentalMcpServerTasks = class {
30272
30555
  constructor(_mcpServer) {
30273
30556
  this._mcpServer = _mcpServer;
@@ -30286,7 +30569,7 @@ var ExperimentalMcpServerTasks = class {
30286
30569
  init_external();
30287
30570
  init_external();
30288
30571
 
30289
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30572
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30290
30573
  var McpServer = class {
30291
30574
  constructor(serverInfo, options) {
30292
30575
  this._registeredResources = {};
@@ -30910,6 +31193,9 @@ var McpServer = class {
30910
31193
  annotations = rest.shift();
30911
31194
  }
30912
31195
  } else if (typeof firstArg === "object" && firstArg !== null) {
31196
+ if (Object.values(firstArg).some((v) => typeof v === "object" && v !== null)) {
31197
+ throw new Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);
31198
+ }
30913
31199
  annotations = rest.shift();
30914
31200
  }
30915
31201
  }
@@ -31028,6 +31314,9 @@ function getZodSchemaObject(schema) {
31028
31314
  if (isZodRawShapeCompat(schema)) {
31029
31315
  return objectFromShape(schema);
31030
31316
  }
31317
+ if (!isZodSchemaInstance(schema)) {
31318
+ throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");
31319
+ }
31031
31320
  return schema;
31032
31321
  }
31033
31322
  function promptArgumentsFromSchema(schema) {
@@ -31072,6 +31361,94 @@ var EMPTY_COMPLETION_RESULT = {
31072
31361
  }
31073
31362
  };
31074
31363
 
31364
+ // src/server/BeansMcpServer.ts
31365
+ var import_node_child_process2 = require("child_process");
31366
+ var import_node_util2 = require("util");
31367
+
31368
+ // package.json
31369
+ var package_default = {
31370
+ name: "@selfagency/beans-mcp",
31371
+ version: "0.5.0",
31372
+ private: false,
31373
+ description: "MCP (Model Context Protocol) server for Beans issue tracker",
31374
+ author: {
31375
+ name: "Daniel Sieradski",
31376
+ email: "daniel@self.agency",
31377
+ url: "https://self.agency"
31378
+ },
31379
+ homepage: "https://github.com/selfagency/beans-mcp",
31380
+ bugs: {
31381
+ url: "https://github.com/selfagency/beans-mcp/issues"
31382
+ },
31383
+ repository: {
31384
+ type: "git",
31385
+ url: "git+https://github.com/selfagency/beans-mcp.git"
31386
+ },
31387
+ mcpName: "io.github.selfagency/beans-mcp",
31388
+ keywords: [
31389
+ "beans",
31390
+ "mcp",
31391
+ "model-context-protocol",
31392
+ "issue-tracker",
31393
+ "ai"
31394
+ ],
31395
+ license: "MIT",
31396
+ type: "module",
31397
+ exports: {
31398
+ ".": {
31399
+ types: "./dist/index.d.ts",
31400
+ import: "./dist/index.js",
31401
+ require: "./dist/index.cjs"
31402
+ }
31403
+ },
31404
+ main: "./dist/index.cjs",
31405
+ module: "./dist/index.js",
31406
+ types: "./dist/index.d.ts",
31407
+ bin: {
31408
+ "beans-mcp": "dist/beans-mcp-server.cjs"
31409
+ },
31410
+ scripts: {
31411
+ build: "tsup",
31412
+ format: "oxfmt",
31413
+ "lint:fix": "oxlint --fix",
31414
+ lint: "oxlint",
31415
+ postbuild: "node ./scripts/write-dist-package.js",
31416
+ prepare: "husky",
31417
+ release: "zx ./scripts/release.js",
31418
+ "test:coverage": "vitest run --coverage",
31419
+ "test:watch": "vitest",
31420
+ test: "vitest run",
31421
+ "type-check": "tsc --noEmit"
31422
+ },
31423
+ devDependencies: {
31424
+ "@modelcontextprotocol/sdk": "^1.29.0",
31425
+ "@octokit/rest": "^22.0.1",
31426
+ "@types/node": "25.5.2",
31427
+ "@vitest/coverage-v8": "^4.1.2",
31428
+ "@vitest/ui": "4.1.2",
31429
+ husky: "^9.1.7",
31430
+ "lint-staged": "^16.4.0",
31431
+ ora: "^9.3.0",
31432
+ oxfmt: "^0.43.0",
31433
+ oxlint: "^1.58.0",
31434
+ "oxlint-tsgolint": "^0.20.0",
31435
+ tsup: "8.5.1",
31436
+ typescript: "6.0.2",
31437
+ vitest: "4.1.2",
31438
+ zod: "4.3.6",
31439
+ zx: "^8.8.5"
31440
+ },
31441
+ engines: {
31442
+ node: ">=18"
31443
+ },
31444
+ "lint-staged": {
31445
+ "src/**/*.ts": [
31446
+ "pnpm run lint:fix",
31447
+ "pnpm run format"
31448
+ ]
31449
+ }
31450
+ };
31451
+
31075
31452
  // src/internal/queryHelpers.ts
31076
31453
  function sortBeansInternal(beans, mode) {
31077
31454
  const sorted = [...beans];
@@ -31127,15 +31504,23 @@ async function handleQueryOperation(backend, params) {
31127
31504
  const { operation, mode, statuses, types, search, tags, writeToWorkspaceInstructions, includeClosed } = params;
31128
31505
  if (operation === "llm_context") {
31129
31506
  const graphqlSchema = typeof backend.graphqlSchema === "function" ? await backend.graphqlSchema() : "";
31130
- const instructionsPath = writeToWorkspaceInstructions && typeof backend.writeInstructions === "function" ? await backend.writeInstructions("") : null;
31507
+ let generatedInstructions = "";
31508
+ if (typeof backend.primeInstructions === "function") {
31509
+ try {
31510
+ generatedInstructions = await backend.primeInstructions();
31511
+ } catch {
31512
+ generatedInstructions = "";
31513
+ }
31514
+ }
31515
+ const instructionsPath = writeToWorkspaceInstructions && typeof backend.writeInstructions === "function" ? await backend.writeInstructions(generatedInstructions) : null;
31131
31516
  return {
31132
31517
  content: [
31133
31518
  {
31134
31519
  type: "text",
31135
- text: JSON.stringify({ graphqlSchema, generatedInstructions: "", instructionsPath }, null, 2)
31520
+ text: JSON.stringify({ graphqlSchema, generatedInstructions, instructionsPath }, null, 2)
31136
31521
  }
31137
31522
  ],
31138
- structuredContent: { graphqlSchema, generatedInstructions: "", instructionsPath }
31523
+ structuredContent: { graphqlSchema, generatedInstructions, instructionsPath }
31139
31524
  };
31140
31525
  }
31141
31526
  if (operation === "open_config") {
@@ -31181,6 +31566,31 @@ async function handleQueryOperation(backend, params) {
31181
31566
  structuredContent: { query: search, count: beans2.length, beans: beans2 }
31182
31567
  };
31183
31568
  }
31569
+ if (operation === "ready") {
31570
+ const allBeans = await backend.list();
31571
+ const byId = new Map(allBeans.map((bean) => [bean.id, bean]));
31572
+ const candidates = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
31573
+ const readyBeans = candidates.filter((bean) => {
31574
+ if (bean.status !== "todo") {
31575
+ return false;
31576
+ }
31577
+ const blockedBy = bean.blockedByIds || [];
31578
+ if (blockedBy.length === 0) {
31579
+ return true;
31580
+ }
31581
+ return blockedBy.every((blockerId) => {
31582
+ const blocker = byId.get(blockerId);
31583
+ if (!blocker) {
31584
+ return false;
31585
+ }
31586
+ return blocker.status === "completed" || blocker.status === "scrapped";
31587
+ });
31588
+ });
31589
+ return {
31590
+ content: [{ type: "text", text: JSON.stringify({ count: readyBeans.length, beans: readyBeans }, null, 2) }],
31591
+ structuredContent: { count: readyBeans.length, beans: readyBeans }
31592
+ };
31593
+ }
31184
31594
  const beans = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
31185
31595
  const sorted = sortBeansInternal(beans, mode ?? "status-priority-type-title");
31186
31596
  return {
@@ -31199,92 +31609,45 @@ var MAX_PATH_LENGTH = 1024;
31199
31609
 
31200
31610
  // src/server/BeansMcpServer.ts
31201
31611
  init_utils();
31202
-
31203
- // package.json
31204
- var package_default = {
31205
- name: "@selfagency/beans-mcp",
31206
- version: "0.1.4",
31207
- private: false,
31208
- description: "MCP (Model Context Protocol) server for Beans issue tracker",
31209
- author: {
31210
- name: "Daniel Sieradski",
31211
- email: "daniel@self.agency",
31212
- url: "https://self.agency"
31213
- },
31214
- homepage: "https://github.com/hmans/beans",
31215
- bugs: {
31216
- url: "https://github.com/selfagency/beans-mcp/issues"
31217
- },
31218
- repository: {
31219
- type: "git",
31220
- url: "git+https://github.com/selfagency/beans-mcp.git"
31221
- },
31222
- mcpName: "io.github.selfagency/beans-mcp",
31223
- keywords: [
31224
- "beans",
31225
- "mcp",
31226
- "model-context-protocol",
31227
- "issue-tracker",
31228
- "ai"
31229
- ],
31230
- license: "MIT",
31231
- type: "module",
31232
- exports: {
31233
- ".": {
31234
- types: "./dist/index.d.ts",
31235
- import: "./dist/index.js",
31236
- require: "./dist/index.cjs"
31612
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
31613
+ var PACKAGE_VERSION = package_default.version ?? "0.0.0-dev";
31614
+ function getSafeCliEnv(env) {
31615
+ const whitelist = ["PATH", "HOME", "USER", "LANG", "LC_ALL", "LC_CTYPE", "SHELL"];
31616
+ const safeEnv = {};
31617
+ for (const key of whitelist) {
31618
+ if (env[key]) {
31619
+ safeEnv[key] = env[key];
31237
31620
  }
31238
- },
31239
- main: "./dist/index.cjs",
31240
- module: "./dist/index.js",
31241
- types: "./dist/index.d.ts",
31242
- bin: {
31243
- "beans-mcp": "dist/beans-mcp-server.cjs"
31244
- },
31245
- scripts: {
31246
- build: "tsup",
31247
- format: "oxfmt",
31248
- "lint:fix": "oxlint --fix",
31249
- lint: "oxlint",
31250
- postbuild: "node ./scripts/write-dist-package.js",
31251
- prepare: "husky",
31252
- release: "zx ./scripts/release.js",
31253
- "test:coverage": "vitest run --coverage",
31254
- "test:watch": "vitest",
31255
- test: "vitest run",
31256
- "type-check": "tsc --noEmit"
31257
- },
31258
- devDependencies: {
31259
- "@modelcontextprotocol/sdk": "^1.27.1",
31260
- "@octokit/rest": "^22.0.1",
31261
- "@types/node": "^20.19.0",
31262
- "@vitest/coverage-v8": "^4.0.18",
31263
- "@vitest/ui": "4.0.18",
31264
- husky: "^9.1.7",
31265
- "lint-staged": "^16.2.7",
31266
- ora: "^9.3.0",
31267
- oxfmt: "^0.35.0",
31268
- oxlint: "^1.50.0",
31269
- "oxlint-tsgolint": "^0.15.0",
31270
- tsup: "8.5.1",
31271
- typescript: "^5.9.3",
31272
- vitest: "4.0.18",
31273
- zod: "4.3.6",
31274
- zx: "^8.8.5"
31275
- },
31276
- engines: {
31277
- node: ">=18"
31278
- },
31279
- "lint-staged": {
31280
- "src/**/*.ts": [
31281
- "pnpm run lint:fix",
31282
- "pnpm run format"
31283
- ]
31284
31621
  }
31285
- };
31286
-
31287
- // src/server/BeansMcpServer.ts
31622
+ for (const key in env) {
31623
+ if (key.startsWith("BEANS_")) {
31624
+ safeEnv[key] = env[key];
31625
+ }
31626
+ }
31627
+ return safeEnv;
31628
+ }
31629
+ function extractVersionFromOutput(output) {
31630
+ const trimmed = output.trim();
31631
+ if (!trimmed) {
31632
+ return null;
31633
+ }
31634
+ const match = trimmed.match(/(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
31635
+ return match?.[1] ?? null;
31636
+ }
31637
+ async function detectBeansCliVersion(cliPath, workspaceRoot) {
31638
+ try {
31639
+ const { stdout, stderr } = await execFileAsync2(cliPath, ["version"], {
31640
+ cwd: workspaceRoot,
31641
+ env: getSafeCliEnv(process.env),
31642
+ maxBuffer: 1024 * 1024,
31643
+ timeout: 5e3
31644
+ });
31645
+ return extractVersionFromOutput(`${stdout}
31646
+ ${stderr}`);
31647
+ } catch {
31648
+ return null;
31649
+ }
31650
+ }
31288
31651
  async function getBeanById(backend, beanId) {
31289
31652
  try {
31290
31653
  const beans = await backend.list();
@@ -31304,7 +31667,40 @@ function initHandler(backend) {
31304
31667
  };
31305
31668
  }
31306
31669
  function viewHandler(backend) {
31307
- return async ({ beanId }) => makeTextAndStructured({ bean: await getBeanById(backend, beanId) });
31670
+ return async ({ beanId, beanIds }) => {
31671
+ const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
31672
+ if (ids.length === 0) {
31673
+ throw new Error("Either beanId or beanIds must be provided");
31674
+ }
31675
+ if (ids.length === 1) {
31676
+ const bean = await getBeanById(backend, ids[0]);
31677
+ return makeTextAndStructured({ bean });
31678
+ }
31679
+ const beans = await backend.list();
31680
+ const byId = new Map(beans.map((b) => [b.id, b]));
31681
+ const found = ids.map((id) => byId.get(id)).filter(Boolean);
31682
+ const missingBeanIds = ids.filter((id) => !byId.has(id));
31683
+ return makeTextAndStructured({
31684
+ beans: found,
31685
+ missingBeanIds,
31686
+ count: found.length,
31687
+ requestedCount: ids.length
31688
+ });
31689
+ };
31690
+ }
31691
+ async function checkVersionCompatibility(cliPath, workspaceRoot, detector) {
31692
+ const detectedBeansVersion = await detector(cliPath, workspaceRoot);
31693
+ if (!detectedBeansVersion) {
31694
+ console.error(
31695
+ `[beans-mcp] warning: unable to determine Beans CLI version from \`${cliPath}\`; proceeding without version compatibility checks.`
31696
+ );
31697
+ return;
31698
+ }
31699
+ if (detectedBeansVersion !== PACKAGE_VERSION) {
31700
+ console.error(
31701
+ `[beans-mcp] warning: version mismatch detected (beans=${detectedBeansVersion}, beans-mcp=${PACKAGE_VERSION}); continuing startup.`
31702
+ );
31703
+ }
31308
31704
  }
31309
31705
  function createHandler(backend) {
31310
31706
  return async (input) => makeTextAndStructured({ bean: await backend.create(input) });
@@ -31340,17 +31736,82 @@ function updateHandler(backend) {
31340
31736
  clearParent: input.clearParent,
31341
31737
  blocking: input.blocking,
31342
31738
  blockedBy: input.blockedBy,
31343
- body: input.body
31739
+ body: input.body,
31740
+ bodyAppend: input.bodyAppend,
31741
+ bodyReplace: input.bodyReplace,
31742
+ ifMatch: input.ifMatch
31344
31743
  })
31345
31744
  });
31346
31745
  }
31347
31746
  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");
31747
+ return async ({ beanId, beanIds, force }) => {
31748
+ const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
31749
+ if (ids.length === 0) {
31750
+ throw new Error("Either beanId or beanIds must be provided");
31352
31751
  }
31353
- return makeTextAndStructured(await backend.delete(beanId));
31752
+ if (ids.length === 1) {
31753
+ const bean = await getBeanById(backend, ids[0]);
31754
+ if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31755
+ throw new Error("Only draft and scrapped beans are deletable unless force=true");
31756
+ }
31757
+ return makeTextAndStructured(await backend.delete(ids[0]));
31758
+ }
31759
+ const beans = await backend.list();
31760
+ const byId = new Map(beans.map((b) => [b.id, b]));
31761
+ const results = [];
31762
+ for (const id of ids) {
31763
+ const bean = byId.get(id);
31764
+ if (!bean) {
31765
+ results.push({ beanId: id, deleted: false, error: "Bean not found" });
31766
+ continue;
31767
+ }
31768
+ if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31769
+ results.push({
31770
+ beanId: id,
31771
+ deleted: false,
31772
+ error: "Only draft and scrapped beans are deletable unless force=true"
31773
+ });
31774
+ continue;
31775
+ }
31776
+ try {
31777
+ await backend.delete(id);
31778
+ results.push({ beanId: id, deleted: true });
31779
+ } catch (error48) {
31780
+ results.push({
31781
+ beanId: id,
31782
+ deleted: false,
31783
+ error: error48.message
31784
+ });
31785
+ }
31786
+ }
31787
+ return makeTextAndStructured({
31788
+ results,
31789
+ requestedCount: ids.length,
31790
+ deletedCount: results.filter((r) => r.deleted).length,
31791
+ failedCount: results.filter((r) => !r.deleted).length
31792
+ });
31793
+ };
31794
+ }
31795
+ function bulkCreateHandler(backend) {
31796
+ return async (input) => {
31797
+ const results = await backend.bulkCreate(input.beans, input.parent);
31798
+ return makeTextAndStructured({
31799
+ results,
31800
+ requestedCount: input.beans.length,
31801
+ successCount: results.filter((r) => r.bean).length,
31802
+ failedCount: results.filter((r) => r.error).length
31803
+ });
31804
+ };
31805
+ }
31806
+ function bulkUpdateHandler(backend) {
31807
+ return async (input) => {
31808
+ const results = await backend.bulkUpdate(input.beans, input.parent);
31809
+ return makeTextAndStructured({
31810
+ results,
31811
+ requestedCount: input.beans.length,
31812
+ successCount: results.filter((r) => r.bean).length,
31813
+ failedCount: results.filter((r) => r.error).length
31814
+ });
31354
31815
  };
31355
31816
  }
31356
31817
  function queryHandler(backend) {
@@ -31411,7 +31872,12 @@ function registerTools(server, backend) {
31411
31872
  {
31412
31873
  title: "View Bean",
31413
31874
  description: "Fetch full bean details by ID.",
31414
- inputSchema: external_exports3.object({ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH) }),
31875
+ inputSchema: external_exports3.object({
31876
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31877
+ beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional()
31878
+ }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31879
+ message: "Either beanId or beanIds must be provided"
31880
+ }),
31415
31881
  annotations: {
31416
31882
  readOnlyHint: true,
31417
31883
  destructiveHint: false,
@@ -31431,7 +31897,8 @@ function registerTools(server, backend) {
31431
31897
  type: external_exports3.string().min(1).max(MAX_METADATA_LENGTH),
31432
31898
  status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31433
31899
  priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31434
- description: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31900
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Body markdown content"),
31901
+ description: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Deprecated alias for body"),
31435
31902
  parent: external_exports3.string().max(MAX_ID_LENGTH).optional()
31436
31903
  }),
31437
31904
  annotations: {
@@ -31500,8 +31967,21 @@ function registerTools(server, backend) {
31500
31967
  clearParent: external_exports3.boolean().optional(),
31501
31968
  blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31502
31969
  blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31503
- body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional()
31504
- }),
31970
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31971
+ bodyAppend: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31972
+ bodyReplace: external_exports3.array(
31973
+ external_exports3.object({
31974
+ old: external_exports3.string().max(MAX_DESCRIPTION_LENGTH),
31975
+ new: external_exports3.string().max(MAX_DESCRIPTION_LENGTH)
31976
+ })
31977
+ ).optional(),
31978
+ ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
31979
+ }).refine(
31980
+ (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
31981
+ {
31982
+ message: "body cannot be combined with bodyAppend/bodyReplace"
31983
+ }
31984
+ ),
31505
31985
  annotations: {
31506
31986
  readOnlyHint: false,
31507
31987
  destructiveHint: false,
@@ -31517,8 +31997,11 @@ function registerTools(server, backend) {
31517
31997
  title: "Delete Bean",
31518
31998
  description: "Delete a bean (intended for draft/scrapped beans).",
31519
31999
  inputSchema: external_exports3.object({
31520
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32000
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
32001
+ beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional(),
31521
32002
  force: external_exports3.boolean().default(false)
32003
+ }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
32004
+ message: "Either beanId or beanIds must be provided"
31522
32005
  }),
31523
32006
  annotations: {
31524
32007
  readOnlyHint: false,
@@ -31529,13 +32012,75 @@ function registerTools(server, backend) {
31529
32012
  },
31530
32013
  deleteHandler(backend)
31531
32014
  );
32015
+ const beanCreateItemSchema = external_exports3.object({
32016
+ title: external_exports3.string().min(1).max(MAX_TITLE_LENGTH),
32017
+ type: external_exports3.string().min(1).max(MAX_METADATA_LENGTH),
32018
+ status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32019
+ priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32020
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Body markdown content"),
32021
+ description: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Deprecated alias for body"),
32022
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Override the top-level parent for this item")
32023
+ });
32024
+ server.registerTool(
32025
+ "beans_bulk_create",
32026
+ {
32027
+ title: "Bulk Create Beans",
32028
+ description: "Create multiple beans in one call. Optionally assign all of them (or a subset) to a shared parent.",
32029
+ inputSchema: external_exports3.object({
32030
+ beans: external_exports3.array(beanCreateItemSchema).min(1),
32031
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Default parent ID applied to any bean that does not specify its own parent")
32032
+ }),
32033
+ annotations: {
32034
+ readOnlyHint: false,
32035
+ destructiveHint: false,
32036
+ idempotentHint: false,
32037
+ openWorldHint: false
32038
+ }
32039
+ },
32040
+ bulkCreateHandler(backend)
32041
+ );
32042
+ const beanUpdateItemSchema = external_exports3.object({
32043
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32044
+ status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32045
+ type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32046
+ priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32047
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Override the top-level parent for this item"),
32048
+ clearParent: external_exports3.boolean().optional(),
32049
+ blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
32050
+ blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
32051
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32052
+ bodyAppend: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32053
+ bodyReplace: external_exports3.array(external_exports3.object({ old: external_exports3.string().max(MAX_DESCRIPTION_LENGTH), new: external_exports3.string().max(MAX_DESCRIPTION_LENGTH) })).optional(),
32054
+ ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
32055
+ }).refine(
32056
+ (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
32057
+ { message: "body cannot be combined with bodyAppend/bodyReplace" }
32058
+ );
32059
+ server.registerTool(
32060
+ "beans_bulk_update",
32061
+ {
32062
+ title: "Bulk Update Beans",
32063
+ description: "Update multiple beans in one call. Optionally assign all of them (or a subset) to a shared parent.",
32064
+ inputSchema: external_exports3.object({
32065
+ beans: external_exports3.array(beanUpdateItemSchema).min(1),
32066
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Default parent ID applied to any bean that does not specify its own parent")
32067
+ }),
32068
+ annotations: {
32069
+ readOnlyHint: false,
32070
+ destructiveHint: false,
32071
+ idempotentHint: false,
32072
+ openWorldHint: false
32073
+ }
32074
+ },
32075
+ bulkUpdateHandler(backend)
32076
+ );
31532
32077
  server.registerTool(
31533
32078
  "beans_query",
31534
32079
  {
31535
32080
  title: "Query Beans",
31536
32081
  description: "Unified query tool for refresh, filter, search, and sort operations.",
31537
32082
  inputSchema: external_exports3.object({
31538
- operation: external_exports3.enum(["refresh", "filter", "search", "sort", "llm_context", "open_config"]).default("refresh"),
32083
+ operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config"]).default("refresh"),
31539
32084
  mode: external_exports3.enum(["status-priority-type-title", "updated", "created", "id"]).optional(),
31540
32085
  statuses: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
31541
32086
  types: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
@@ -31614,9 +32159,21 @@ var MutableBackend = class {
31614
32159
  delete(id) {
31615
32160
  return this.inner.delete(id);
31616
32161
  }
32162
+ bulkCreate(beans, defaultParent) {
32163
+ return this.inner.bulkCreate(beans, defaultParent);
32164
+ }
32165
+ bulkUpdate(beans, defaultParent) {
32166
+ return this.inner.bulkUpdate(beans, defaultParent);
32167
+ }
31617
32168
  openConfig() {
31618
32169
  return this.inner.openConfig();
31619
32170
  }
32171
+ primeInstructions() {
32172
+ return this.inner.primeInstructions?.() ?? Promise.resolve("");
32173
+ }
32174
+ writeInstructions(instructions) {
32175
+ return this.inner.writeInstructions?.(instructions) ?? Promise.resolve(null);
32176
+ }
31620
32177
  graphqlSchema() {
31621
32178
  return this.inner.graphqlSchema();
31622
32179
  }
@@ -31654,7 +32211,7 @@ async function createBeansMcpServer(opts) {
31654
32211
  const backend = opts.backend || new BeansCliBackend2(opts.workspaceRoot, opts.cliPath || "beans", opts.logDir);
31655
32212
  const server = new McpServer({
31656
32213
  name: opts.name || "beans-mcp-server",
31657
- version: opts.version || "0.1.0"
32214
+ version: opts.version || PACKAGE_VERSION
31658
32215
  });
31659
32216
  registerTools(server, backend);
31660
32217
  return { server, backend };
@@ -31726,17 +32283,17 @@ function parseCliArgs(argv) {
31726
32283
  }
31727
32284
  return { workspaceRoot, workspaceExplicit, cliPath, port, logDir };
31728
32285
  }
31729
- async function startBeansMcpServer(argv, _resolveRoots) {
32286
+ async function startBeansMcpServer(argv, _resolveRoots, _detectBeansVersion) {
31730
32287
  const { BeansCliBackend: BeansCliBackend2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
31731
32288
  const { StdioServerTransport: StdioServerTransport2 } = await Promise.resolve().then(() => (init_stdio2(), stdio_exports));
31732
32289
  const { workspaceRoot, workspaceExplicit, cliPath, port, logDir } = parseCliArgs(argv);
32290
+ let effectiveWorkspaceRoot = workspaceRoot;
31733
32291
  process.env.BEANS_VSCODE_MCP_PORT = String(port);
31734
32292
  process.env.BEANS_MCP_PORT = String(port);
31735
32293
  try {
31736
- const version2 = package_default.version ?? "0.0.0-dev";
31737
32294
  const workspaceLabel = workspaceExplicit ? workspaceRoot : "(auto from roots)";
31738
32295
  console.error(
31739
- `[beans-mcp] v${version2} starting (port=${port}, workspace=${workspaceLabel}, cli=${cliPath}, logDir=${logDir})`
32296
+ `[beans-mcp] v${PACKAGE_VERSION} starting (port=${port}, workspace=${workspaceLabel}, cli=${cliPath}, logDir=${logDir})`
31740
32297
  );
31741
32298
  } catch {
31742
32299
  }
@@ -31753,13 +32310,17 @@ async function startBeansMcpServer(argv, _resolveRoots) {
31753
32310
  const resolver = _resolveRoots ?? resolveWorkspaceFromRoots;
31754
32311
  const rootPath = await resolver(server);
31755
32312
  if (rootPath) {
31756
- mutable.setInner(new BeansCliBackend2(rootPath, cliPath));
32313
+ mutable.setInner(new BeansCliBackend2(rootPath, cliPath, logDir));
32314
+ effectiveWorkspaceRoot = rootPath;
31757
32315
  try {
31758
32316
  console.error(`[beans-mcp] workspace resolved from roots: ${rootPath}`);
31759
32317
  } catch {
31760
32318
  }
31761
32319
  }
31762
32320
  }
32321
+ const beansVersionDetector = _detectBeansVersion ?? detectBeansCliVersion;
32322
+ void checkVersionCompatibility(cliPath, effectiveWorkspaceRoot, beansVersionDetector).catch(() => {
32323
+ });
31763
32324
  }
31764
32325
 
31765
32326
  // src/cli.ts