@selfagency/beans-mcp 0.1.3 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +63 -6
  2. package/{dist/beans-mcp-server.cjs → beans-mcp-server.cjs} +269 -34
  3. package/{dist/index.cjs → index.cjs} +269 -34
  4. package/{dist/index.d.ts → index.d.ts} +19 -1
  5. package/{dist/index.js → index.js} +269 -34
  6. package/package.json +28 -64
  7. package/.beans.yml +0 -6
  8. package/.claude/settings.local.json +0 -18
  9. package/.editorconfig +0 -13
  10. package/.github/dependabot.yml +0 -11
  11. package/.github/workflows/release.yml +0 -235
  12. package/.github/workflows/test.yml +0 -84
  13. package/.husky/pre-commit +0 -1
  14. package/.nvmrc +0 -1
  15. package/.oxfmtrc.json +0 -11
  16. package/.oxlintrc.json +0 -37
  17. package/.vscode/settings.json +0 -3
  18. package/CHANGELOG.md +0 -160
  19. package/CONTRIBUTING.md +0 -139
  20. package/LICENSE.txt +0 -21
  21. package/codeql/codeql-custom-queries-actions/README.md +0 -14
  22. package/codeql/codeql-custom-queries-actions/codeql-pack.lock.yml +0 -32
  23. package/codeql/codeql-custom-queries-actions/codeql-pack.yml +0 -7
  24. package/codeql/codeql-custom-queries-actions/qlpack.yml +0 -6
  25. package/codeql/codeql-custom-queries-actions/queries/github-script-without-tojson.ql +0 -18
  26. package/codeql/codeql-custom-queries-actions/queries/strict-external-action-pinning.ql +0 -18
  27. package/codeql/codeql-custom-queries-javascript/README.md +0 -14
  28. package/codeql/codeql-custom-queries-javascript/codeql-pack.lock.yml +0 -30
  29. package/codeql/codeql-custom-queries-javascript/codeql-pack.yml +0 -7
  30. package/codeql/codeql-custom-queries-javascript/qlpack.yml +0 -6
  31. package/codeql/codeql-custom-queries-javascript/queries/child-process-shell-apis.ql +0 -26
  32. package/codeql/codeql-custom-queries-javascript/queries/innerhtml-assignment.ql +0 -24
  33. package/dist/README.md +0 -307
  34. package/dist/beans-mcp-server.cjs.map +0 -1
  35. package/dist/index.cjs.map +0 -1
  36. package/dist/index.js.map +0 -1
  37. package/dist/package.json +0 -43
  38. package/pnpm-workspace.yaml +0 -2
  39. package/scripts/release.js +0 -433
  40. package/scripts/write-dist-package.js +0 -53
  41. package/src/cli.ts +0 -14
  42. package/src/index.ts +0 -21
  43. package/src/internal/graphql.ts +0 -33
  44. package/src/internal/queryHelpers.ts +0 -157
  45. package/src/server/BeansMcpServer.ts +0 -623
  46. package/src/server/backend.ts +0 -364
  47. package/src/test/BeansMcpServer.test.ts +0 -514
  48. package/src/test/handlers.unit.test.ts +0 -201
  49. package/src/test/parseCliArgs.test.ts +0 -69
  50. package/src/test/protocol.e2e.test.ts +0 -884
  51. package/src/test/queryHelpers.test.ts +0 -524
  52. package/src/test/startBeansMcpServer.test.ts +0 -146
  53. package/src/test/tools-integration.test.ts +0 -912
  54. package/src/test/utils.test.ts +0 -81
  55. package/src/types.ts +0 -46
  56. package/src/utils.ts +0 -20
  57. package/tsconfig.json +0 -24
  58. package/tsup.config.ts +0 -42
  59. package/vitest.config.ts +0 -18
@@ -22744,7 +22744,7 @@ var init_utils = __esm({
22744
22744
  });
22745
22745
 
22746
22746
  // src/internal/graphql.ts
