@selfagency/beans-mcp 0.4.2 → 0.6.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.js CHANGED
@@ -14431,7 +14431,7 @@ var init_v4 = __esm({
14431
14431
  }
14432
14432
  });
14433
14433
 
14434
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
14434
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
14435
14435
  function assertCompleteRequestPrompt(request) {
14436
14436
  if (request.params.ref.type !== "ref/prompt") {
14437
14437
  throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`);
@@ -14446,7 +14446,7 @@ function assertCompleteRequestResourceTemplate(request) {
14446
14446
  }
14447
14447
  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;
14448
14448
  var init_types = __esm({
14449
- "node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js"() {
14449
+ "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js"() {
14450
14450
  "use strict";
14451
14451
  init_v4();
14452
14452
  LATEST_PROTOCOL_VERSION = "2025-11-25";
@@ -14458,10 +14458,9 @@ var init_types = __esm({
14458
14458
  CursorSchema = string2();
14459
14459
  TaskCreationParamsSchema = looseObject({
14460
14460
  /**
14461
- * Time in milliseconds to keep task results available after completion.
14462
- * If null, the task has unlimited lifetime until manually cleaned up.
14461
+ * Requested duration in milliseconds to retain task from creation.
14463
14462
  */
14464
- ttl: union([number2(), _null3()]).optional(),
14463
+ ttl: number2().optional(),
14465
14464
  /**
14466
14465
  * Time in milliseconds to wait between task status requests.
14467
14466
  */
@@ -14760,7 +14759,11 @@ var init_types = __esm({
14760
14759
  /**
14761
14760
  * Present if the client supports task creation.
14762
14761
  */
14763
- tasks: ClientTasksCapabilitySchema.optional()
14762
+ tasks: ClientTasksCapabilitySchema.optional(),
14763
+ /**
14764
+ * Extensions that the client supports. Keys are extension identifiers (vendor-prefix/extension-name).
14765
+ */
14766
+ extensions: record(string2(), AssertObjectSchema).optional()
14764
14767
  });
14765
14768
  InitializeRequestParamsSchema = BaseRequestParamsSchema.extend({
14766
14769
  /**
@@ -14821,7 +14824,11 @@ var init_types = __esm({
14821
14824
  /**
14822
14825
  * Present if the server supports task creation.
14823
14826
  */
14824
- tasks: ServerTasksCapabilitySchema.optional()
14827
+ tasks: ServerTasksCapabilitySchema.optional(),
14828
+ /**
14829
+ * Extensions that the server supports. Keys are extension identifiers (vendor-prefix/extension-name).
14830
+ */
14831
+ extensions: record(string2(), AssertObjectSchema).optional()
14825
14832
  });
14826
14833
  InitializeResultSchema = ResultSchema.extend({
14827
14834
  /**
@@ -15013,6 +15020,12 @@ var init_types = __esm({
15013
15020
  * The MIME type of this resource, if known.
15014
15021
  */
15015
15022
  mimeType: optional(string2()),
15023
+ /**
15024
+ * The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
15025
+ *
15026
+ * This can be used by Hosts to display file sizes and estimate context window usage.
15027
+ */
15028
+ size: optional(number2()),
15016
15029
  /**
15017
15030
  * Optional annotations for the client.
15018
15031
  */
@@ -22744,7 +22757,7 @@ var init_utils = __esm({
22744
22757
  });
22745
22758
 
22746
22759
  // src/internal/graphql.ts
22747
- var LIST_BEANS_QUERY, CREATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION_WITH_IF_MATCH, DELETE_BEAN_MUTATION;
22760
+ var LIST_BEANS_QUERY, CREATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION_WITH_IF_MATCH, DELETE_BEAN_MUTATION, LIST_BEANS_TIMESTAMPS_QUERY;
22748
22761
  var init_graphql = __esm({
22749
22762
  "src/internal/graphql.ts"() {
22750
22763
  "use strict";
@@ -22772,6 +22785,11 @@ var init_graphql = __esm({
22772
22785
  mutation($id: ID!) {
22773
22786
  deleteBean(id: $id)
22774
22787
  }
22788
+ `;
22789
+ LIST_BEANS_TIMESTAMPS_QUERY = `
22790
+ query {
22791
+ beans { id updatedAt }
22792
+ }
22775
22793
  `;
22776
22794
  }
22777
22795
  });
@@ -22783,7 +22801,7 @@ __export(backend_exports, {
22783
22801
  });
22784
22802
  import { execFile } from "child_process";
22785
22803
  import { createReadStream } from "fs";
22786
- import { mkdir, readFile, rm, writeFile } from "fs/promises";
22804
+ import { mkdir, readFile, realpath, rename, rm, writeFile } from "fs/promises";
22787
22805
  import { dirname, join, resolve as resolve2 } from "path";
22788
22806
  import { createInterface } from "readline";
22789
22807
  import { promisify } from "util";
