@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.
package/index.cjs CHANGED
@@ -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();
@@ -27083,7 +27360,7 @@ init_core2();
27083
27360
  // node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/mini/coerce.js
27084
27361
  init_core2();
27085
27362
 
27086
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
27363
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
27087
27364
  function isZ4Schema(s) {
27088
27365
  const schema = s;
27089
27366
  return !!schema._zod;
@@ -27227,10 +27504,10 @@ function getLiteralValue(schema) {
27227
27504
  return void 0;
27228
27505
  }
27229
27506
 
27230
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
27507
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
27231
27508
  init_types();
27232
27509
 
27233
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
27510
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
27234
27511
  function isTerminal(status) {
27235
27512
  return status === "completed" || status === "failed" || status === "cancelled";
27236
27513
  }
@@ -28519,7 +28796,7 @@ var zodToJsonSchema = (schema, options) => {
28519
28796
  return combined;
28520
28797
  };
28521
28798
 
28522
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
28799
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
28523
28800
  function mapMiniTarget(t) {
28524
28801
  if (!t)
28525
28802
  return "draft-7";
@@ -28561,7 +28838,7 @@ function parseWithCompat(schema, data) {
28561
28838
  return result.data;
28562
28839
  }
28563
28840
 
28564
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
28841
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
28565
28842
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 6e4;
28566
28843
  var Protocol = class {
28567
28844
  constructor(_options) {
@@ -28773,6 +29050,10 @@ var Protocol = class {
28773
29050
  this._progressHandlers.clear();
28774
29051
  this._taskProgressTokens.clear();
28775
29052
  this._pendingDebouncedNotifications.clear();
29053
+ for (const info of this._timeoutInfo.values()) {
29054
+ clearTimeout(info.timeoutId);
29055
+ }
29056
+ this._timeoutInfo.clear();
28776
29057
  for (const controller of this._requestHandlerAbortControllers.values()) {
28777
29058
  controller.abort();
28778
29059
  }
@@ -28903,7 +29184,9 @@ var Protocol = class {
28903
29184
  await capturedTransport?.send(errorResponse);
28904
29185
  }
28905
29186
  }).catch((error48) => this._onerror(new Error(`Failed to send response: ${error48}`))).finally(() => {
28906
- this._requestHandlerAbortControllers.delete(request.id);
29187
+ if (this._requestHandlerAbortControllers.get(request.id) === abortController) {
29188
+ this._requestHandlerAbortControllers.delete(request.id);
29189
+ }
28907
29190
  });
28908
29191
  }
28909
29192
  _onprogress(notification) {
@@ -29509,10 +29792,10 @@ function mergeCapabilities(base, additional) {
29509
29792
  return result;
29510
29793
  }
29511
29794
 
29512
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
29795
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
29513
29796
  init_types();
29514
29797
 
29515
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
29798
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
29516
29799
  var import_ajv = __toESM(require_ajv(), 1);
29517
29800
  var import_ajv_formats = __toESM(require_dist(), 1);
29518
29801
  function createDefaultAjvInstance() {
@@ -29580,7 +29863,7 @@ var AjvJsonSchemaValidator = class {
29580
29863
  }
29581
29864
  };
29582
29865
 
29583
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
29866
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
29584
29867
  init_types();
29585
29868
  var ExperimentalServerTasks = class {
29586
29869
  constructor(_server) {
@@ -29794,7 +30077,7 @@ var ExperimentalServerTasks = class {
29794
30077
  }
29795
30078
  };
29796
30079
 
29797
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
30080
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
29798
30081
  function assertToolsCallTaskCapability(requests, method, entityName) {
29799
30082
  if (!requests) {
29800
30083
  throw new Error(`${entityName} does not support task creation (required for ${method})`);
@@ -29829,7 +30112,7 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
29829
30112
  }
29830
30113
  }
29831
30114
 
29832
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
30115
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
29833
30116
  var Server = class extends Protocol {
29834
30117
  /**
29835
30118
  * Initializes this server with the given name and version information.
@@ -30209,10 +30492,10 @@ var Server = class extends Protocol {
30209
30492
  }
30210
30493
  };
30211
30494
 
30212
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30495
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30213
30496
  init_types();
30214
30497
 
30215
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
30498
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
30216
30499
  var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
30217
30500
  function isCompletable(schema) {
30218
30501
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
@@ -30226,7 +30509,7 @@ var McpZodTypeKind;
30226
30509
  McpZodTypeKind2["Completable"] = "McpCompletable";
30227
30510
  })(McpZodTypeKind || (McpZodTypeKind = {}));
30228
30511
 
30229
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
30512
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
30230
30513
  var TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
30231
30514
  function validateToolName(name) {
30232
30515
  const warnings = [];
@@ -30284,7 +30567,7 @@ function validateAndWarnToolName(name) {
30284
30567
  return result.isValid;
30285
30568
  }
30286
30569
 
30287
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
30570
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
30288
30571
  var ExperimentalMcpServerTasks = class {
30289
30572
  constructor(_mcpServer) {
30290
30573
  this._mcpServer = _mcpServer;
@@ -30303,7 +30586,7 @@ var ExperimentalMcpServerTasks = class {
30303
30586
  init_external();
30304
30587
  init_external();
30305
30588
 
30306
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30589
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30307
30590
  var McpServer = class {
30308
30591
  constructor(serverInfo, options) {
30309
30592
  this._registeredResources = {};
@@ -30927,6 +31210,9 @@ var McpServer = class {
30927
31210
  annotations = rest.shift();
30928
31211
  }
30929
31212
  } else if (typeof firstArg === "object" && firstArg !== null) {
31213
+ if (Object.values(firstArg).some((v) => typeof v === "object" && v !== null)) {
31214
+ throw new Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);
31215
+ }
30930
31216
  annotations = rest.shift();
30931
31217
  }
30932
31218
  }
@@ -31045,6 +31331,9 @@ function getZodSchemaObject(schema) {
31045
31331
  if (isZodRawShapeCompat(schema)) {
31046
31332
  return objectFromShape(schema);
31047
31333
  }
31334
+ if (!isZodSchemaInstance(schema)) {
31335
+ throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");
31336
+ }
31048
31337
  return schema;
31049
31338
  }
31050
31339
  function promptArgumentsFromSchema(schema) {
@@ -31089,6 +31378,94 @@ var EMPTY_COMPLETION_RESULT = {
31089
31378
  }
31090
31379
  };
31091
31380
 
31381
+ // src/server/BeansMcpServer.ts
31382
+ var import_node_child_process2 = require("child_process");
31383
+ var import_node_util2 = require("util");
31384
+
31385
+ // package.json
31386
+ var package_default = {
31387
+ name: "@selfagency/beans-mcp",
31388
+ version: "0.5.0",
31389
+ private: false,
31390
+ description: "MCP (Model Context Protocol) server for Beans issue tracker",
31391
+ author: {
31392
+ name: "Daniel Sieradski",
31393
+ email: "daniel@self.agency",
31394
+ url: "https://self.agency"
31395
+ },
31396
+ homepage: "https://github.com/selfagency/beans-mcp",
31397
+ bugs: {
31398
+ url: "https://github.com/selfagency/beans-mcp/issues"
31399
+ },
31400
+ repository: {
31401
+ type: "git",
31402
+ url: "git+https://github.com/selfagency/beans-mcp.git"
31403
+ },
31404
+ mcpName: "io.github.selfagency/beans-mcp",
31405
+ keywords: [
31406
+ "beans",
31407
+ "mcp",
31408
+ "model-context-protocol",
31409
+ "issue-tracker",
31410
+ "ai"
31411
+ ],
31412
+ license: "MIT",
31413
+ type: "module",
31414
+ exports: {
31415
+ ".": {
31416
+ types: "./dist/index.d.ts",
31417
+ import: "./dist/index.js",
31418
+ require: "./dist/index.cjs"
31419
+ }
31420
+ },
31421
+ main: "./dist/index.cjs",
31422
+ module: "./dist/index.js",
31423
+ types: "./dist/index.d.ts",
31424
+ bin: {
31425
+ "beans-mcp": "dist/beans-mcp-server.cjs"
31426
+ },
31427
+ scripts: {
31428
+ build: "tsup",
31429
+ format: "oxfmt",
31430
+ "lint:fix": "oxlint --fix",
31431
+ lint: "oxlint",
31432
+ postbuild: "node ./scripts/write-dist-package.js",
31433
+ prepare: "husky",
31434
+ release: "zx ./scripts/release.js",
31435
+ "test:coverage": "vitest run --coverage",
31436
+ "test:watch": "vitest",
31437
+ test: "vitest run",
31438
+ "type-check": "tsc --noEmit"
31439
+ },
31440
+ devDependencies: {
31441
+ "@modelcontextprotocol/sdk": "^1.29.0",
31442
+ "@octokit/rest": "^22.0.1",
31443
+ "@types/node": "25.5.2",
31444
+ "@vitest/coverage-v8": "^4.1.2",
31445
+ "@vitest/ui": "4.1.2",
31446
+ husky: "^9.1.7",
31447
+ "lint-staged": "^16.4.0",
31448
+ ora: "^9.3.0",
31449
+ oxfmt: "^0.43.0",
31450
+ oxlint: "^1.58.0",
31451
+ "oxlint-tsgolint": "^0.20.0",
31452
+ tsup: "8.5.1",
31453
+ typescript: "6.0.2",
31454
+ vitest: "4.1.2",
31455
+ zod: "4.3.6",
31456
+ zx: "^8.8.5"
31457
+ },
31458
+ engines: {
31459
+ node: ">=18"
31460
+ },
31461
+ "lint-staged": {
31462
+ "src/**/*.ts": [
31463
+ "pnpm run lint:fix",
31464
+ "pnpm run format"
31465
+ ]
31466
+ }
31467
+ };
31468
+
31092
31469
  // src/internal/queryHelpers.ts
31093
31470
  function sortBeansInternal(beans, mode) {
31094
31471
  const sorted = [...beans];
@@ -31144,15 +31521,23 @@ async function handleQueryOperation(backend, params) {
31144
31521
  const { operation, mode, statuses, types, search, tags, writeToWorkspaceInstructions, includeClosed } = params;
31145
31522
  if (operation === "llm_context") {
31146
31523
  const graphqlSchema = typeof backend.graphqlSchema === "function" ? await backend.graphqlSchema() : "";
31147
- const instructionsPath = writeToWorkspaceInstructions && typeof backend.writeInstructions === "function" ? await backend.writeInstructions("") : null;
31524
+ let generatedInstructions = "";
31525
+ if (typeof backend.primeInstructions === "function") {
31526
+ try {
31527
+ generatedInstructions = await backend.primeInstructions();
31528
+ } catch {
31529
+ generatedInstructions = "";
31530
+ }
31531
+ }
31532
+ const instructionsPath = writeToWorkspaceInstructions && typeof backend.writeInstructions === "function" ? await backend.writeInstructions(generatedInstructions) : null;
31148
31533
  return {
31149
31534
  content: [
31150
31535
  {
31151
31536
  type: "text",
31152
- text: JSON.stringify({ graphqlSchema, generatedInstructions: "", instructionsPath }, null, 2)
31537
+ text: JSON.stringify({ graphqlSchema, generatedInstructions, instructionsPath }, null, 2)
31153
31538
  }
31154
31539
  ],
31155
- structuredContent: { graphqlSchema, generatedInstructions: "", instructionsPath }
31540
+ structuredContent: { graphqlSchema, generatedInstructions, instructionsPath }
31156
31541
  };
31157
31542
  }
31158
31543
  if (operation === "open_config") {
@@ -31198,6 +31583,31 @@ async function handleQueryOperation(backend, params) {
31198
31583
  structuredContent: { query: search, count: beans2.length, beans: beans2 }
31199
31584
  };
31200
31585
  }
31586
+ if (operation === "ready") {
31587
+ const allBeans = await backend.list();
31588
+ const byId = new Map(allBeans.map((bean) => [bean.id, bean]));
31589
+ const candidates = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
31590
+ const readyBeans = candidates.filter((bean) => {
31591
+ if (bean.status !== "todo") {
31592
+ return false;
31593
+ }
31594
+ const blockedBy = bean.blockedByIds || [];
31595
+ if (blockedBy.length === 0) {
31596
+ return true;
31597
+ }
31598
+ return blockedBy.every((blockerId) => {
31599
+ const blocker = byId.get(blockerId);
31600
+ if (!blocker) {
31601
+ return false;
31602
+ }
31603
+ return blocker.status === "completed" || blocker.status === "scrapped";
31604
+ });
31605
+ });
31606
+ return {
31607
+ content: [{ type: "text", text: JSON.stringify({ count: readyBeans.length, beans: readyBeans }, null, 2) }],
31608
+ structuredContent: { count: readyBeans.length, beans: readyBeans }
31609
+ };
31610
+ }
31201
31611
  const beans = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
31202
31612
  const sorted = sortBeansInternal(beans, mode ?? "status-priority-type-title");
31203
31613
  return {
@@ -31216,92 +31626,45 @@ var MAX_PATH_LENGTH = 1024;
31216
31626
 
31217
31627
  // src/server/BeansMcpServer.ts
31218
31628
  init_utils();
31219
-
31220
- // package.json
31221
- var package_default = {
31222
- name: "@selfagency/beans-mcp",
31223
- version: "0.1.4",
31224
- private: false,
31225
- description: "MCP (Model Context Protocol) server for Beans issue tracker",
31226
- author: {
31227
- name: "Daniel Sieradski",
31228
- email: "daniel@self.agency",
31229
- url: "https://self.agency"
31230
- },
31231
- homepage: "https://github.com/hmans/beans",
31232
- bugs: {
31233
- url: "https://github.com/selfagency/beans-mcp/issues"
31234
- },
31235
- repository: {
31236
- type: "git",
31237
- url: "git+https://github.com/selfagency/beans-mcp.git"
31238
- },
31239
- mcpName: "io.github.selfagency/beans-mcp",
31240
- keywords: [
31241
- "beans",
31242
- "mcp",
31243
- "model-context-protocol",
31244
- "issue-tracker",
31245
- "ai"
31246
- ],
31247
- license: "MIT",
31248
- type: "module",
31249
- exports: {
31250
- ".": {
31251
- types: "./dist/index.d.ts",
31252
- import: "./dist/index.js",
31253
- require: "./dist/index.cjs"
31629
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
31630
+ var PACKAGE_VERSION = package_default.version ?? "0.0.0-dev";
31631
+ function getSafeCliEnv(env) {
31632
+ const whitelist = ["PATH", "HOME", "USER", "LANG", "LC_ALL", "LC_CTYPE", "SHELL"];
31633
+ const safeEnv = {};
31634
+ for (const key of whitelist) {
31635
+ if (env[key]) {
31636
+ safeEnv[key] = env[key];
31254
31637
  }
31255
- },
31256
- main: "./dist/index.cjs",
31257
- module: "./dist/index.js",
31258
- types: "./dist/index.d.ts",
31259
- bin: {
31260
- "beans-mcp": "dist/beans-mcp-server.cjs"
31261
- },
31262
- scripts: {
31263
- build: "tsup",
31264
- format: "oxfmt",
31265
- "lint:fix": "oxlint --fix",
31266
- lint: "oxlint",
31267
- postbuild: "node ./scripts/write-dist-package.js",
31268
- prepare: "husky",
31269
- release: "zx ./scripts/release.js",
31270
- "test:coverage": "vitest run --coverage",
31271
- "test:watch": "vitest",
31272
- test: "vitest run",
31273
- "type-check": "tsc --noEmit"
31274
- },
31275
- devDependencies: {
31276
- "@modelcontextprotocol/sdk": "^1.27.1",
31277
- "@octokit/rest": "^22.0.1",
31278
- "@types/node": "^20.19.0",
31279
- "@vitest/coverage-v8": "^4.0.18",
31280
- "@vitest/ui": "4.0.18",
31281
- husky: "^9.1.7",
31282
- "lint-staged": "^16.2.7",
31283
- ora: "^9.3.0",
31284
- oxfmt: "^0.35.0",
31285
- oxlint: "^1.50.0",
31286
- "oxlint-tsgolint": "^0.15.0",
31287
- tsup: "8.5.1",
31288
- typescript: "^5.9.3",
31289
- vitest: "4.0.18",
31290
- zod: "4.3.6",
31291
- zx: "^8.8.5"
31292
- },
31293
- engines: {
31294
- node: ">=18"
31295
- },
31296
- "lint-staged": {
31297
- "src/**/*.ts": [
31298
- "pnpm run lint:fix",
31299
- "pnpm run format"
31300
- ]
31301
31638
  }
31302
- };
31303
-
31304
- // src/server/BeansMcpServer.ts
31639
+ for (const key in env) {
31640
+ if (key.startsWith("BEANS_")) {
31641
+ safeEnv[key] = env[key];
31642
+ }
31643
+ }
31644
+ return safeEnv;
31645
+ }
31646
+ function extractVersionFromOutput(output) {
31647
+ const trimmed = output.trim();
31648
+ if (!trimmed) {
31649
+ return null;
31650
+ }
31651
+ const match = trimmed.match(/(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
31652
+ return match?.[1] ?? null;
31653
+ }
31654
+ async function detectBeansCliVersion(cliPath, workspaceRoot) {
31655
+ try {
31656
+ const { stdout, stderr } = await execFileAsync2(cliPath, ["version"], {
31657
+ cwd: workspaceRoot,
31658
+ env: getSafeCliEnv(process.env),
31659
+ maxBuffer: 1024 * 1024,
31660
+ timeout: 5e3
31661
+ });
31662
+ return extractVersionFromOutput(`${stdout}
31663
+ ${stderr}`);
31664
+ } catch {
31665
+ return null;
31666
+ }
31667
+ }
31305
31668
  async function getBeanById(backend, beanId) {
31306
31669
  try {
31307
31670
  const beans = await backend.list();
@@ -31321,7 +31684,40 @@ function initHandler(backend) {
31321
31684
  };
31322
31685
  }
31323
31686
  function viewHandler(backend) {
31324
- return async ({ beanId }) => makeTextAndStructured({ bean: await getBeanById(backend, beanId) });
31687
+ return async ({ beanId, beanIds }) => {
31688
+ const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
31689
+ if (ids.length === 0) {
31690
+ throw new Error("Either beanId or beanIds must be provided");
31691
+ }
31692
+ if (ids.length === 1) {
31693
+ const bean = await getBeanById(backend, ids[0]);
31694
+ return makeTextAndStructured({ bean });
31695
+ }
31696
+ const beans = await backend.list();
31697
+ const byId = new Map(beans.map((b) => [b.id, b]));
31698
+ const found = ids.map((id) => byId.get(id)).filter(Boolean);
31699
+ const missingBeanIds = ids.filter((id) => !byId.has(id));
31700
+ return makeTextAndStructured({
31701
+ beans: found,
31702
+ missingBeanIds,
31703
+ count: found.length,
31704
+ requestedCount: ids.length
31705
+ });
31706
+ };
31707
+ }
31708
+ async function checkVersionCompatibility(cliPath, workspaceRoot, detector) {
31709
+ const detectedBeansVersion = await detector(cliPath, workspaceRoot);
31710
+ if (!detectedBeansVersion) {
31711
+ console.error(
31712
+ `[beans-mcp] warning: unable to determine Beans CLI version from \`${cliPath}\`; proceeding without version compatibility checks.`
31713
+ );
31714
+ return;
31715
+ }
31716
+ if (detectedBeansVersion !== PACKAGE_VERSION) {
31717
+ console.error(
31718
+ `[beans-mcp] warning: version mismatch detected (beans=${detectedBeansVersion}, beans-mcp=${PACKAGE_VERSION}); continuing startup.`
31719
+ );
31720
+ }
31325
31721
  }
31326
31722
  function createHandler(backend) {
31327
31723
  return async (input) => makeTextAndStructured({ bean: await backend.create(input) });
@@ -31357,17 +31753,82 @@ function updateHandler(backend) {
31357
31753
  clearParent: input.clearParent,
31358
31754
  blocking: input.blocking,
31359
31755
  blockedBy: input.blockedBy,
31360
- body: input.body
31756
+ body: input.body,
31757
+ bodyAppend: input.bodyAppend,
31758
+ bodyReplace: input.bodyReplace,
31759
+ ifMatch: input.ifMatch
31361
31760
  })
31362
31761
  });
31363
31762
  }
31364
31763
  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");
31764
+ return async ({ beanId, beanIds, force }) => {
31765
+ const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
31766
+ if (ids.length === 0) {
31767
+ throw new Error("Either beanId or beanIds must be provided");
31369
31768
  }
31370
- return makeTextAndStructured(await backend.delete(beanId));
31769
+ if (ids.length === 1) {
31770
+ const bean = await getBeanById(backend, ids[0]);
31771
+ if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31772
+ throw new Error("Only draft and scrapped beans are deletable unless force=true");
31773
+ }
31774
+ return makeTextAndStructured(await backend.delete(ids[0]));
31775
+ }
31776
+ const beans = await backend.list();
31777
+ const byId = new Map(beans.map((b) => [b.id, b]));
31778
+ const results = [];
31779
+ for (const id of ids) {
31780
+ const bean = byId.get(id);
31781
+ if (!bean) {
31782
+ results.push({ beanId: id, deleted: false, error: "Bean not found" });
31783
+ continue;
31784
+ }
31785
+ if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31786
+ results.push({
31787
+ beanId: id,
31788
+ deleted: false,
31789
+ error: "Only draft and scrapped beans are deletable unless force=true"
31790
+ });
31791
+ continue;
31792
+ }
31793
+ try {
31794
+ await backend.delete(id);
31795
+ results.push({ beanId: id, deleted: true });
31796
+ } catch (error48) {
31797
+ results.push({
31798
+ beanId: id,
31799
+ deleted: false,
31800
+ error: error48.message
31801
+ });
31802
+ }
31803
+ }
31804
+ return makeTextAndStructured({
31805
+ results,
31806
+ requestedCount: ids.length,
31807
+ deletedCount: results.filter((r) => r.deleted).length,
31808
+ failedCount: results.filter((r) => !r.deleted).length
31809
+ });
31810
+ };
31811
+ }
31812
+ function bulkCreateHandler(backend) {
31813
+ return async (input) => {
31814
+ const results = await backend.bulkCreate(input.beans, input.parent);
31815
+ return makeTextAndStructured({
31816
+ results,
31817
+ requestedCount: input.beans.length,
31818
+ successCount: results.filter((r) => r.bean).length,
31819
+ failedCount: results.filter((r) => r.error).length
31820
+ });
31821
+ };
31822
+ }
31823
+ function bulkUpdateHandler(backend) {
31824
+ return async (input) => {
31825
+ const results = await backend.bulkUpdate(input.beans, input.parent);
31826
+ return makeTextAndStructured({
31827
+ results,
31828
+ requestedCount: input.beans.length,
31829
+ successCount: results.filter((r) => r.bean).length,
31830
+ failedCount: results.filter((r) => r.error).length
31831
+ });
31371
31832
  };
31372
31833
  }
31373
31834
  function queryHandler(backend) {
@@ -31428,7 +31889,12 @@ function registerTools(server, backend) {
31428
31889
  {
31429
31890
  title: "View Bean",
31430
31891
  description: "Fetch full bean details by ID.",
31431
- inputSchema: external_exports3.object({ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH) }),
31892
+ inputSchema: external_exports3.object({
31893
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31894
+ beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional()
31895
+ }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31896
+ message: "Either beanId or beanIds must be provided"
31897
+ }),
31432
31898
  annotations: {
31433
31899
  readOnlyHint: true,
31434
31900
  destructiveHint: false,
@@ -31448,7 +31914,8 @@ function registerTools(server, backend) {
31448
31914
  type: external_exports3.string().min(1).max(MAX_METADATA_LENGTH),
31449
31915
  status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31450
31916
  priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31451
- description: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31917
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Body markdown content"),
31918
+ description: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Deprecated alias for body"),
31452
31919
  parent: external_exports3.string().max(MAX_ID_LENGTH).optional()
31453
31920
  }),
31454
31921
  annotations: {
@@ -31517,8 +31984,21 @@ function registerTools(server, backend) {
31517
31984
  clearParent: external_exports3.boolean().optional(),
31518
31985
  blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31519
31986
  blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31520
- body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional()
31521
- }),
31987
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31988
+ bodyAppend: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31989
+ bodyReplace: external_exports3.array(
31990
+ external_exports3.object({
31991
+ old: external_exports3.string().max(MAX_DESCRIPTION_LENGTH),
31992
+ new: external_exports3.string().max(MAX_DESCRIPTION_LENGTH)
31993
+ })
31994
+ ).optional(),
31995
+ ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
31996
+ }).refine(
31997
+ (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
31998
+ {
31999
+ message: "body cannot be combined with bodyAppend/bodyReplace"
32000
+ }
32001
+ ),
31522
32002
  annotations: {
31523
32003
  readOnlyHint: false,
31524
32004
  destructiveHint: false,
@@ -31534,8 +32014,11 @@ function registerTools(server, backend) {
31534
32014
  title: "Delete Bean",
31535
32015
  description: "Delete a bean (intended for draft/scrapped beans).",
31536
32016
  inputSchema: external_exports3.object({
31537
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32017
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
32018
+ beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional(),
31538
32019
  force: external_exports3.boolean().default(false)
32020
+ }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
32021
+ message: "Either beanId or beanIds must be provided"
31539
32022
  }),
31540
32023
  annotations: {
31541
32024
  readOnlyHint: false,
@@ -31546,13 +32029,75 @@ function registerTools(server, backend) {
31546
32029
  },
31547
32030
  deleteHandler(backend)
31548
32031
  );
32032
+ const beanCreateItemSchema = external_exports3.object({
32033
+ title: external_exports3.string().min(1).max(MAX_TITLE_LENGTH),
32034
+ type: external_exports3.string().min(1).max(MAX_METADATA_LENGTH),
32035
+ status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32036
+ priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32037
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Body markdown content"),
32038
+ description: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Deprecated alias for body"),
32039
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Override the top-level parent for this item")
32040
+ });
32041
+ server.registerTool(
32042
+ "beans_bulk_create",
32043
+ {
32044
+ title: "Bulk Create Beans",
32045
+ description: "Create multiple beans in one call. Optionally assign all of them (or a subset) to a shared parent.",
32046
+ inputSchema: external_exports3.object({
32047
+ beans: external_exports3.array(beanCreateItemSchema).min(1),
32048
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Default parent ID applied to any bean that does not specify its own parent")
32049
+ }),
32050
+ annotations: {
32051
+ readOnlyHint: false,
32052
+ destructiveHint: false,
32053
+ idempotentHint: false,
32054
+ openWorldHint: false
32055
+ }
32056
+ },
32057
+ bulkCreateHandler(backend)
32058
+ );
32059
+ const beanUpdateItemSchema = external_exports3.object({
32060
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32061
+ status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32062
+ type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32063
+ priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32064
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Override the top-level parent for this item"),
32065
+ clearParent: external_exports3.boolean().optional(),
32066
+ blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
32067
+ blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
32068
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32069
+ bodyAppend: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32070
+ bodyReplace: external_exports3.array(external_exports3.object({ old: external_exports3.string().max(MAX_DESCRIPTION_LENGTH), new: external_exports3.string().max(MAX_DESCRIPTION_LENGTH) })).optional(),
32071
+ ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
32072
+ }).refine(
32073
+ (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
32074
+ { message: "body cannot be combined with bodyAppend/bodyReplace" }
32075
+ );
32076
+ server.registerTool(
32077
+ "beans_bulk_update",
32078
+ {
32079
+ title: "Bulk Update Beans",
32080
+ description: "Update multiple beans in one call. Optionally assign all of them (or a subset) to a shared parent.",
32081
+ inputSchema: external_exports3.object({
32082
+ beans: external_exports3.array(beanUpdateItemSchema).min(1),
32083
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Default parent ID applied to any bean that does not specify its own parent")
32084
+ }),
32085
+ annotations: {
32086
+ readOnlyHint: false,
32087
+ destructiveHint: false,
32088
+ idempotentHint: false,
32089
+ openWorldHint: false
32090
+ }
32091
+ },
32092
+ bulkUpdateHandler(backend)
32093
+ );
31549
32094
  server.registerTool(
31550
32095
  "beans_query",
31551
32096
  {
31552
32097
  title: "Query Beans",
31553
32098
  description: "Unified query tool for refresh, filter, search, and sort operations.",
31554
32099
  inputSchema: external_exports3.object({
31555
- operation: external_exports3.enum(["refresh", "filter", "search", "sort", "llm_context", "open_config"]).default("refresh"),
32100
+ operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config"]).default("refresh"),
31556
32101
  mode: external_exports3.enum(["status-priority-type-title", "updated", "created", "id"]).optional(),
31557
32102
  statuses: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
31558
32103
  types: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
@@ -31631,9 +32176,21 @@ var MutableBackend = class {
31631
32176
  delete(id) {
31632
32177
  return this.inner.delete(id);
31633
32178
  }
32179
+ bulkCreate(beans, defaultParent) {
32180
+ return this.inner.bulkCreate(beans, defaultParent);
32181
+ }
32182
+ bulkUpdate(beans, defaultParent) {
32183
+ return this.inner.bulkUpdate(beans, defaultParent);
32184
+ }
31634
32185
  openConfig() {
31635
32186
  return this.inner.openConfig();
31636
32187
  }
32188
+ primeInstructions() {
32189
+ return this.inner.primeInstructions?.() ?? Promise.resolve("");
32190
+ }
32191
+ writeInstructions(instructions) {
32192
+ return this.inner.writeInstructions?.(instructions) ?? Promise.resolve(null);
32193
+ }
31637
32194
  graphqlSchema() {
31638
32195
  return this.inner.graphqlSchema();
31639
32196
  }
@@ -31671,7 +32228,7 @@ async function createBeansMcpServer(opts) {
31671
32228
  const backend = opts.backend || new BeansCliBackend2(opts.workspaceRoot, opts.cliPath || "beans", opts.logDir);
31672
32229
  const server = new McpServer({
31673
32230
  name: opts.name || "beans-mcp-server",
31674
- version: opts.version || "0.1.0"
32231
+ version: opts.version || PACKAGE_VERSION
31675
32232
  });
31676
32233
  registerTools(server, backend);
31677
32234
  return { server, backend };
@@ -31743,17 +32300,17 @@ function parseCliArgs(argv) {
31743
32300
  }
31744
32301
  return { workspaceRoot, workspaceExplicit, cliPath, port, logDir };
31745
32302
  }
31746
- async function startBeansMcpServer(argv, _resolveRoots) {
32303
+ async function startBeansMcpServer(argv, _resolveRoots, _detectBeansVersion) {
31747
32304
  const { BeansCliBackend: BeansCliBackend2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
31748
32305
  const { StdioServerTransport: StdioServerTransport2 } = await Promise.resolve().then(() => (init_stdio2(), stdio_exports));
31749
32306
  const { workspaceRoot, workspaceExplicit, cliPath, port, logDir } = parseCliArgs(argv);
32307
+ let effectiveWorkspaceRoot = workspaceRoot;
31750
32308
  process.env.BEANS_VSCODE_MCP_PORT = String(port);
31751
32309
  process.env.BEANS_MCP_PORT = String(port);
31752
32310
  try {
31753
- const version2 = package_default.version ?? "0.0.0-dev";
31754
32311
  const workspaceLabel = workspaceExplicit ? workspaceRoot : "(auto from roots)";
31755
32312
  console.error(
31756
- `[beans-mcp] v${version2} starting (port=${port}, workspace=${workspaceLabel}, cli=${cliPath}, logDir=${logDir})`
32313
+ `[beans-mcp] v${PACKAGE_VERSION} starting (port=${port}, workspace=${workspaceLabel}, cli=${cliPath}, logDir=${logDir})`
31757
32314
  );
31758
32315
  } catch {
31759
32316
  }
@@ -31770,13 +32327,17 @@ async function startBeansMcpServer(argv, _resolveRoots) {
31770
32327
  const resolver = _resolveRoots ?? resolveWorkspaceFromRoots;
31771
32328
  const rootPath = await resolver(server);
31772
32329
  if (rootPath) {
31773
- mutable.setInner(new BeansCliBackend2(rootPath, cliPath));
32330
+ mutable.setInner(new BeansCliBackend2(rootPath, cliPath, logDir));
32331
+ effectiveWorkspaceRoot = rootPath;
31774
32332
  try {
31775
32333
  console.error(`[beans-mcp] workspace resolved from roots: ${rootPath}`);
31776
32334
  } catch {
31777
32335
  }
31778
32336
  }
31779
32337
  }
32338
+ const beansVersionDetector = _detectBeansVersion ?? detectBeansCliVersion;
32339
+ void checkVersionCompatibility(cliPath, effectiveWorkspaceRoot, beansVersionDetector).catch(() => {
32340
+ });
31780
32341
  }
31781
32342
 
31782
32343
  // src/index.ts