22747
- var LIST_BEANS_QUERY, CREATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION, DELETE_BEAN_MUTATION;
22747
+ var LIST_BEANS_QUERY, CREATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION_WITH_IF_MATCH, DELETE_BEAN_MUTATION;
22748
22748
  var init_graphql = __esm({
22749
22749
  "src/internal/graphql.ts"() {
22750
22750
  "use strict";
@@ -22762,6 +22762,11 @@ var init_graphql = __esm({
22762
22762
  mutation($id: ID!, $input: UpdateBeanInput!) {
22763
22763
  updateBean(id: $id, input: $input) { id slug path title body status type priority tags parentId blockingIds blockedByIds createdAt updatedAt etag }
22764
22764
  }
22765
+ `;
22766
+ UPDATE_BEAN_MUTATION_WITH_IF_MATCH = `
22767
+ mutation($id: ID!, $input: UpdateBeanInput!, $ifMatch: String!) {
22768
+ updateBean(id: $id, input: $input, ifMatch: $ifMatch) { id slug path title body status type priority tags parentId blockingIds blockedByIds createdAt updatedAt etag }
22769
+ }
22765
22770
  `;
22766
22771
  DELETE_BEAN_MUTATION = `
22767
22772
  mutation($id: ID!) {
@@ -22919,10 +22924,50 @@ Output: ${stdout.slice(0, 1e3)}`
22919
22924
  if (updates.body !== void 0) {
22920
22925
  updateInput.body = updates.body;
22921
22926
  }
22922
- const { data, errors } = await this.executeGraphQL(UPDATE_BEAN_MUTATION, {
22923
- id: beanId,
22924
- input: updateInput
22925
- });
22927
+ const bodyMod = {};
22928
+ if (updates.bodyAppend !== void 0) {
22929
+ bodyMod.append = updates.bodyAppend;
22930
+ }
22931
+ if (Array.isArray(updates.bodyReplace) && updates.bodyReplace.length > 0) {
22932
+ bodyMod.replace = updates.bodyReplace;
22933
+ }
22934
+ if (Object.keys(bodyMod).length > 0) {
22935
+ updateInput.bodyMod = bodyMod;
22936
+ }
22937
+ let data;
22938
+ let errors;
22939
+ if (updates.ifMatch) {
22940
+ try {
22941
+ const res = await this.executeGraphQL(UPDATE_BEAN_MUTATION_WITH_IF_MATCH, {
22942
+ id: beanId,
22943
+ input: updateInput,
22944
+ ifMatch: updates.ifMatch
22945
+ });
22946
+ data = res.data;
22947
+ errors = res.errors;
22948
+ } catch (error48) {
22949
+ const message = error48.message || "";
22950
+ const unsupportedIfMatch = /unknown argument.*ifMatch|unknown field.*ifMatch|ifMatch.*not defined|field .*updateBean.* argument .*ifMatch/i.test(
22951
+ message
22952
+ );
22953
+ if (!unsupportedIfMatch) {
22954
+ throw error48;
22955
+ }
22956
+ const fallback = await this.executeGraphQL(UPDATE_BEAN_MUTATION, {
22957
+ id: beanId,
22958
+ input: updateInput
22959
+ });
22960
+ data = fallback.data;
22961
+ errors = fallback.errors;
22962
+ }
22963
+ } else {
22964
+ const res = await this.executeGraphQL(UPDATE_BEAN_MUTATION, {
22965
+ id: beanId,
22966
+ input: updateInput
22967
+ });
22968
+ data = res.data;
22969
+ errors = res.errors;
22970
+ }
22926
22971
  if (errors && errors.length > 0) {
22927
22972
  throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
22928
22973
  }
@@ -22942,6 +22987,21 @@ Output: ${stdout.slice(0, 1e3)}`
22942
22987
  const content = await readFile(configPath, "utf8");
22943
22988
  return { configPath, content };
22944
22989
  }
22990
+ async primeInstructions() {
22991
+ const { stdout } = await execFileAsync(this.cliPath, ["prime"], {
22992
+ cwd: this.workspaceRoot,
22993
+ env: this.getSafeEnv(),
22994
+ maxBuffer: 10 * 1024 * 1024,
22995
+ timeout: 3e4
22996
+ });
22997
+ return stdout.trim();
22998
+ }
22999
+ async writeInstructions(instructions) {
23000
+ const instructionsPath = join(this.workspaceRoot, ".github", "instructions", "beans-prime.instructions.md");
23001
+ await mkdir(dirname(instructionsPath), { recursive: true });
23002
+ await writeFile(instructionsPath, instructions, "utf8");
23003
+ return instructionsPath;
23004
+ }
22945
23005
  async graphqlSchema() {
22946
23006
  const { stdout } = await execFileAsync(this.cliPath, ["graphql", "--schema"], {
22947
23007
  cwd: this.workspaceRoot,
@@ -31069,6 +31129,10 @@ var EMPTY_COMPLETION_RESULT = {
31069
31129
  }
31070
31130
  };
31071
31131
 
31132
+ // src/server/BeansMcpServer.ts
31133
+ import { execFile as execFile2 } from "child_process";
31134
+ import { promisify as promisify2 } from "util";
31135
+
31072
31136
  // src/internal/queryHelpers.ts
31073
31137
  function sortBeansInternal(beans, mode) {
31074
31138
  const sorted = [...beans];
@@ -31124,15 +31188,23 @@ async function handleQueryOperation(backend, params) {
31124
31188
  const { operation, mode, statuses, types, search, tags, writeToWorkspaceInstructions, includeClosed } = params;
31125
31189
  if (operation === "llm_context") {
31126
31190
  const graphqlSchema = typeof backend.graphqlSchema === "function" ? await backend.graphqlSchema() : "";
31127
- const instructionsPath = writeToWorkspaceInstructions && typeof backend.writeInstructions === "function" ? await backend.writeInstructions("") : null;
31191
+ let generatedInstructions = "";
31192
+ if (typeof backend.primeInstructions === "function") {
31193
+ try {
31194
+ generatedInstructions = await backend.primeInstructions();
31195
+ } catch {
31196
+ generatedInstructions = "";
31197
+ }
31198
+ }
31199
+ const instructionsPath = writeToWorkspaceInstructions && typeof backend.writeInstructions === "function" ? await backend.writeInstructions(generatedInstructions) : null;
31128
31200
  return {
31129
31201
  content: [
31130
31202
  {
31131
31203
  type: "text",
31132
- text: JSON.stringify({ graphqlSchema, generatedInstructions: "", instructionsPath }, null, 2)
31204
+ text: JSON.stringify({ graphqlSchema, generatedInstructions, instructionsPath }, null, 2)
31133
31205
  }
31134
31206
  ],
31135
- structuredContent: { graphqlSchema, generatedInstructions: "", instructionsPath }
31207
+ structuredContent: { graphqlSchema, generatedInstructions, instructionsPath }
31136
31208
  };
31137
31209
  }
31138
31210
  if (operation === "open_config") {
@@ -31178,6 +31250,31 @@ async function handleQueryOperation(backend, params) {
31178
31250
  structuredContent: { query: search, count: beans2.length, beans: beans2 }
31179
31251
  };
31180
31252
  }
31253
+ if (operation === "ready") {
31254
+ const allBeans = await backend.list();
31255
+ const byId = new Map(allBeans.map((bean) => [bean.id, bean]));
31256
+ const candidates = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
31257
+ const readyBeans = candidates.filter((bean) => {
31258
+ if (bean.status !== "todo") {
31259
+ return false;
31260
+ }
31261
+ const blockedBy = bean.blockedByIds || [];
31262
+ if (blockedBy.length === 0) {
31263
+ return true;
31264
+ }
31265
+ return blockedBy.every((blockerId) => {
31266
+ const blocker = byId.get(blockerId);
31267
+ if (!blocker) {
31268
+ return false;
31269
+ }
31270
+ return blocker.status === "completed" || blocker.status === "scrapped";
31271
+ });
31272
+ });
31273
+ return {
31274
+ content: [{ type: "text", text: JSON.stringify({ count: readyBeans.length, beans: readyBeans }, null, 2) }],
31275
+ structuredContent: { count: readyBeans.length, beans: readyBeans }
31276
+ };
31277
+ }
31181
31278
  const beans = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
31182
31279
  const sorted = sortBeansInternal(beans, mode ?? "status-priority-type-title");
31183
31280
  return {
@@ -31200,7 +31297,7 @@ init_utils();
31200
31297
  // package.json
31201
31298
  var package_default = {
31202
31299
  name: "@selfagency/beans-mcp",
31203
- version: "0.1.3",
31300
+ version: "0.4.2",
31204
31301
  private: false,
31205
31302
  description: "MCP (Model Context Protocol) server for Beans issue tracker",
31206
31303
  author: {
@@ -31216,6 +31313,7 @@ var package_default = {
31216
31313
  type: "git",
31217
31314
  url: "git+https://github.com/selfagency/beans-mcp.git"
31218
31315
  },
31316
+ mcpName: "io.github.selfagency/beans-mcp",
31219
31317
  keywords: [
31220
31318
  "beans",
31221
31319
  "mcp",
@@ -31254,18 +31352,18 @@ var package_default = {
31254
31352
  devDependencies: {
31255
31353
  "@modelcontextprotocol/sdk": "^1.27.1",
31256
31354
  "@octokit/rest": "^22.0.1",
31257
- "@types/node": "^20.19.0",
31258
- "@vitest/coverage-v8": "^4.0.18",
31259
- "@vitest/ui": "4.0.18",
31355
+ "@types/node": "25.5.0",
31356
+ "@vitest/coverage-v8": "^4.1.0",
31357
+ "@vitest/ui": "4.1.0",
31260
31358
  husky: "^9.1.7",
31261
- "lint-staged": "^16.2.7",
31359
+ "lint-staged": "^16.3.3",
31262
31360
  ora: "^9.3.0",
31263
- oxfmt: "^0.35.0",
31264
- oxlint: "^1.50.0",
31265
- "oxlint-tsgolint": "^0.15.0",
31361
+ oxfmt: "^0.40.0",
31362
+ oxlint: "^1.55.0",
31363
+ "oxlint-tsgolint": "^0.16.0",
31266
31364
  tsup: "8.5.1",
31267
31365
  typescript: "^5.9.3",
31268
- vitest: "4.0.18",
31366
+ vitest: "4.1.0",
31269
31367
  zod: "4.3.6",
31270
31368
  zx: "^8.8.5"
31271
31369
  },
@@ -31281,6 +31379,45 @@ var package_default = {
31281
31379
  };
31282
31380
 
31283
31381
  // src/server/BeansMcpServer.ts
31382
+ var execFileAsync2 = promisify2(execFile2);
31383
+ var PACKAGE_VERSION = package_default.version ?? "0.0.0-dev";
31384
+ function getSafeCliEnv(env) {
31385
+ const whitelist = ["PATH", "HOME", "USER", "LANG", "LC_ALL", "LC_CTYPE", "SHELL"];
31386
+ const safeEnv = {};
31387
+ for (const key of whitelist) {
31388
+ if (env[key]) {
31389
+ safeEnv[key] = env[key];
31390
+ }
31391
+ }
31392
+ for (const key in env) {
31393
+ if (key.startsWith("BEANS_")) {
31394
+ safeEnv[key] = env[key];
31395
+ }
31396
+ }
31397
+ return safeEnv;
31398
+ }
31399
+ function extractVersionFromOutput(output) {
31400
+ const trimmed = output.trim();
31401
+ if (!trimmed) {
31402
+ return null;
31403
+ }
31404
+ const match = trimmed.match(/(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
31405
+ return match?.[1] ?? null;
31406
+ }
31407
+ async function detectBeansCliVersion(cliPath, workspaceRoot) {
31408
+ try {
31409
+ const { stdout, stderr } = await execFileAsync2(cliPath, ["version"], {
31410
+ cwd: workspaceRoot,
31411
+ env: getSafeCliEnv(process.env),
31412
+ maxBuffer: 1024 * 1024,
31413
+ timeout: 5e3
31414
+ });
31415
+ return extractVersionFromOutput(`${stdout}
31416
+ ${stderr}`);
31417
+ } catch {
31418
+ return null;
31419
+ }
31420
+ }
31284
31421
  async function getBeanById(backend, beanId) {
31285
31422
  try {
31286
31423
  const beans = await backend.list();
@@ -31300,7 +31437,35 @@ function initHandler(backend) {
31300
31437
  };
31301
31438
  }
31302
31439
  function viewHandler(backend) {
31303
- return async ({ beanId }) => makeTextAndStructured({ bean: await getBeanById(backend, beanId) });
31440
+ return async ({ beanId, beanIds }) => {
31441
+ const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
31442
+ if (ids.length === 0) {
31443
+ throw new Error("Either beanId or beanIds must be provided");
31444
+ }
31445
+ if (ids.length === 1) {
31446
+ const bean = await getBeanById(backend, ids[0]);
31447
+ return makeTextAndStructured({ bean });
31448
+ }
31449
+ const beans = await backend.list();
31450
+ const byId = new Map(beans.map((b) => [b.id, b]));
31451
+ const found = ids.map((id) => byId.get(id)).filter(Boolean);
31452
+ const missingBeanIds = ids.filter((id) => !byId.has(id));
31453
+ return makeTextAndStructured({ beans: found, missingBeanIds, count: found.length, requestedCount: ids.length });
31454
+ };
31455
+ }
31456
+ async function checkVersionCompatibility(cliPath, workspaceRoot, detector) {
31457
+ const detectedBeansVersion = await detector(cliPath, workspaceRoot);
31458
+ if (!detectedBeansVersion) {
31459
+ console.error(
31460
+ `[beans-mcp] warning: unable to determine Beans CLI version from \`${cliPath}\`; proceeding without version compatibility checks.`
31461
+ );
31462
+ return;
31463
+ }
31464
+ if (detectedBeansVersion !== PACKAGE_VERSION) {
31465
+ console.error(
31466
+ `[beans-mcp] warning: version mismatch detected (beans=${detectedBeansVersion}, beans-mcp=${PACKAGE_VERSION}); continuing startup.`
31467
+ );
31468
+ }
31304
31469
  }
31305
31470
  function createHandler(backend) {
31306
31471
  return async (input) => makeTextAndStructured({ bean: await backend.create(input) });
@@ -31336,17 +31501,56 @@ function updateHandler(backend) {
31336
31501
  clearParent: input.clearParent,
31337
31502
  blocking: input.blocking,
31338
31503
  blockedBy: input.blockedBy,
31339
- body: input.body
31504
+ body: input.body,
31505
+ bodyAppend: input.bodyAppend,
31506
+ bodyReplace: input.bodyReplace,
31507
+ ifMatch: input.ifMatch
31340
31508
  })
31341
31509
  });
31342
31510
  }
31343
31511
  function deleteHandler(backend) {
31344
- return async ({ beanId, force }) => {
31345
- const bean = await getBeanById(backend, beanId);
31346
- if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31347
- throw new Error("Only draft and scrapped beans are deletable unless force=true");
31512
+ return async ({ beanId, beanIds, force }) => {
31513
+ const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
31514
+ if (ids.length === 0) {
31515
+ throw new Error("Either beanId or beanIds must be provided");
31516
+ }
31517
+ if (ids.length === 1) {
31518
+ const bean = await getBeanById(backend, ids[0]);
31519
+ if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31520
+ throw new Error("Only draft and scrapped beans are deletable unless force=true");
31521
+ }
31522
+ return makeTextAndStructured(await backend.delete(ids[0]));
31348
31523
  }
31349
- return makeTextAndStructured(await backend.delete(beanId));
31524
+ const beans = await backend.list();
31525
+ const byId = new Map(beans.map((b) => [b.id, b]));
31526
+ const results = [];
31527
+ for (const id of ids) {
31528
+ const bean = byId.get(id);
31529
+ if (!bean) {
31530
+ results.push({ beanId: id, deleted: false, error: "Bean not found" });
31531
+ continue;
31532
+ }
31533
+ if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
31534
+ results.push({
31535
+ beanId: id,
31536
+ deleted: false,
31537
+ error: "Only draft and scrapped beans are deletable unless force=true"
31538
+ });
31539
+ continue;
31540
+ }
31541
+ try {
31542
+ await backend.delete(id);
31543
+ results.push({ beanId: id, deleted: true });
31544
+ } catch (error48) {
31545
+ results.push({ beanId: id, deleted: false, error: error48.message });
31546
+ }
31547
+ }
31548
+ return makeTextAndStructured({
31549
+ results,
31550
+ requestedCount: ids.length,
31551
+ deletedCount: results.filter((r) => r.deleted).length,
31552
+ failedCount: results.filter((r) => !r.deleted).length
31553
+ });
31350
31554
  };
31351
31555
  }
31352
31556
  function queryHandler(backend) {
@@ -31407,7 +31611,12 @@ function registerTools(server, backend) {
31407
31611
  {
31408
31612
  title: "View Bean",
31409
31613
  description: "Fetch full bean details by ID.",
31410
- inputSchema: external_exports3.object({ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH) }),
31614
+ inputSchema: external_exports3.object({
31615
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31616
+ beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional()
31617
+ }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31618
+ message: "Either beanId or beanIds must be provided"
31619
+ }),
31411
31620
  annotations: {
31412
31621
  readOnlyHint: true,
31413
31622
  destructiveHint: false,
@@ -31496,8 +31705,21 @@ function registerTools(server, backend) {
31496
31705
  clearParent: external_exports3.boolean().optional(),
31497
31706
  blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31498
31707
  blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
31499
- body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional()
31500
- }),
31708
+ body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31709
+ bodyAppend: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
31710
+ bodyReplace: external_exports3.array(
31711
+ external_exports3.object({
31712
+ old: external_exports3.string().max(MAX_DESCRIPTION_LENGTH),
31713
+ new: external_exports3.string().max(MAX_DESCRIPTION_LENGTH)
31714
+ })
31715
+ ).optional(),
31716
+ ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
31717
+ }).refine(
31718
+ (input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
31719
+ {
31720
+ message: "body cannot be combined with bodyAppend/bodyReplace"
31721
+ }
31722
+ ),
31501
31723
  annotations: {
31502
31724
  readOnlyHint: false,
31503
31725
  destructiveHint: false,
@@ -31513,8 +31735,11 @@ function registerTools(server, backend) {
31513
31735
  title: "Delete Bean",
31514
31736
  description: "Delete a bean (intended for draft/scrapped beans).",
31515
31737
  inputSchema: external_exports3.object({
31516
- beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
31738
+ beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
31739
+ beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional(),
31517
31740
  force: external_exports3.boolean().default(false)
31741
+ }).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
31742
+ message: "Either beanId or beanIds must be provided"
31518
31743
  }),
31519
31744
  annotations: {
31520
31745
  readOnlyHint: false,
@@ -31531,7 +31756,7 @@ function registerTools(server, backend) {
31531
31756
  title: "Query Beans",
31532
31757
  description: "Unified query tool for refresh, filter, search, and sort operations.",
31533
31758
  inputSchema: external_exports3.object({
31534
- operation: external_exports3.enum(["refresh", "filter", "search", "sort", "llm_context", "open_config"]).default("refresh"),
31759
+ operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config"]).default("refresh"),
31535
31760
  mode: external_exports3.enum(["status-priority-type-title", "updated", "created", "id"]).optional(),
31536
31761
  statuses: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
31537
31762
  types: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
@@ -31613,6 +31838,12 @@ var MutableBackend = class {
31613
31838
  openConfig() {
31614
31839
  return this.inner.openConfig();
31615
31840
  }
31841
+ primeInstructions() {
31842
+ return this.inner.primeInstructions?.() ?? Promise.resolve("");
31843
+ }
31844
+ writeInstructions(instructions) {
31845
+ return this.inner.writeInstructions?.(instructions) ?? Promise.resolve(null);
31846
+ }
31616
31847
  graphqlSchema() {
31617
31848
  return this.inner.graphqlSchema();
31618
31849
  }
@@ -31650,7 +31881,7 @@ async function createBeansMcpServer(opts) {
31650
31881
  const backend = opts.backend || new BeansCliBackend2(opts.workspaceRoot, opts.cliPath || "beans", opts.logDir);
31651
31882
  const server = new McpServer({
31652
31883
  name: opts.name || "beans-mcp-server",
31653
- version: opts.version || "0.1.0"
31884
+ version: opts.version || PACKAGE_VERSION
31654
31885
  });
31655
31886
  registerTools(server, backend);
31656
31887
  return { server, backend };
@@ -31722,17 +31953,17 @@ function parseCliArgs(argv) {
31722
31953
  }
31723
31954
  return { workspaceRoot, workspaceExplicit, cliPath, port, logDir };
31724
31955
  }
31725
- async function startBeansMcpServer(argv, _resolveRoots) {
31956
+ async function startBeansMcpServer(argv, _resolveRoots, _detectBeansVersion) {
31726
31957
  const { BeansCliBackend: BeansCliBackend2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
31727
31958
  const { StdioServerTransport: StdioServerTransport2 } = await Promise.resolve().then(() => (init_stdio2(), stdio_exports));
31728
31959
  const { workspaceRoot, workspaceExplicit, cliPath, port, logDir } = parseCliArgs(argv);
31960
+ let effectiveWorkspaceRoot = workspaceRoot;
31729
31961
  process.env.BEANS_VSCODE_MCP_PORT = String(port);
31730
31962
  process.env.BEANS_MCP_PORT = String(port);
31731
31963
  try {
31732
- const version2 = package_default.version ?? "0.0.0-dev";
31733
31964
  const workspaceLabel = workspaceExplicit ? workspaceRoot : "(auto from roots)";
31734
31965
  console.error(
31735
- `[beans-mcp] v${version2} starting (port=${port}, workspace=${workspaceLabel}, cli=${cliPath}, logDir=${logDir})`
31966
+ `[beans-mcp] v${PACKAGE_VERSION} starting (port=${port}, workspace=${workspaceLabel}, cli=${cliPath}, logDir=${logDir})`
31736
31967
  );
31737
31968
  } catch {
31738
31969
  }
@@ -31749,13 +31980,17 @@ async function startBeansMcpServer(argv, _resolveRoots) {
31749
31980
  const resolver = _resolveRoots ?? resolveWorkspaceFromRoots;
31750
31981
  const rootPath = await resolver(server);
31751
31982
  if (rootPath) {
31752
- mutable.setInner(new BeansCliBackend2(rootPath, cliPath));
31983
+ mutable.setInner(new BeansCliBackend2(rootPath, cliPath, logDir));
31984
+ effectiveWorkspaceRoot = rootPath;
31753
31985
  try {
31754
31986
  console.error(`[beans-mcp] workspace resolved from roots: ${rootPath}`);
31755
31987
  } catch {
31756
31988
  }
31757
31989
  }
31758
31990
  }
31991
+ const beansVersionDetector = _detectBeansVersion ?? detectBeansCliVersion;
31992
+ void checkVersionCompatibility(cliPath, effectiveWorkspaceRoot, beansVersionDetector).catch(() => {
31993
+ });
31759
31994
  }
31760
31995
 
31761
31996
  // src/index.ts
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@selfagency/beans-mcp",
3
- "version": "0.1.3",
4
- "private": false,
3
+ "version": "0.4.2",
5
4
  "description": "MCP (Model Context Protocol) server for Beans issue tracker",
6
- "author": {
7
- "name": "Daniel Sieradski",
8
- "email": "daniel@self.agency",
9
- "url": "https://self.agency"
10
- },
5
+ "keywords": [
6
+ "beans",
7
+ "mcp",
8
+ "model-context-protocol",
9
+ "issue-tracker",
10
+ "ai"
11
+ ],
11
12
  "homepage": "https://github.com/hmans/beans",
12
13
  "bugs": {
13
14
  "url": "https://github.com/selfagency/beans-mcp/issues"
@@ -16,65 +17,28 @@
16
17
  "type": "git",
17
18
  "url": "git+https://github.com/selfagency/beans-mcp.git"
18
19
  },
19
- "keywords": [
20
- "beans",
21
- "mcp",
22
- "model-context-protocol",
23
- "issue-tracker",
24
- "ai"
25
- ],
26
20
  "license": "MIT",
27
- "type": "module",
28
- "exports": {
29
- ".": {
30
- "types": "./dist/index.d.ts",
31
- "import": "./dist/index.js",
32
- "require": "./dist/index.cjs"
33
- }
21
+ "author": {
22
+ "name": "Daniel Sieradski",
23
+ "email": "daniel@self.agency",
24
+ "url": "https://self.agency"
34
25
  },
35
- "main": "./dist/index.cjs",
36
- "module": "./dist/index.js",
37
- "types": "./dist/index.d.ts",
26
+ "main": "./index.cjs",
27
+ "module": "./index.js",
28
+ "types": "./index.d.ts",
29
+ "files": [
30
+ "./index.cjs",
31
+ "./index.js",
32
+ "./index.d.ts"
33
+ ],
38
34
  "bin": {
39
- "beans-mcp": "dist/beans-mcp-server.cjs"
40
- },
41
- "devDependencies": {
42
- "@modelcontextprotocol/sdk": "^1.27.1",
43
- "@octokit/rest": "^22.0.1",
44
- "@types/node": "^20.19.0",
45
- "@vitest/coverage-v8": "^4.0.18",
46
- "@vitest/ui": "4.0.18",
47
- "husky": "^9.1.7",
48
- "lint-staged": "^16.2.7",
49
- "ora": "^9.3.0",
50
- "oxfmt": "^0.35.0",
51
- "oxlint": "^1.50.0",
52
- "oxlint-tsgolint": "^0.15.0",
53
- "tsup": "8.5.1",
54
- "typescript": "^5.9.3",
55
- "vitest": "4.0.18",
56
- "zod": "4.3.6",
57
- "zx": "^8.8.5"
35
+ "beans-mcp": "./beans-mcp-server.cjs"
58
36
  },
59
- "engines": {
60
- "node": ">=18"
61
- },
62
- "lint-staged": {
63
- "src/**/*.ts": [
64
- "pnpm run lint:fix",
65
- "pnpm run format"
66
- ]
37
+ "exports": {
38
+ ".": {
39
+ "import": "./index.js",
40
+ "require": "./index.cjs"
41
+ }
67
42
  },
68
- "scripts": {
69
- "build": "tsup",
70
- "format": "oxfmt",
71
- "lint:fix": "oxlint --fix",
72
- "lint": "oxlint",
73
- "postbuild": "node ./scripts/write-dist-package.js",
74
- "release": "zx ./scripts/release.js",
75
- "test:coverage": "vitest run --coverage",
76
- "test:watch": "vitest",
77
- "test": "vitest run",
78
- "type-check": "tsc --noEmit"
79
- }
80
- }
43
+ "mcpName": "io.github.selfagency/beans-mcp"
44
+ }
package/.beans.yml DELETED
@@ -1,6 +0,0 @@
1
- beans:
2
- path: .beans
3
- prefix: beans-mcp-server-
4
- id_length: 4
5
- default_status: todo
6
- default_type: task
@@ -1,18 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(node /Users/daniel/Developer/beans-mcp-server/dist/index.cjs)",
5
- "Bash(echo \"Exit: $?\")",
6
- "Bash(pnpm run build)",
7
- "Bash(node /Users/daniel/Developer/beans-mcp-server/dist/beans-mcp-server.cjs --help)",
8
- "Bash(pnpm test)",
9
- "Bash(pnpm test src/test/protocol.e2e.test.ts)",
10
- "Bash(python3 -c \"import sys,json; p=json.load\\(sys.stdin\\); print\\(p.get\\(''version''\\)\\); print\\(list\\(p.get\\(''exports'',{}\\).keys\\(\\)\\)[:15]\\)\")",
11
- "Bash(node -e \"const {z} = require\\(''./node_modules/zod''\\); const s = z.string\\(\\); console.log\\(''v4 internal marker:'', !!s._zod\\); console.log\\(''v3 internal marker:'', !!s._def\\);\")",
12
- "Bash(pnpm run type-check)",
13
- "Bash(pnpm build)",
14
- "Bash(pnpm test --coverage)",
15
- "Bash(git add README.md package.json scripts/write-dist-package.js src/cli.ts src/internal/queryHelpers.ts src/server/BeansMcpServer.ts src/server/backend.ts src/test/BeansMcpServer.test.ts src/test/handlers.unit.test.ts src/test/parseCliArgs.test.ts src/test/queryHelpers.test.ts tsup.config.ts src/test/protocol.e2e.test.ts src/test/startBeansMcpServer.test.ts CHANGELOG.md)"
16
- ]
17
- }
18
- }
package/.editorconfig DELETED
@@ -1,13 +0,0 @@
1
- # top-most EditorConfig file
2
- root = true
3
-
4
- # Unix-style newlines with a newline ending every file
5
- [*]
6
- end_of_line = lf
7
- insert_final_newline = true
8
- charset = utf-8
9
- indent_style = space
10
- indent_size = 2
11
-
12
- [*.cs]
13
- indent_size = 4
@@ -1,11 +0,0 @@
1
- # To get started with Dependabot version updates, you'll need to specify which
2
- # package ecosystems to update and where the package manifests are located.
3
- # Please see the documentation for all configuration options:
4
- # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
-
6
- version: 2
7
- updates:
8
- - package-ecosystem: "" # See documentation for possible values
9
- directory: "/" # Location of package manifests
10
- schedule:
11
- interval: "weekly"