@@ -22794,12 +22812,26 @@ var init_backend = __esm({
22794
22812
  init_graphql();
22795
22813
  init_utils();
22796
22814
  execFileAsync = promisify(execFile);
22797
- BeansCliBackend = class {
22815
+ BeansCliBackend = class _BeansCliBackend {
22798
22816
  constructor(workspaceRoot, cliPath, logDir) {
22799
22817
  this.workspaceRoot = workspaceRoot;
22800
22818
  this.cliPath = cliPath;
22801
22819
  this.logDir = logDir;
22802
22820
  }
22821
+ // ---------------------------------------------------------------------------
22822
+ // Cache
22823
+ // ---------------------------------------------------------------------------
22824
+ /** Full unfiltered records keyed by bean ID, stored under the fixed cache key `'all'`. */
22825
+ _cache = /* @__PURE__ */ new Map();
22826
+ /** Last time the unfiltered cache entry `'all'` was fetched (ms). */
22827
+ _cacheTime = /* @__PURE__ */ new Map();
22828
+ /** Short-circuit TTL: skip even the timestamp check within this window (ms). */
22829
+ static BURST_TTL_MS = 5e3;
22830
+ /** Invalidate the unfiltered list cache so the next call does a full fetch. */
22831
+ invalidateCache() {
22832
+ this._cache.delete("all");
22833
+ this._cacheTime.delete("all");
22834
+ }
22803
22835
  /**
22804
22836
  * Returns a safe environment for executing the Beans CLI,
22805
22837
  * whitelisting only necessary variables.
@@ -22823,7 +22855,7 @@ var init_backend = __esm({
22823
22855
  return resolve2(this.workspaceRoot, ".beans");
22824
22856
  }
22825
22857
  resolveBeanFilePath(relativePath) {
22826
- const cleaned = relativePath.trim().replace(/^\/+/, "");
22858
+ const cleaned = relativePath.trim().replace(/^\/+/, "").replace(/^\.beans(?:[\\/]|$)/, "");
22827
22859
  if (!cleaned) {
22828
22860
  throw new Error("Path is required");
22829
22861
  }
@@ -22870,6 +22902,26 @@ Output: ${stdout.slice(0, 1e3)}`
22870
22902
  });
22871
22903
  return { initialized: true };
22872
22904
  }
22905
+ async archive() {
22906
+ const { stdout } = await execFileAsync(this.cliPath, ["archive", "--json"], {
22907
+ cwd: this.workspaceRoot,
22908
+ env: this.getSafeEnv(),
22909
+ maxBuffer: 10 * 1024 * 1024,
22910
+ timeout: 3e4
22911
+ });
22912
+ this.invalidateCache();
22913
+ if (!stdout.trim()) {
22914
+ return { archived: true };
22915
+ }
22916
+ try {
22917
+ return JSON.parse(stdout);
22918
+ } catch {
22919
+ return { archived: true, output: stdout.trim() };
22920
+ }
22921
+ }
22922
+ async queryGraphql(query, variables) {
22923
+ return this.executeGraphQL(query, variables);
22924
+ }
22873
22925
  async list(options) {
22874
22926
  const filter = {};
22875
22927
  if (options?.status && options.status.length > 0) {
@@ -22881,10 +22933,48 @@ Output: ${stdout.slice(0, 1e3)}`
22881
22933
  if (options?.search) {
22882
22934
  filter.search = options.search;
22883
22935
  }
22936
+ const isCacheable = !filter.status && !filter.type && !filter.search;
22937
+ const cacheKey = "all";
22938
+ if (isCacheable) {
22939
+ const lastFetch = this._cacheTime.get(cacheKey) ?? 0;
22940
+ const cached2 = this._cache.get(cacheKey);
22941
+ const age = Date.now() - lastFetch;
22942
+ if (cached2 && age < _BeansCliBackend.BURST_TTL_MS) {
22943
+ return Array.from(cached2.values());
22944
+ }
22945
+ if (cached2) {
22946
+ try {
22947
+ const { data: tsData } = await this.executeGraphQL(
22948
+ LIST_BEANS_TIMESTAMPS_QUERY
22949
+ );
22950
+ const timestamps = tsData.beans;
22951
+ let dirty = timestamps.length !== cached2.size;
22952
+ if (!dirty) {
22953
+ for (const { id, updatedAt } of timestamps) {
22954
+ const existing = cached2.get(id);
22955
+ if (!existing || existing.updatedAt !== updatedAt) {
22956
+ dirty = true;
22957
+ break;
22958
+ }
22959
+ }
22960
+ }
22961
+ if (!dirty) {
22962
+ this._cacheTime.set(cacheKey, Date.now());
22963
+ return Array.from(cached2.values());
22964
+ }
22965
+ } catch {
22966
+ }
22967
+ }
22968
+ }
22884
22969
  const { data, errors } = await this.executeGraphQL(LIST_BEANS_QUERY, { filter });
22885
22970
  if (errors && errors.length > 0) {
22886
22971
  throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
22887
22972
  }
22973
+ if (isCacheable) {
22974
+ const byId = new Map(data.beans.map((b) => [b.id, b]));
22975
+ this._cache.set(cacheKey, byId);
22976
+ this._cacheTime.set(cacheKey, Date.now());
22977
+ }
22888
22978
  return data.beans;
22889
22979
  }
22890
22980
  async create(input) {
@@ -22893,7 +22983,7 @@ Output: ${stdout.slice(0, 1e3)}`
22893
22983
  type: input.type,
22894
22984
  status: input.status,
22895
22985
  priority: input.priority,
22896
- body: input.description,
22986
+ body: input.body ?? input.description,
22897
22987
  parent: input.parent
22898
22988
  };
22899
22989
  const { data, errors } = await this.executeGraphQL(CREATE_BEAN_MUTATION, {
@@ -22902,6 +22992,7 @@ Output: ${stdout.slice(0, 1e3)}`
22902
22992
  if (errors && errors.length > 0) {
22903
22993
  throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
22904
22994
  }
22995
+ this.invalidateCache();
22905
22996
  return data.createBean;
22906
22997
  }
22907
22998
  async update(beanId, updates) {
@@ -22971,6 +23062,7 @@ Output: ${stdout.slice(0, 1e3)}`
22971
23062
  if (errors && errors.length > 0) {
22972
23063
  throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
22973
23064
  }
23065
+ this.invalidateCache();
22974
23066
  return data.updateBean;
22975
23067
  }
22976
23068
  async delete(beanId) {
@@ -22980,8 +23072,44 @@ Output: ${stdout.slice(0, 1e3)}`
22980
23072
  if (errors && errors.length > 0) {
22981
23073
  throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
22982
23074
  }
23075
+ this.invalidateCache();
22983
23076
  return { deleted: true, beanId };
22984
23077
  }
23078
+ async bulkCreate(beans, defaultParent) {
23079
+ const settled = await Promise.allSettled(
23080
+ beans.map(
23081
+ async (item) => this.create({
23082
+ ...item,
23083
+ parent: item.parent ?? defaultParent
23084
+ })
23085
+ )
23086
+ );
23087
+ return settled.map(
23088
+ (result) => result.status === "fulfilled" ? { bean: result.value } : { error: result.reason instanceof Error ? result.reason.message : String(result.reason) }
23089
+ );
23090
+ }
23091
+ async bulkUpdate(beans, defaultParent) {
23092
+ const settled = await Promise.allSettled(
23093
+ beans.map(async ({ beanId, ...updates }) => {
23094
+ const resolvedParent = updates.parent ?? (updates.clearParent ? void 0 : defaultParent);
23095
+ const bean = await this.update(beanId, { ...updates, parent: resolvedParent });
23096
+ return { beanId, bean };
23097
+ })
23098
+ );
23099
+ return settled.map((result, index) => {
23100
+ const beanId = beans[index]?.beanId;
23101
+ if (!beanId) {
23102
+ return { beanId: "unknown", error: "Unknown bean id" };
23103
+ }
23104
+ if (result.status === "fulfilled") {
23105
+ return result.value;
23106
+ }
23107
+ return {
23108
+ beanId,
23109
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
23110
+ };
23111
+ });
23112
+ }
22985
23113
  async openConfig() {
22986
23114
  const configPath = join(this.workspaceRoot, ".beans.yml");
22987
23115
  const content = await readFile(configPath, "utf8");
@@ -23015,9 +23143,12 @@ Output: ${stdout.slice(0, 1e3)}`
23015
23143
  const outputPath = resolve2(
23016
23144
  process.env.BEANS_VSCODE_OUTPUT_LOG || join(this.workspaceRoot, ".vscode", "logs", "beans-output.log")
23017
23145
  );
23018
- const isWithinWorkspace = isPathWithinRoot(this.workspaceRoot, outputPath);
23146
+ const canonicalOutputPath = await realpath(outputPath).catch(() => outputPath);
23147
+ const canonicalWorkspaceRoot = await realpath(this.workspaceRoot).catch(() => resolve2(this.workspaceRoot));
23148
+ const isWithinWorkspace = isPathWithinRoot(canonicalWorkspaceRoot, canonicalOutputPath);
23019
23149
  const vscodeLogDir = process.env.BEANS_VSCODE_LOG_DIR || this.logDir ? resolve2(process.env.BEANS_VSCODE_LOG_DIR || this.logDir || "") : void 0;
23020
- const isWithinVscodeLogDir = vscodeLogDir ? isPathWithinRoot(vscodeLogDir, outputPath) : false;
23150
+ const canonicalVscodeLogDir = vscodeLogDir ? await realpath(vscodeLogDir).catch(() => resolve2(vscodeLogDir)) : void 0;
23151
+ const isWithinVscodeLogDir = canonicalVscodeLogDir ? isPathWithinRoot(canonicalVscodeLogDir, canonicalOutputPath) : false;
23021
23152
  if (!isWithinWorkspace && !isWithinVscodeLogDir) {
23022
23153
  throw new Error("Output log path must stay within the workspace or VS Code log directory");
23023
23154
  }
@@ -23040,6 +23171,219 @@ Output: ${stdout.slice(0, 1e3)}`
23040
23171
  linesReturned: ringBuffer.length
23041
23172
  };
23042
23173
  }
23174
+ /**
23175
+ * Split a YAML scalar value from any trailing inline comment.
23176
+ * Understands single-quoted and double-quoted YAML strings so it won't
23177
+ * mistake a `#` inside a quoted value for a comment delimiter.
23178
+ */
23179
+ splitYamlInlineComment(value) {
23180
+ let inSingle = false;
23181
+ let inDouble = false;
23182
+ for (let i = 0; i < value.length; i += 1) {
23183
+ const char = value[i];
23184
+ if (inSingle) {
23185
+ if (char === "'") {
23186
+ if (value[i + 1] === "'") {
23187
+ i += 1;
23188
+ } else {
23189
+ inSingle = false;
23190
+ }
23191
+ }
23192
+ continue;
23193
+ }
23194
+ if (inDouble) {
23195
+ if (char === "\\") {
23196
+ i += 1;
23197
+ continue;
23198
+ }
23199
+ if (char === '"') {
23200
+ inDouble = false;
23201
+ }
23202
+ continue;
23203
+ }
23204
+ if (char === "'") {
23205
+ inSingle = true;
23206
+ continue;
23207
+ }
23208
+ if (char === '"') {
23209
+ inDouble = true;
23210
+ continue;
23211
+ }
23212
+ if (char === "#" && i > 0 && /\s/.test(value[i - 1])) {
23213
+ const valuePart = value.slice(0, i).trimEnd();
23214
+ return {
23215
+ valuePart,
23216
+ commentPart: value.slice(valuePart.length)
23217
+ };
23218
+ }
23219
+ }
23220
+ return { valuePart: value, commentPart: "" };
23221
+ }
23222
+ /** Returns true when `value` looks like a YAML block scalar indicator (`>`, `|`, `>-`, `|-`, etc.) */
23223
+ isYamlBlockScalarIndicator(value) {
23224
+ return /^[>|][+-]?[0-9]*$/.test(value) || /^[>|][0-9]*[+-]?$/.test(value);
23225
+ }
23226
+ /** Escape a plain string for use inside a YAML double-quoted scalar. */
23227
+ escapeForYamlDoubleQuoted(value) {
23228
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
23229
+ }
23230
+ shouldQuoteFrontmatterValue(value) {
23231
+ return !/^[A-Za-z0-9._-]+$/.test(value);
23232
+ }
23233
+ parseFrontmatterLine(line) {
23234
+ const separatorIndex = line.indexOf(":");
23235
+ if (separatorIndex <= 0) {
23236
+ return null;
23237
+ }
23238
+ const key = line.slice(0, separatorIndex).trim();
23239
+ if (key.length === 0) {
23240
+ return null;
23241
+ }
23242
+ for (const character of key) {
23243
+ const isAlphaNumericUnderscore = character >= "a" && character <= "z" || character >= "A" && character <= "Z" || character >= "0" && character <= "9" || character === "_";
23244
+ if (!isAlphaNumericUnderscore) {
23245
+ return null;
23246
+ }
23247
+ }
23248
+ const rawValue = line.slice(separatorIndex + 1).trimStart();
23249
+ return { key, rawValue };
23250
+ }
23251
+ buildFrontmatterIndex(frontmatterLines) {
23252
+ const indexByKey = /* @__PURE__ */ new Map();
23253
+ frontmatterLines.forEach((line, index) => {
23254
+ const parsed = this.parseFrontmatterLine(line);
23255
+ if (!parsed) {
23256
+ return;
23257
+ }
23258
+ indexByKey.set(parsed.key, index);
23259
+ });
23260
+ return indexByKey;
23261
+ }
23262
+ serializeFrontmatterValue(key, value) {
23263
+ if (Array.isArray(value)) {
23264
+ return JSON.stringify(value);
23265
+ }
23266
+ if (key === "title") {
23267
+ return this.normalizeFrontmatterTitleValue(value);
23268
+ }
23269
+ if (this.shouldQuoteFrontmatterValue(value)) {
23270
+ return `"${this.escapeForYamlDoubleQuoted(value)}"`;
23271
+ }
23272
+ return value;
23273
+ }
23274
+ deserializeFrontmatterValue(value) {
23275
+ const trimmed = value.trim();
23276
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
23277
+ try {
23278
+ const parsed = JSON.parse(trimmed);
23279
+ if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
23280
+ return parsed;
23281
+ }
23282
+ } catch {
23283
+ }
23284
+ }
23285
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
23286
+ return trimmed.slice(1, -1).replaceAll('\\"', '"').replaceAll("\\\\", "\\").replaceAll("''", "'");
23287
+ }
23288
+ return trimmed;
23289
+ }
23290
+ splitFrontmatterDocument(content) {
23291
+ const crlfOpen = content.startsWith("---\r\n");
23292
+ const lfOpen = content.startsWith("---\n");
23293
+ const eol = crlfOpen ? "\r\n" : "\n";
23294
+ if (!crlfOpen && !lfOpen) {
23295
+ return { eol: "\n", hasFrontmatter: false, frontmatterLines: [], body: content };
23296
+ }
23297
+ const openEnd = `---${eol}`.length;
23298
+ const closeMarker = `${eol}---`;
23299
+ const closeIdx = content.indexOf(closeMarker, openEnd);
23300
+ if (closeIdx === -1) {
23301
+ return { eol, hasFrontmatter: false, frontmatterLines: [], body: content };
23302
+ }
23303
+ const frontmatter = content.slice(openEnd, closeIdx);
23304
+ const body = content.slice(closeIdx + closeMarker.length);
23305
+ return {
23306
+ eol,
23307
+ hasFrontmatter: true,
23308
+ frontmatterLines: frontmatter.length > 0 ? frontmatter.split(eol) : [],
23309
+ body
23310
+ };
23311
+ }
23312
+ parseFrontmatterFields(frontmatterLines) {
23313
+ const fields = {};
23314
+ for (const line of frontmatterLines) {
23315
+ const parsed = this.parseFrontmatterLine(line);
23316
+ if (!parsed) {
23317
+ continue;
23318
+ }
23319
+ const { valuePart } = this.splitYamlInlineComment(parsed.rawValue);
23320
+ fields[parsed.key] = this.deserializeFrontmatterValue(valuePart);
23321
+ }
23322
+ return fields;
23323
+ }
23324
+ async writeFileAtomically(absolutePath, content) {
23325
+ const tempPath = `${absolutePath}.tmp-${process.pid}-${Date.now()}`;
23326
+ await writeFile(tempPath, content, "utf8");
23327
+ await rename(tempPath, absolutePath);
23328
+ }
23329
+ /**
23330
+ * Normalise a raw YAML title value to a double-quoted scalar.
23331
+ * Handles: empty, already double-quoted, single-quoted (unescaping `''`),
23332
+ * block-scalar indicators, and plain unquoted values.
23333
+ */
23334
+ normalizeFrontmatterTitleValue(value) {
23335
+ const trimmed = value.trim();
23336
+ if (trimmed === "") {
23337
+ return '""';
23338
+ }
23339
+ if (this.isYamlBlockScalarIndicator(trimmed)) {
23340
+ return value;
23341
+ }
23342
+ if (/^"(?:[^"\\]|\\[\s\S])*"$/.test(trimmed)) {
23343
+ return trimmed;
23344
+ }
23345
+ if (/^'(?:[^']|'')*'$/.test(trimmed)) {
23346
+ const inner = trimmed.slice(1, -1).replace(/''/g, "'");
23347
+ return `"${this.escapeForYamlDoubleQuoted(inner)}"`;
23348
+ }
23349
+ return `"${this.escapeForYamlDoubleQuoted(trimmed)}"`;
23350
+ }
23351
+ /**
23352
+ * Ensure every `title:` line in YAML frontmatter is double-quoted.
23353
+ * Handles already-quoted (single or double), multi-word, and special-char values.
23354
+ * Preserves inline comments and handles both LF and CRLF line endings.
23355
+ */
23356
+ quoteFrontmatterTitles(content) {
23357
+ const crlfOpen = content.startsWith("---\r\n");
23358
+ const lfOpen = content.startsWith("---\n");
23359
+ if (!crlfOpen && !lfOpen) {
23360
+ return content;
23361
+ }
23362
+ const eol = crlfOpen ? "\r\n" : "\n";
23363
+ const openEnd = `---${eol}`.length;
23364
+ const closeMarker = `${eol}---`;
23365
+ const closeIdx = content.indexOf(closeMarker, openEnd);
23366
+ if (closeIdx === -1) {
23367
+ return content;
23368
+ }
23369
+ const frontmatter = content.slice(openEnd, closeIdx);
23370
+ const rest = content.slice(closeIdx);
23371
+ const lines = frontmatter.split(eol);
23372
+ const fixedLines = lines.map((line) => {
23373
+ if (!line.startsWith("title:")) {
23374
+ return line;
23375
+ }
23376
+ const colonIdx = line.indexOf(":");
23377
+ const afterColon = line.slice(colonIdx + 1);
23378
+ const leadingSpace = afterColon.length - afterColon.trimStart().length;
23379
+ const raw = afterColon.trimStart();
23380
+ const { valuePart, commentPart } = this.splitYamlInlineComment(raw);
23381
+ const normalized = this.normalizeFrontmatterTitleValue(valuePart);
23382
+ const prefix = `title:${" ".repeat(Math.max(1, leadingSpace))}`;
23383
+ return `${prefix}${normalized}${commentPart}`;
23384
+ });
23385
+ return `---${eol}${fixedLines.join(eol)}${rest}`;
23386
+ }
23043
23387
  async readBeanFile(relativePath) {
23044
23388
  const absolutePath = this.resolveBeanFilePath(relativePath);
23045
23389
  const content = await readFile(absolutePath, "utf8");
@@ -23047,20 +23391,74 @@ Output: ${stdout.slice(0, 1e3)}`
23047
23391
  }
23048
23392
  async editBeanFile(relativePath, content) {
23049
23393
  const absolutePath = this.resolveBeanFilePath(relativePath);
23394
+ const fixed = this.quoteFrontmatterTitles(content);
23050
23395
  await mkdir(dirname(absolutePath), { recursive: true });
23051
- await writeFile(absolutePath, content, "utf8");
23052
- return { path: absolutePath, bytes: Buffer.byteLength(content, "utf8") };
23396
+ await writeFile(absolutePath, fixed, "utf8");
23397
+ return { path: absolutePath, bytes: Buffer.byteLength(fixed, "utf8") };
23398
+ }
23399
+ async updateBeanFrontmatter(relativePath, updates) {
23400
+ const absolutePath = this.resolveBeanFilePath(relativePath);
23401
+ const content = await readFile(absolutePath, "utf8");
23402
+ const { eol, hasFrontmatter, frontmatterLines, body } = this.splitFrontmatterDocument(content);
23403
+ const updatedFields = Object.entries(updates).filter(([, value]) => value !== void 0).map(([key]) => key);
23404
+ if (updatedFields.length === 0) {
23405
+ throw new Error("At least one frontmatter field update is required");
23406
+ }
23407
+ const nextLines = [...frontmatterLines];
23408
+ let indexByKey = this.buildFrontmatterIndex(nextLines);
23409
+ for (const [key, value] of Object.entries(updates)) {
23410
+ if (value === void 0) {
23411
+ continue;
23412
+ }
23413
+ const existingIndex = indexByKey.get(key);
23414
+ if (value === null) {
23415
+ if (existingIndex !== void 0) {
23416
+ nextLines.splice(existingIndex, 1);
23417
+ indexByKey = this.buildFrontmatterIndex(nextLines);
23418
+ }
23419
+ continue;
23420
+ }
23421
+ const serialized = `${key}: ${this.serializeFrontmatterValue(key, value)}`;
23422
+ if (existingIndex !== void 0) {
23423
+ const existingLine = nextLines[existingIndex] ?? "";
23424
+ const existingParsed = this.parseFrontmatterLine(existingLine);
23425
+ const commentPart = existingParsed ? this.splitYamlInlineComment(existingParsed.rawValue).commentPart : "";
23426
+ nextLines[existingIndex] = `${serialized}${commentPart}`;
23427
+ } else {
23428
+ nextLines.push(serialized);
23429
+ indexByKey.set(key, nextLines.length - 1);
23430
+ }
23431
+ }
23432
+ const frontmatterBlock = nextLines.length > 0 ? nextLines.join(eol) : "";
23433
+ const nextContent = hasFrontmatter ? `---${eol}${frontmatterBlock}${eol}---${body}` : `---${eol}${frontmatterBlock}${eol}---${eol}${body}`;
23434
+ const fixed = this.quoteFrontmatterTitles(nextContent);
23435
+ await this.writeFileAtomically(absolutePath, fixed);
23436
+ return {
23437
+ path: absolutePath,
23438
+ bytes: Buffer.byteLength(fixed, "utf8"),
23439
+ updatedFields,
23440
+ frontmatter: this.parseFrontmatterFields(this.splitFrontmatterDocument(fixed).frontmatterLines)
23441
+ };
23053
23442
  }
23054
23443
  async createBeanFile(relativePath, content, options) {
23055
23444
  const absolutePath = this.resolveBeanFilePath(relativePath);
23445
+ const fixed = this.quoteFrontmatterTitles(content);
23056
23446
  await mkdir(dirname(absolutePath), { recursive: true });
23057
- await writeFile(absolutePath, content, {
23058
- encoding: "utf8",
23059
- flag: options?.overwrite ? "w" : "wx"
23060
- });
23447
+ try {
23448
+ await writeFile(absolutePath, fixed, {
23449
+ encoding: "utf8",
23450
+ flag: options?.overwrite ? "w" : "wx"
23451
+ });
23452
+ } catch (error48) {
23453
+ const maybeNodeError = error48;
23454
+ if (maybeNodeError.code === "EEXIST" && !options?.overwrite) {
23455
+ throw new Error("Bean file already exists. Pass overwrite=true to replace it.");
23456
+ }
23457
+ throw error48;
23458
+ }
23061
23459
  return {
23062
23460
  path: absolutePath,
23063
- bytes: Buffer.byteLength(content, "utf8"),
23461
+ bytes: Buffer.byteLength(fixed, "utf8"),
23064
23462
  created: true
23065
23463
  };
23066
23464
  }
@@ -23073,7 +23471,7 @@ Output: ${stdout.slice(0, 1e3)}`
23073
23471
  }
23074
23472
  });
23075
23473
 
23076
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
23474
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
23077
23475
  function deserializeMessage(line) {
23078
23476
  return JSONRPCMessageSchema.parse(JSON.parse(line));
23079
23477
  }
@@ -23082,7 +23480,7 @@ function serializeMessage(message) {
23082
23480
  }
23083
23481
  var ReadBuffer;
23084
23482
  var init_stdio = __esm({
23085
- "node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js"() {
23483
+ "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js"() {
23086
23484
  "use strict";
23087
23485
  init_types();
23088
23486
  ReadBuffer = class {
@@ -23108,7 +23506,7 @@ var init_stdio = __esm({
23108
23506
  }
23109
23507
  });
23110
23508
 
23111
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
23509
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
23112
23510
  var stdio_exports = {};
23113
23511
  __export(stdio_exports, {
23114
23512
  StdioServerTransport: () => StdioServerTransport
@@ -23116,7 +23514,7 @@ __export(stdio_exports, {
23116
23514
  import process3 from "process";
23117
23515
  var StdioServerTransport;
23118
23516
  var init_stdio2 = __esm({
23119
- "node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js"() {
23517
+ "node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js"() {
23120
23518
  "use strict";
23121
23519
  init_stdio();
23122
23520
  StdioServerTransport = class {
@@ -27123,7 +27521,7 @@ init_core2();
27123
27521
  // node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/mini/coerce.js
27124
27522
  init_core2();
27125
27523
 
27126
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
27524
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
27127
27525
  function isZ4Schema(s) {
27128
27526
  const schema = s;
27129
27527
  return !!schema._zod;
@@ -27267,10 +27665,10 @@ function getLiteralValue(schema) {
27267
27665
  return void 0;
27268
27666
  }
27269
27667
 
27270
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
27668
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
27271
27669
  init_types();
27272
27670
 
27273
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
27671
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
27274
27672
  function isTerminal(status) {
27275
27673
  return status === "completed" || status === "failed" || status === "cancelled";
27276
27674
  }
@@ -28559,7 +28957,7 @@ var zodToJsonSchema = (schema, options) => {
28559
28957
  return combined;
28560
28958
  };
28561
28959
 
28562
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
28960
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
28563
28961
  function mapMiniTarget(t) {
28564
28962
  if (!t)
28565
28963
  return "draft-7";
@@ -28601,7 +28999,7 @@ function parseWithCompat(schema, data) {
28601
28999
  return result.data;
28602
29000
  }
28603
29001
 
28604
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
29002
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
28605
29003
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 6e4;
28606
29004
  var Protocol = class {
28607
29005
  constructor(_options) {
@@ -28813,6 +29211,10 @@ var Protocol = class {
28813
29211
  this._progressHandlers.clear();
28814
29212
  this._taskProgressTokens.clear();
28815
29213
  this._pendingDebouncedNotifications.clear();
29214
+ for (const info of this._timeoutInfo.values()) {
29215
+ clearTimeout(info.timeoutId);
29216
+ }
29217
+ this._timeoutInfo.clear();
28816
29218
  for (const controller of this._requestHandlerAbortControllers.values()) {
28817
29219
  controller.abort();
28818
29220
  }
@@ -28943,7 +29345,9 @@ var Protocol = class {
28943
29345
  await capturedTransport?.send(errorResponse);
28944
29346
  }
28945
29347
  }).catch((error48) => this._onerror(new Error(`Failed to send response: ${error48}`))).finally(() => {
28946
- this._requestHandlerAbortControllers.delete(request.id);
29348
+ if (this._requestHandlerAbortControllers.get(request.id) === abortController) {
29349
+ this._requestHandlerAbortControllers.delete(request.id);
29350
+ }
28947
29351
  });
28948
29352
  }
28949
29353
  _onprogress(notification) {
@@ -29549,10 +29953,10 @@ function mergeCapabilities(base, additional) {
29549
29953
  return result;
29550
29954
  }
29551
29955
 
29552
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
29956
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
29553
29957
  init_types();
29554
29958
 
29555
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
29959
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
29556
29960
  var import_ajv = __toESM(require_ajv(), 1);
29557
29961
  var import_ajv_formats = __toESM(require_dist(), 1);
29558
29962
  function createDefaultAjvInstance() {
@@ -29620,7 +30024,7 @@ var AjvJsonSchemaValidator = class {
29620
30024
  }
29621
30025
  };
29622
30026
 
29623
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
30027
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
29624
30028
  init_types();
29625
30029
  var ExperimentalServerTasks = class {
29626
30030
  constructor(_server) {
@@ -29834,7 +30238,7 @@ var ExperimentalServerTasks = class {
29834
30238
  }
29835
30239
  };
29836
30240
 
29837
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
30241
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
29838
30242
  function assertToolsCallTaskCapability(requests, method, entityName) {
29839
30243
  if (!requests) {
29840
30244
  throw new Error(`${entityName} does not support task creation (required for ${method})`);
@@ -29869,7 +30273,7 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
29869
30273
  }
29870
30274
  }
29871
30275
 
29872
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
30276
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
29873
30277
  var Server = class extends Protocol {
29874
30278
  /**
29875
30279
  * Initializes this server with the given name and version information.
@@ -30249,10 +30653,10 @@ var Server = class extends Protocol {
30249
30653
  }
30250
30654
  };
30251
30655
 
30252
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30656
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30253
30657
  init_types();
30254
30658
 
30255
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
30659
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
30256
30660
  var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
30257
30661
  function isCompletable(schema) {
30258
30662
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
@@ -30266,7 +30670,7 @@ var McpZodTypeKind;
30266
30670
  McpZodTypeKind2["Completable"] = "McpCompletable";
30267
30671
  })(McpZodTypeKind || (McpZodTypeKind = {}));
30268
30672
 
30269
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
30673
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
30270
30674
  var TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
30271
30675
  function validateToolName(name) {
30272
30676
  const warnings = [];
@@ -30324,7 +30728,7 @@ function validateAndWarnToolName(name) {
30324
30728
  return result.isValid;
30325
30729
  }
30326
30730
 
30327
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
30731
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
30328
30732
  var ExperimentalMcpServerTasks = class {
30329
30733
  constructor(_mcpServer) {
30330
30734
  this._mcpServer = _mcpServer;
@@ -30343,7 +30747,7 @@ var ExperimentalMcpServerTasks = class {
30343
30747
  init_external();
30344
30748
  init_external();
30345
30749
 
30346
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30750
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
30347
30751
  var McpServer = class {
30348
30752
  constructor(serverInfo, options) {
30349
30753
  this._registeredResources = {};
@@ -30967,6 +31371,9 @@ var McpServer = class {
30967
31371
  annotations = rest.shift();
30968
31372
  }
30969
31373
  } else if (typeof firstArg === "object" && firstArg !== null) {
31374
+ if (Object.values(firstArg).some((v) => typeof v === "object" && v !== null)) {
31375
+ throw new Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);
31376
+ }
30970
31377
  annotations = rest.shift();
30971
31378
  }
30972
31379
  }
@@ -31085,6 +31492,9 @@ function getZodSchemaObject(schema) {
31085
31492
  if (isZodRawShapeCompat(schema)) {
31086
31493
  return objectFromShape(schema);
31087
31494
  }
31495
+ if (!isZodSchemaInstance(schema)) {
31496
+ throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");
31497
+ }
31088
31498
  return schema;
31089
31499
  }
31090
31500
  function promptArgumentsFromSchema(schema) {
@@ -31133,6 +31543,100 @@ var EMPTY_COMPLETION_RESULT = {
31133
31543
  import { execFile as execFile2 } from "child_process";
31134
31544
  import { promisify as promisify2 } from "util";
31135
31545
 
31546
+ // package.json
31547
+ var package_default = {
31548
+ name: "@selfagency/beans-mcp",
31549
+ version: "0.6.0",
31550
+ private: false,
31551
+ description: "MCP (Model Context Protocol) server for Beans issue tracker",
31552
+ keywords: [
31553
+ "ai",
31554
+ "beans",
31555
+ "issue-tracker",
31556
+ "mcp",
31557
+ "model-context-protocol"
31558
+ ],
31559
+ homepage: "https://github.com/selfagency/beans-mcp",
31560
+ bugs: {
31561
+ url: "https://github.com/selfagency/beans-mcp/issues"
31562
+ },
31563
+ license: "MIT",
31564
+ author: {
31565
+ name: "Daniel Sieradski",
31566
+ email: "daniel@self.agency",
31567
+ url: "https://self.agency"
31568
+ },
31569
+ repository: {
31570
+ type: "git",
31571
+ url: "git+https://github.com/selfagency/beans-mcp.git"
31572
+ },
31573
+ bin: {
31574
+ "beans-mcp": "dist/beans-mcp-server.cjs"
31575
+ },
31576
+ files: [
31577
+ "dist",
31578
+ "skills",
31579
+ "README.md",
31580
+ "LICENSE.txt"
31581
+ ],
31582
+ type: "module",
31583
+ main: "./dist/index.cjs",
31584
+ module: "./dist/index.js",
31585
+ types: "./dist/index.d.ts",
31586
+ exports: {
31587
+ ".": {
31588
+ types: "./dist/index.d.ts",
31589
+ import: "./dist/index.js",
31590
+ require: "./dist/index.cjs"
31591
+ }
31592
+ },
31593
+ scripts: {
31594
+ build: "tsup",
31595
+ "docs:dev": "vitepress dev docs",
31596
+ "docs:build": "vitepress build docs",
31597
+ "docs:preview": "vitepress preview docs",
31598
+ format: "oxfmt",
31599
+ "lint:fix": "oxlint --fix",
31600
+ lint: "oxlint",
31601
+ postbuild: "node ./scripts/write-dist-package.js",
31602
+ prepare: "husky",
31603
+ release: "zx ./scripts/release.js",
31604
+ "test:coverage": "vitest run --coverage",
31605
+ "test:watch": "vitest",
31606
+ test: "vitest run",
31607
+ "type-check": "tsc --noEmit"
31608
+ },
31609
+ devDependencies: {
31610
+ "@modelcontextprotocol/sdk": "^1.29.0",
31611
+ "@octokit/rest": "^22.0.1",
31612
+ "@types/node": "25.6.0",
31613
+ "@vitest/coverage-v8": "^4.1.4",
31614
+ "@vitest/ui": "4.1.4",
31615
+ husky: "^9.1.7",
31616
+ "lint-staged": "^16.4.0",
31617
+ ora: "^9.3.0",
31618
+ oxfmt: "^0.45.0",
31619
+ oxlint: "^1.60.0",
31620
+ "oxlint-tsgolint": "^0.21.1",
31621
+ tsup: "8.5.1",
31622
+ typescript: "6.0.3",
31623
+ vitepress: "^1.6.4",
31624
+ vitest: "4.1.4",
31625
+ zod: "4.3.6",
31626
+ zx: "^8.8.5"
31627
+ },
31628
+ "lint-staged": {
31629
+ "src/**/*.ts": [
31630
+ "pnpm run lint:fix",
31631
+ "pnpm run format"
31632
+ ]
31633
+ },
31634
+ engines: {
31635
+ node: ">=18"
31636
+ },
31637
+ mcpName: "io.github.selfagency/beans-mcp"
31638
+ };
31639
+
31136
31640
  // src/internal/queryHelpers.ts
31137
31641
  function sortBeansInternal(beans, mode) {
31138
31642
  const sorted = [...beans];
@@ -31293,94 +31797,10 @@ var MAX_PATH_LENGTH = 1024;
31293
31797
 
31294
31798
  // src/server/BeansMcpServer.ts
31295
31799
  init_utils();
31296
-
31297
- // package.json
31298
- var package_default = {
31299
- name: "@selfagency/beans-mcp",
31300
- version: "0.4.2",
31301
- private: false,
31302
- description: "MCP (Model Context Protocol) server for Beans issue tracker",
31303
- author: {
31304
- name: "Daniel Sieradski",
31305
- email: "daniel@self.agency",
31306
- url: "https://self.agency"
31307
- },
31308
- homepage: "https://github.com/hmans/beans",
31309
- bugs: {
31310
- url: "https://github.com/selfagency/beans-mcp/issues"
31311
- },
31312
- repository: {
31313
- type: "git",
31314
- url: "git+https://github.com/selfagency/beans-mcp.git"
31315
- },
31316
- mcpName: "io.github.selfagency/beans-mcp",
31317
- keywords: [
31318
- "beans",
31319
- "mcp",
31320
- "model-context-protocol",
31321
- "issue-tracker",
31322
- "ai"
31323
- ],
31324
- license: "MIT",
31325
- type: "module",
31326
- exports: {
31327
- ".": {
31328
- types: "./dist/index.d.ts",
31329
- import: "./dist/index.js",
31330
- require: "./dist/index.cjs"
31331
- }
31332
- },
31333
- main: "./dist/index.cjs",
31334
- module: "./dist/index.js",
31335
- types: "./dist/index.d.ts",
31336
- bin: {
31337
- "beans-mcp": "dist/beans-mcp-server.cjs"
31338
- },
31339
- scripts: {
31340
- build: "tsup",
31341
- format: "oxfmt",
31342
- "lint:fix": "oxlint --fix",
31343
- lint: "oxlint",
31344
- postbuild: "node ./scripts/write-dist-package.js",
31345
- prepare: "husky",
31346
- release: "zx ./scripts/release.js",
31347
- "test:coverage": "vitest run --coverage",
31348
- "test:watch": "vitest",
31349
- test: "vitest run",
31350
- "type-check": "tsc --noEmit"
31351
- },
31352
- devDependencies: {
31353
- "@modelcontextprotocol/sdk": "^1.27.1",
31354
- "@octokit/rest": "^22.0.1",
31355
- "@types/node": "25.5.0",
31356
- "@vitest/coverage-v8": "^4.1.0",
31357
- "@vitest/ui": "4.1.0",
31358
- husky: "^9.1.7",
31359
- "lint-staged": "^16.3.3",
31360
- ora: "^9.3.0",
31361
- oxfmt: "^0.40.0",
31362
- oxlint: "^1.55.0",
31363
- "oxlint-tsgolint": "^0.16.0",
31364
- tsup: "8.5.1",
31365
- typescript: "^5.9.3",
31366
- vitest: "4.1.0",
31367
- zod: "4.3.6",
31368
- zx: "^8.8.5"
31369
- },
31370
- engines: {
31371
- node: ">=18"
31372
- },
31373
- "lint-staged": {
31374
- "src/**/*.ts": [
31375
- "pnpm run lint:fix",
31376
- "pnpm run format"
31377
- ]
31378
- }
31379
- };
31380
-
31381
- // src/server/BeansMcpServer.ts
31382
31800
  var execFileAsync2 = promisify2(execFile2);
31383
31801
  var PACKAGE_VERSION = package_default.version ?? "0.0.0-dev";
31802
+ var CLOSED_STATUSES = /* @__PURE__ */ new Set(["completed", "scrapped"]);
31803
+ var BEAN_ID_HINT = "Missing required field `beanId`. Did you mean `beanId`?";
31384
31804
  function getSafeCliEnv(env) {
31385
31805
  const whitelist = ["PATH", "HOME", "USER", "LANG", "LC_ALL", "LC_CTYPE", "SHELL"];
31386
31806
  const safeEnv = {};
@@ -31401,7 +31821,8 @@ function extractVersionFromOutput(output) {
31401
31821
  if (!trimmed) {
31402
31822
  return null;
31403
31823
  }
31404
- const match = trimmed.match(/(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
31824
+ const versionRegex = /(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
31825
+ const match = versionRegex.exec(trimmed);
31405
31826
  return match?.[1] ?? null;
31406
31827
  }
31407
31828
  async function detectBeansCliVersion(cliPath, workspaceRoot) {
@@ -31418,10 +31839,10 @@ ${stderr}`);
31418
31839
  return null;
31419
31840
  }
31420
31841
  }
31421
- async function getBeanById(backend, beanId) {
31842
+ async function getBeanById(backend, beanId, beans) {
31422
31843
  try {
31423
- const beans = await backend.list();
31424
- const found = beans.find((b) => b.id === beanId);
31844
+ const allBeans = beans ?? await backend.list();
31845
+ const found = allBeans.find((b) => b.id === beanId);
31425
31846
  if (!found) {
31426
31847
  throw new Error(`Bean not found: ${beanId}`);
31427
31848
  }
@@ -31430,12 +31851,111 @@ async function getBeanById(backend, beanId) {
31430
31851
  throw new Error(`Failed to fetch bean ${beanId}: ${error48.message}`);
31431
31852
  }
31432
31853
  }
31854
+ function collectDescendantBeans(beans, rootBeanId) {
31855
+ const byParent = /* @__PURE__ */ new Map();
31856
+ const byId = new Map(beans.map((bean) => [bean.id, bean]));
31857
+ for (const bean of beans) {
31858
+ if (!bean.parentId) {
31859
+ continue;
31860
+ }
31861
+ const children = byParent.get(bean.parentId) ?? [];
31862
+ children.push(bean.id);
31863
+ byParent.set(bean.parentId, children);
31864
+ }
31865
+ const queue = [...byParent.get(rootBeanId) ?? []];
31866
+ const visited = /* @__PURE__ */ new Set();
31867
+ const descendants = [];
31868
+ while (queue.length > 0) {
31869
+ const currentId = queue.shift();
31870
+ if (!currentId || visited.has(currentId)) {
31871
+ continue;
31872
+ }
31873
+ visited.add(currentId);
31874
+ const currentBean = byId.get(currentId);
31875
+ if (!currentBean) {
31876
+ continue;
31877
+ }
31878
+ descendants.push(currentBean);
31879
+ const children = byParent.get(currentId);
31880
+ if (children && children.length > 0) {
31881
+ queue.push(...children);
31882
+ }
31883
+ }
31884
+ return descendants;
31885
+ }
31886
+ async function cascadeStatusToDescendants(backend, rootBeanId, targetStatus, options) {
31887
+ const beans = options?.beans ?? await backend.list();
31888
+ const descendants = collectDescendantBeans(beans, rootBeanId);
31889
+ const updatedBeanIds = [];
31890
+ const skippedBeanIds = [];
31891
+ const errors = [];
31892
+ const toUpdate = [];
31893
+ for (const bean of descendants) {
31894
+ if (options?.onlyCurrentStatuses && !options.onlyCurrentStatuses.has(bean.status)) {
31895
+ skippedBeanIds.push(bean.id);
31896
+ continue;
31897
+ }
31898
+ toUpdate.push(bean);
31899
+ }
31900
+ const settled = await Promise.allSettled(
31901
+ toUpdate.map(async (bean) => backend.update(bean.id, { status: targetStatus }))
31902
+ );
31903
+ settled.forEach((result, index) => {
31904
+ const bean = toUpdate[index];
31905
+ if (!bean) {
31906
+ return;
31907
+ }
31908
+ if (result.status === "fulfilled") {
31909
+ updatedBeanIds.push(bean.id);
31910
+ return;
31911
+ }
31912
+ errors.push({
31913
+ beanId: bean.id,
31914
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
31915
+ });
31916
+ });
31917
+ return {
31918
+ totalDescendants: descendants.length,
31919
+ updatedBeanIds,
31920
+ skippedBeanIds,
31921
+ errors
31922
+ };
31923
+ }
31924
+ function completeMarkdownTasks(body) {
31925
+ const lines = body.split(/\r?\n/);
31926
+ let totalTaskCount = 0;
31927
+ let updatedTaskCount = 0;
31928
+ const taskLinePattern = /^\s*(?:[-*+]|\d+\.)\s+\[[ xX]\]/;
31929
+ const uncheckedTaskLinePattern = /^(\s*(?:[-*+]|\d+\.)\s+\[)\s(\].*)$/;
31930
+ const nextLines = lines.map((line) => {
31931
+ if (!taskLinePattern.test(line)) {
31932
+ return line;
31933
+ }
31934
+ totalTaskCount += 1;
31935
+ const uncheckedMatch = uncheckedTaskLinePattern.exec(line);
31936
+ if (!uncheckedMatch) {
31937
+ return line;
31938
+ }
31939
+ updatedTaskCount += 1;
31940
+ return `${uncheckedMatch[1]}x${uncheckedMatch[2]}`;
31941
+ });
31942
+ const nextBody = nextLines.join("\n");
31943
+ return { nextBody, totalTaskCount, updatedTaskCount };
31944
+ }
31433
31945
  function initHandler(backend) {
31434
31946
  return async ({ prefix }) => {
31435
31947
  const result = await backend.init(prefix);
31436
31948
  return makeTextAndStructured(result);
31437
31949
  };
31438
31950
  }
31951
+ function archiveHandler(backend) {
31952
+ return async () => {
31953
+ if (typeof backend.archive !== "function") {
31954
+ throw new TypeError("Archive is not supported by the current backend");
31955
+ }
31956
+ return makeTextAndStructured(await backend.archive());
31957
+ };
31958
+ }
31439
31959
  function viewHandler(backend) {
31440
31960
  return async ({ beanId, beanIds }) => {
31441
31961
  const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
@@ -31450,7 +31970,12 @@ function viewHandler(backend) {
31450
31970
  const byId = new Map(beans.map((b) => [b.id, b]));
31451
31971
  const found = ids.map((id) => byId.get(id)).filter(Boolean);
31452
31972
  const missingBeanIds = ids.filter((id) => !byId.has(id));
31453
- return makeTextAndStructured({ beans: found, missingBeanIds, count: found.length, requestedCount: ids.length });
31973
+ return makeTextAndStructured({
31974
+ beans: found,
31975
+ missingBeanIds,
31976
+ count: found.length,
31977
+ requestedCount: ids.length
31978
+ });
31454
31979
  };
31455
31980
  }
31456
31981
  async function checkVersionCompatibility(cliPath, workspaceRoot, detector) {
@@ -31468,7 +31993,14 @@ async function checkVersionCompatibility(cliPath, workspaceRoot, detector) {
31468
31993
  }
31469
31994
  }
31470
31995
  function createHandler(backend) {
31471
- return async (input) => makeTextAndStructured({ bean: await backend.create(input) });
31996
+ return async (input) => {
31997
+ const bean = await backend.create(input);
31998
+ const warnings = input.description !== void 0 ? ["`description` is deprecated; use `body` instead."] : void 0;
31999
+ return makeTextAndStructured({
32000
+ bean,
32001
+ ...warnings ? { warnings } : {}
32002
+ });
32003
+ };
31472
32004
  }
31473
32005
  function editHandler(backend) {
31474
32006
  return async ({
@@ -31482,18 +32014,30 @@ function reopenHandler(backend) {
31482
32014
  requiredCurrentStatus,
31483
32015
  targetStatus
31484
32016
  }) => {
31485
- const bean = await getBeanById(backend, beanId);
31486
- if (bean.status !== requiredCurrentStatus) {
31487
- throw new Error(`Bean ${beanId} is not ${requiredCurrentStatus}`);
32017
+ const beans = await backend.list();
32018
+ const bean = await getBeanById(backend, beanId, beans);
32019
+ if (bean.status === requiredCurrentStatus) {
32020
+ const updatedParentBean = await backend.update(beanId, { status: targetStatus });
32021
+ const cascade = await cascadeStatusToDescendants(backend, beanId, targetStatus, {
32022
+ onlyCurrentStatuses: CLOSED_STATUSES,
32023
+ beans
32024
+ });
32025
+ return makeTextAndStructured({
32026
+ bean: updatedParentBean,
32027
+ cascade: {
32028
+ totalDescendants: cascade.totalDescendants,
32029
+ updatedBeanIds: cascade.updatedBeanIds,
32030
+ skippedBeanIds: cascade.skippedBeanIds,
32031
+ errors: cascade.errors
32032
+ }
32033
+ });
31488
32034
  }
31489
- return makeTextAndStructured({
31490
- bean: await backend.update(beanId, { status: targetStatus })
31491
- });
32035
+ throw new Error(`Bean ${beanId} is not ${requiredCurrentStatus}`);
31492
32036
  };
31493
32037
  }
31494
32038
  function updateHandler(backend) {
31495
- return async (input) => makeTextAndStructured({
31496
- bean: await backend.update(input.beanId, {
32039
+ return async (input) => {
32040
+ const updatedBean = await backend.update(input.beanId, {
31497
32041
  status: input.status,
31498
32042
  type: input.type,
31499
32043
  priority: input.priority,
@@ -31505,8 +32049,37 @@ function updateHandler(backend) {
31505
32049
  bodyAppend: input.bodyAppend,
31506
32050
  bodyReplace: input.bodyReplace,
31507
32051
  ifMatch: input.ifMatch
31508
- })
31509
- });
32052
+ });
32053
+ const closeStatus = input.status;
32054
+ const shouldCascadeClose = Boolean(closeStatus && CLOSED_STATUSES.has(closeStatus));
32055
+ const cascade = shouldCascadeClose ? await cascadeStatusToDescendants(backend, input.beanId, closeStatus, {
32056
+ beans: await backend.list()
32057
+ }) : null;
32058
+ return makeTextAndStructured({
32059
+ bean: updatedBean,
32060
+ ...cascade ? {
32061
+ cascade: {
32062
+ totalDescendants: cascade.totalDescendants,
32063
+ updatedBeanIds: cascade.updatedBeanIds,
32064
+ skippedBeanIds: cascade.skippedBeanIds,
32065
+ errors: cascade.errors
32066
+ }
32067
+ } : {}
32068
+ });
32069
+ };
32070
+ }
32071
+ function completeTasksHandler(backend) {
32072
+ return async ({ beanId }) => {
32073
+ const bean = await getBeanById(backend, beanId);
32074
+ const { nextBody, totalTaskCount, updatedTaskCount } = completeMarkdownTasks(bean.body || "");
32075
+ const updatedBean = updatedTaskCount > 0 ? await backend.update(beanId, { body: nextBody }) : bean;
32076
+ return makeTextAndStructured({
32077
+ bean: updatedBean,
32078
+ totalTaskCount,
32079
+ updatedTaskCount,
32080
+ unchangedTaskCount: totalTaskCount - updatedTaskCount
32081
+ });
32082
+ };
31510
32083
  }
31511
32084
  function deleteHandler(backend) {
31512
32085
  return async ({ beanId, beanIds, force }) => {
@@ -31542,7 +32115,11 @@ function deleteHandler(backend) {
31542
32115
  await backend.delete(id);
31543
32116
  results.push({ beanId: id, deleted: true });
31544
32117
  } catch (error48) {
31545
- results.push({ beanId: id, deleted: false, error: error48.message });
32118
+ results.push({
32119
+ beanId: id,
32120
+ deleted: false,
32121
+ error: error48.message
32122
+ });
31546
32123
  }
31547
32124
  }
31548
32125
  return makeTextAndStructured({
@@ -31553,15 +32130,53 @@ function deleteHandler(backend) {
31553
32130
  });
31554
32131
  };
31555
32132
  }
32133
+ function bulkCreateHandler(backend) {
32134
+ return async (input) => {
32135
+ const results = await backend.bulkCreate(input.beans, input.parent);
32136
+ const deprecatedDescriptionCount = input.beans.filter((bean) => bean.description !== void 0).length;
32137
+ return makeTextAndStructured({
32138
+ results,
32139
+ requestedCount: input.beans.length,
32140
+ successCount: results.filter((r) => r.bean).length,
32141
+ failedCount: results.filter((r) => r.error).length,
32142
+ ...deprecatedDescriptionCount > 0 ? {
32143
+ warnings: [
32144
+ `Found ${deprecatedDescriptionCount} bean(s) using deprecated field \`description\`; use \`body\` instead.`
32145
+ ]
32146
+ } : {}
32147
+ });
32148
+ };
32149
+ }
32150
+ function bulkUpdateHandler(backend) {
32151
+ return async (input) => {
32152
+ const results = await backend.bulkUpdate(input.beans, input.parent);
32153
+ return makeTextAndStructured({
32154
+ results,
32155
+ requestedCount: input.beans.length,
32156
+ successCount: results.filter((r) => r.bean).length,
32157
+ failedCount: results.filter((r) => r.error).length
32158
+ });
32159
+ };
32160
+ }
31556
32161
  function queryHandler(backend) {
31557
- return async (opts) => handleQueryOperation(backend, opts);
32162
+ return async (opts) => {
32163
+ if (opts.operation === "graphql") {
32164
+ if (typeof backend.queryGraphql !== "function") {
32165
+ throw new TypeError("GraphQL passthrough is not supported by the current backend");
32166
+ }
32167
+ const result = await backend.queryGraphql(opts.graphql || "", opts.variables);
32168
+ return makeTextAndStructured({ data: result.data, errors: result.errors ?? [] });
32169
+ }
32170
+ return handleQueryOperation(backend, opts);
32171
+ };
31558
32172
  }
31559
32173
  function beanFileHandler(backend) {
31560
32174
  return async ({
31561
32175
  operation,
31562
32176
  path,
31563
32177
  content,
31564
- overwrite
32178
+ overwrite,
32179
+ fields
31565
32180
  }) => {
31566
32181
  if (operation === "read") {
31567
32182
  return makeTextAndStructured(await backend.readBeanFile(path));
@@ -31572,6 +32187,9 @@ function beanFileHandler(backend) {
31572
32187
  if (operation === "create") {
31573
32188
  return makeTextAndStructured(await backend.createBeanFile(path, content || "", { overwrite }));
31574
32189
  }
32190
+ if (operation === "update_frontmatter") {
32191
+ return makeTextAndStructured(await backend.updateBeanFrontmatter(path, fields || {}));
32192
+ }
31575
32193
  if (operation === "delete") {
31576
32194
  return makeTextAndStructured(await backend.deleteBeanFile(path));
31577
32195
  }
@@ -31606,6 +32224,21 @@ function registerTools(server, backend) {
31606
32224
  },
31607
32225
  initHandler(backend)
31608
32226
  );
32227
+ server.registerTool(
32228
+ "beans_archive",
32229
+ {
32230
+ title: "Archive Beans",
32231
+ description: "Archive completed or scrapped beans, equivalent to the beans CLI archive command.",
32232
+ inputSchema: external_exports3.object({}),
32233
+ annotations: {
32234
+ readOnlyHint: false,
32235
+ destructiveHint: false,
32236
+ idempotentHint: false,
32237
+ openWorldHint: false
32238
+ }
32239
+ },
32240
+ archiveHandler(backend)
32241
+ );
31609
32242
  server.registerTool(
31610
32243
  "beans_view",
31611
32244
  {
@@ -31615,7 +32248,7 @@ function registerTools(server, backend) {
31615
32248
  beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31616
32249
  beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional()
31617
32250
  }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31618
- message: "Either beanId or beanIds must be provided"
32251
+ message: `Either beanId or beanIds must be provided. ${BEAN_ID_HINT}`
31619
32252
  }),
31620
32253
  annotations: {
31621
32254
  readOnlyHint: true,
@@ -31636,7 +32269,8 @@ function registerTools(server, backend) {
31636
32269
  type: external_exports3.string().min(1).max(MAX_METADATA_LENGTH),
31637
32270
  status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31638
32271
  priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31639
- description: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32272
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Body markdown content"),
32273
+ description: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Deprecated alias for body"),
31640
32274
  parent: external_exports3.string().max(MAX_ID_LENGTH).optional()
31641
32275
  }),
31642
32276
  annotations: {
@@ -31654,7 +32288,7 @@ function registerTools(server, backend) {
31654
32288
  title: "Edit Bean Metadata",
31655
32289
  description: "Update bean metadata fields (status/type/priority/parent/blocking).",
31656
32290
  inputSchema: external_exports3.object({
31657
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32291
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31658
32292
  status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31659
32293
  type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31660
32294
  priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
@@ -31662,7 +32296,11 @@ function registerTools(server, backend) {
31662
32296
  clearParent: external_exports3.boolean().optional(),
31663
32297
  blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31664
32298
  blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional()
31665
- }),
32299
+ }).superRefine((input, ctx) => {
32300
+ if (!input.beanId) {
32301
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32302
+ }
32303
+ }).transform((input) => ({ ...input, beanId: input.beanId })),
31666
32304
  annotations: {
31667
32305
  readOnlyHint: false,
31668
32306
  destructiveHint: false,
@@ -31678,10 +32316,14 @@ function registerTools(server, backend) {
31678
32316
  title: "Reopen Bean",
31679
32317
  description: "Reopen a completed or scrapped bean into a non-closed status.",
31680
32318
  inputSchema: external_exports3.object({
31681
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32319
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31682
32320
  requiredCurrentStatus: external_exports3.enum(["completed", "scrapped"]),
31683
32321
  targetStatus: external_exports3.string().max(MAX_METADATA_LENGTH).default("todo")
31684
- }),
32322
+ }).superRefine((input, ctx) => {
32323
+ if (!input.beanId) {
32324
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32325
+ }
32326
+ }).transform((input) => ({ ...input, beanId: input.beanId })),
31685
32327
  annotations: {
31686
32328
  readOnlyHint: false,
31687
32329
  destructiveHint: false,
@@ -31697,7 +32339,7 @@ function registerTools(server, backend) {
31697
32339
  title: "Update Bean",
31698
32340
  description: "Update bean metadata fields (status/type/priority/parent/blocking). Consolidated replacement for per-field update tools.",
31699
32341
  inputSchema: external_exports3.object({
31700
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
32342
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31701
32343
  status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31702
32344
  type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
31703
32345
  priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
@@ -31714,12 +32356,16 @@ function registerTools(server, backend) {
31714
32356
  })
31715
32357
  ).optional(),
31716
32358
  ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
32359
+ }).superRefine((input, ctx) => {
32360
+ if (!input.beanId) {
32361
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32362
+ }
31717
32363
  }).refine(
31718
32364
  (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
31719
32365
  {
31720
32366
  message: "body cannot be combined with bodyAppend/bodyReplace"
31721
32367
  }
31722
- ),
32368
+ ).transform((input) => ({ ...input, beanId: input.beanId })),
31723
32369
  annotations: {
31724
32370
  readOnlyHint: false,
31725
32371
  destructiveHint: false,
@@ -31739,7 +32385,7 @@ function registerTools(server, backend) {
31739
32385
  beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional(),
31740
32386
  force: external_exports3.boolean().default(false)
31741
32387
  }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31742
- message: "Either beanId or beanIds must be provided"
32388
+ message: `Either beanId or beanIds must be provided. ${BEAN_ID_HINT}`
31743
32389
  }),
31744
32390
  annotations: {
31745
32391
  readOnlyHint: false,
@@ -31750,25 +32396,122 @@ function registerTools(server, backend) {
31750
32396
  },
31751
32397
  deleteHandler(backend)
31752
32398
  );
32399
+ const beanCreateItemSchema = external_exports3.object({
32400
+ title: external_exports3.string().min(1).max(MAX_TITLE_LENGTH),
32401
+ type: external_exports3.string().min(1).max(MAX_METADATA_LENGTH),
32402
+ status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32403
+ priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32404
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Body markdown content"),
32405
+ description: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional().describe("Deprecated alias for body"),
32406
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Override the top-level parent for this item")
32407
+ });
32408
+ server.registerTool(
32409
+ "beans_bulk_create",
32410
+ {
32411
+ title: "Bulk Create Beans",
32412
+ description: "Create multiple beans in one call. Optionally assign all of them (or a subset) to a shared parent.",
32413
+ inputSchema: external_exports3.object({
32414
+ beans: external_exports3.array(beanCreateItemSchema).min(1),
32415
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Default parent ID applied to any bean that does not specify its own parent")
32416
+ }),
32417
+ annotations: {
32418
+ readOnlyHint: false,
32419
+ destructiveHint: false,
32420
+ idempotentHint: false,
32421
+ openWorldHint: false
32422
+ }
32423
+ },
32424
+ bulkCreateHandler(backend)
32425
+ );
32426
+ const beanUpdateItemSchema = external_exports3.object({
32427
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
32428
+ status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32429
+ type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32430
+ priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32431
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Override the top-level parent for this item"),
32432
+ clearParent: external_exports3.boolean().optional(),
32433
+ blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
32434
+ blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
32435
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32436
+ bodyAppend: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32437
+ bodyReplace: external_exports3.array(external_exports3.object({ old: external_exports3.string().max(MAX_DESCRIPTION_LENGTH), new: external_exports3.string().max(MAX_DESCRIPTION_LENGTH) })).optional(),
32438
+ ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
32439
+ }).superRefine((input, ctx) => {
32440
+ if (!input.beanId) {
32441
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32442
+ }
32443
+ }).refine(
32444
+ (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
32445
+ { message: "body cannot be combined with bodyAppend/bodyReplace" }
32446
+ ).transform((input) => ({ ...input, beanId: input.beanId }));
32447
+ server.registerTool(
32448
+ "beans_bulk_update",
32449
+ {
32450
+ title: "Bulk Update Beans",
32451
+ description: "Update multiple beans in one call. Optionally assign all of them (or a subset) to a shared parent.",
32452
+ inputSchema: external_exports3.object({
32453
+ beans: external_exports3.array(beanUpdateItemSchema).min(1),
32454
+ parent: external_exports3.string().max(MAX_ID_LENGTH).optional().describe("Default parent ID applied to any bean that does not specify its own parent")
32455
+ }),
32456
+ annotations: {
32457
+ readOnlyHint: false,
32458
+ destructiveHint: false,
32459
+ idempotentHint: false,
32460
+ openWorldHint: false
32461
+ }
32462
+ },
32463
+ bulkUpdateHandler(backend)
32464
+ );
32465
+ server.registerTool(
32466
+ "beans_complete_tasks",
32467
+ {
32468
+ title: "Complete Markdown Tasks",
32469
+ description: "Mark all markdown checklist tasks within a bean as completed.",
32470
+ inputSchema: external_exports3.object({
32471
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional()
32472
+ }).superRefine((input, ctx) => {
32473
+ if (!input.beanId) {
32474
+ ctx.addIssue({ code: external_exports3.ZodIssueCode.custom, path: ["beanId"], message: BEAN_ID_HINT });
32475
+ }
32476
+ }).transform((input) => ({ ...input, beanId: input.beanId })),
32477
+ annotations: {
32478
+ readOnlyHint: false,
32479
+ destructiveHint: false,
32480
+ idempotentHint: true,
32481
+ openWorldHint: false
32482
+ }
32483
+ },
32484
+ completeTasksHandler(backend)
32485
+ );
31753
32486
  server.registerTool(
31754
32487
  "beans_query",
31755
32488
  {
31756
32489
  title: "Query Beans",
31757
32490
  description: "Unified query tool for refresh, filter, search, and sort operations.",
31758
32491
  inputSchema: external_exports3.object({
31759
- operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config"]).default("refresh"),
32492
+ operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config", "graphql"]).default("refresh"),
31760
32493
  mode: external_exports3.enum(["status-priority-type-title", "updated", "created", "id"]).optional(),
31761
32494
  statuses: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
31762
32495
  types: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
31763
32496
  search: external_exports3.string().max(MAX_TITLE_LENGTH).optional(),
31764
32497
  includeClosed: external_exports3.boolean().optional(),
31765
32498
  tags: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
32499
+ graphql: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
32500
+ variables: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional(),
31766
32501
  writeToWorkspaceInstructions: external_exports3.boolean().optional()
32502
+ }).superRefine((input, ctx) => {
32503
+ if (input.operation === "graphql" && (!input.graphql || input.graphql.trim().length === 0)) {
32504
+ ctx.addIssue({
32505
+ code: external_exports3.ZodIssueCode.custom,
32506
+ path: ["graphql"],
32507
+ message: "graphql query string is required when operation is graphql"
32508
+ });
32509
+ }
31767
32510
  }),
31768
32511
  annotations: {
31769
- readOnlyHint: true,
32512
+ readOnlyHint: false,
31770
32513
  destructiveHint: false,
31771
- idempotentHint: true,
32514
+ idempotentHint: false,
31772
32515
  openWorldHint: false
31773
32516
  }
31774
32517
  },
@@ -31780,10 +32523,33 @@ function registerTools(server, backend) {
31780
32523
  title: "Bean File Operations",
31781
32524
  description: "Read, create, edit, or delete files under .beans (operation param).",
31782
32525
  inputSchema: external_exports3.object({
31783
- operation: external_exports3.enum(["read", "edit", "create", "delete"]),
32526
+ operation: external_exports3.enum(["read", "edit", "create", "delete", "update_frontmatter"]),
31784
32527
  path: external_exports3.string().min(1).max(MAX_PATH_LENGTH),
31785
32528
  content: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31786
- overwrite: external_exports3.boolean().optional()
32529
+ overwrite: external_exports3.boolean().optional(),
32530
+ fields: external_exports3.object({
32531
+ title: external_exports3.string().max(MAX_TITLE_LENGTH).optional(),
32532
+ status: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32533
+ type: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32534
+ priority: external_exports3.string().max(MAX_METADATA_LENGTH).optional(),
32535
+ parent_id: external_exports3.string().max(MAX_ID_LENGTH).nullable().optional(),
32536
+ tags: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
32537
+ blocking_ids: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).nullable().optional(),
32538
+ blocked_by_ids: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).nullable().optional(),
32539
+ pr: external_exports3.string().max(MAX_TITLE_LENGTH).nullable().optional(),
32540
+ branch: external_exports3.string().max(MAX_TITLE_LENGTH).nullable().optional()
32541
+ }).optional()
32542
+ }).superRefine((input, ctx) => {
32543
+ if (input.operation === "update_frontmatter") {
32544
+ const fieldCount = Object.values(input.fields || {}).filter((value) => value !== void 0).length;
32545
+ if (fieldCount === 0) {
32546
+ ctx.addIssue({
32547
+ code: external_exports3.ZodIssueCode.custom,
32548
+ path: ["fields"],
32549
+ message: "At least one frontmatter field update is required"
32550
+ });
32551
+ }
32552
+ }
31787
32553
  }),
31788
32554
  annotations: {
31789
32555
  readOnlyHint: false,
@@ -31823,6 +32589,18 @@ var MutableBackend = class {
31823
32589
  init(prefix) {
31824
32590
  return this.inner.init(prefix);
31825
32591
  }
32592
+ archive() {
32593
+ if (typeof this.inner.archive === "function") {
32594
+ return this.inner.archive();
32595
+ }
32596
+ throw new TypeError("Archive is not supported by backend");
32597
+ }
32598
+ queryGraphql(query, variables) {
32599
+ if (typeof this.inner.queryGraphql === "function") {
32600
+ return this.inner.queryGraphql(query, variables);
32601
+ }
32602
+ throw new TypeError("GraphQL passthrough is not supported by backend");
32603
+ }
31826
32604
  list(opts) {
31827
32605
  return this.inner.list(opts);
31828
32606
  }
@@ -31835,6 +32613,12 @@ var MutableBackend = class {
31835
32613
  delete(id) {
31836
32614
  return this.inner.delete(id);
31837
32615
  }
32616
+ bulkCreate(beans, defaultParent) {
32617
+ return this.inner.bulkCreate(beans, defaultParent);
32618
+ }
32619
+ bulkUpdate(beans, defaultParent) {
32620
+ return this.inner.bulkUpdate(beans, defaultParent);
32621
+ }
31838
32622
  openConfig() {
31839
32623
  return this.inner.openConfig();
31840
32624
  }
@@ -31856,6 +32640,9 @@ var MutableBackend = class {
31856
32640
  editBeanFile(path, content) {
31857
32641
  return this.inner.editBeanFile(path, content);
31858
32642
  }
32643
+ updateBeanFrontmatter(path, updates) {
32644
+ return this.inner.updateBeanFrontmatter(path, updates);
32645
+ }
31859
32646
  createBeanFile(path, content, opts) {
31860
32647
  return this.inner.createBeanFile(path, content, opts);
31861
32648
  }
@@ -31922,6 +32709,16 @@ function parseCliArgs(argv) {
31922
32709
  const envPort = Number.parseInt(process.env.BEANS_VSCODE_MCP_PORT || process.env.BEANS_MCP_PORT || "", 10);
31923
32710
  let port = Number.isInteger(envPort) && envPort > 0 ? envPort : DEFAULT_MCP_PORT;
31924
32711
  let logDir;
32712
+ const parseStrictPositiveInt = (raw, flagName) => {
32713
+ if (!/^\d+$/.test(raw)) {
32714
+ throw new Error(`Invalid value for ${flagName}: ${raw}`);
32715
+ }
32716
+ const parsed = Number.parseInt(raw, 10);
32717
+ if (!Number.isInteger(parsed) || parsed <= 0) {
32718
+ throw new Error(`Invalid value for ${flagName}: ${raw}`);
32719
+ }
32720
+ return parsed;
32721
+ };
31925
32722
  for (let i = 0; i < argv.length; i += 1) {
31926
32723
  const arg = argv[i];
31927
32724
  if ((arg === "--workspace" || arg === "--workspace-root") && argv[i + 1]) {
@@ -31935,10 +32732,7 @@ function parseCliArgs(argv) {
31935
32732
  }
31936
32733
  i += 1;
31937
32734
  } else if (arg === "--port" && argv[i + 1]) {
31938
- const parsedPort = Number.parseInt(argv[i + 1], 10);
31939
- if (Number.isInteger(parsedPort) && parsedPort > 0) {
31940
- port = parsedPort;
31941
- }
32735
+ port = parseStrictPositiveInt(argv[i + 1], "--port");
31942
32736
  i += 1;
31943
32737
  } else if (arg === "--log-dir" && argv[i + 1]) {
31944
32738
  logDir = argv[i + 1];
@@ -32001,6 +32795,7 @@ export {
32001
32795
  DEFAULT_MCP_PORT,
32002
32796
  MAX_ID_LENGTH,
32003
32797
  MAX_METADATA_LENGTH,
32798
+ MAX_PATH_LENGTH,
32004
32799
  MAX_TITLE_LENGTH,
32005
32800
  createBeansMcpServer,
32006
32801
  isPathWithinRoot,