@selfagency/beans-mcp 0.1.4 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -6
- package/beans-mcp-server.cjs +268 -34
- package/index.cjs +268 -34
- package/index.d.ts +19 -1
- package/index.js +268 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,14 @@ MCP (Model Context Protocol) server for [Beans](https://github.com/hmans/beans)
|
|
|
12
12
|
npx @selfagency/beans-mcp /path/to/workspace
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
### Versioning
|
|
16
|
+
|
|
17
|
+
`@selfagency/beans-mcp` tracks upstream [Beans](https://github.com/hmans/beans) versions.
|
|
18
|
+
For example, Beans `v0.4.2` maps to `@selfagency/beans-mcp@0.4.2`.
|
|
19
|
+
|
|
20
|
+
At startup, the server compares its own package version against the installed `beans`
|
|
21
|
+
CLI version. If they differ, it prints a warning to stderr and continues startup.
|
|
22
|
+
|
|
15
23
|
### Parameters
|
|
16
24
|
|
|
17
25
|
- `--workspace-root` or positional arg: Workspace root path
|
|
@@ -23,12 +31,12 @@ npx @selfagency/beans-mcp /path/to/workspace
|
|
|
23
31
|
## Summary of public MCP tools
|
|
24
32
|
|
|
25
33
|
- `beans_init` — Initialize the workspace (optional `prefix`).
|
|
26
|
-
- `beans_view` — Fetch full bean details by `beanId`.
|
|
34
|
+
- `beans_view` — Fetch full bean details by `beanId` or `beanIds`.
|
|
27
35
|
- `beans_create` — Create a new bean (title/type + optional fields).
|
|
28
|
-
- `beans_update` — Consolidated metadata updates (status/type/priority/parent/clearParent/blocking/blockedBy).
|
|
29
|
-
- `beans_delete` — Delete
|
|
36
|
+
- `beans_update` — Consolidated metadata + body updates (status/type/priority/parent/clearParent/blocking/blockedBy/body/bodyAppend/bodyReplace) plus optional optimistic concurrency hint (`ifMatch`).
|
|
37
|
+
- `beans_delete` — Delete one or many beans (`beanId` or `beanIds`, optional `force`).
|
|
30
38
|
- `beans_reopen` — Reopen a completed or scrapped bean to an active status.
|
|
31
|
-
- `beans_query` — Unified list/search/filter/sort/llm_context/open_config operations.
|
|
39
|
+
- `beans_query` — Unified list/search/filter/sort/ready/llm_context/open_config operations.
|
|
32
40
|
- `beans_bean_file` — Read/edit/create/delete files under `.beans`.
|
|
33
41
|
- `beans_output` — Read extension output logs or show guidance.
|
|
34
42
|
|
|
@@ -37,6 +45,7 @@ npx @selfagency/beans-mcp /path/to/workspace
|
|
|
37
45
|
- The `beans_query` tool is intentionally broad: prefer it for listing, searching, filtering or sorting beans, and for generating Copilot instructions (`operation: 'llm_context'`).
|
|
38
46
|
- All file and log operations validate paths to keep them within the workspace or the VS Code log directory.
|
|
39
47
|
- `beans_update` replaces many fine-grained update tools; callers should use it to keep the public tool surface small and predictable.
|
|
48
|
+
- Version mismatches are warning-only and non-blocking by design.
|
|
40
49
|
|
|
41
50
|
## Examples
|
|
42
51
|
|
|
@@ -62,6 +71,12 @@ Request:
|
|
|
62
71
|
{ "beanId": "bean-abc" }
|
|
63
72
|
```
|
|
64
73
|
|
|
74
|
+
Request (multiple beans):
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{ "beanIds": ["bean-abc", "bean-def"] }
|
|
78
|
+
```
|
|
79
|
+
|
|
65
80
|
Response (structuredContent):
|
|
66
81
|
|
|
67
82
|
```json
|
|
@@ -114,10 +129,26 @@ Request (change status and add blocking):
|
|
|
114
129
|
{
|
|
115
130
|
"beanId": "bean-abc",
|
|
116
131
|
"status": "in-progress",
|
|
117
|
-
"blocking": ["bean-def"]
|
|
132
|
+
"blocking": ["bean-def"],
|
|
133
|
+
"ifMatch": "etag-value"
|
|
118
134
|
}
|
|
119
135
|
```
|
|
120
136
|
|
|
137
|
+
Request (atomic body modifications):
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"beanId": "bean-abc",
|
|
142
|
+
"bodyReplace": [
|
|
143
|
+
{ "old": "- [ ] Task 1", "new": "- [x] Task 1" },
|
|
144
|
+
{ "old": "- [ ] Task 2", "new": "- [x] Task 2" }
|
|
145
|
+
],
|
|
146
|
+
"bodyAppend": "## Summary\n\nAll checklist items completed."
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
> Note: `body` (full replacement) cannot be combined with `bodyAppend` or `bodyReplace` in the same request.
|
|
151
|
+
|
|
121
152
|
Response (structuredContent):
|
|
122
153
|
|
|
123
154
|
```json
|
|
@@ -144,6 +175,26 @@ Response:
|
|
|
144
175
|
{ "deleted": true, "beanId": "bean-old" }
|
|
145
176
|
```
|
|
146
177
|
|
|
178
|
+
Batch request:
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{ "beanIds": ["bean-old", "bean-older"], "force": false }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Batch response (summary):
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"requestedCount": 2,
|
|
189
|
+
"deletedCount": 2,
|
|
190
|
+
"failedCount": 0,
|
|
191
|
+
"results": [
|
|
192
|
+
{ "beanId": "bean-old", "deleted": true },
|
|
193
|
+
{ "beanId": "bean-older", "deleted": true }
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
147
198
|
### beans_reopen
|
|
148
199
|
|
|
149
200
|
Request:
|
|
@@ -199,6 +250,12 @@ Sort (modes: `status-priority-type-title`, `updated`, `created`, `id`):
|
|
|
199
250
|
{ "operation": "sort", "mode": "updated" }
|
|
200
251
|
```
|
|
201
252
|
|
|
253
|
+
Ready (actionable beans only):
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{ "operation": "ready" }
|
|
257
|
+
```
|
|
258
|
+
|
|
202
259
|
LLM context (generate Copilot instructions; optional write-to-workspace):
|
|
203
260
|
|
|
204
261
|
```json
|
|
@@ -211,7 +268,7 @@ Response (structuredContent):
|
|
|
211
268
|
{
|
|
212
269
|
"graphqlSchema": "...",
|
|
213
270
|
"generatedInstructions": "...",
|
|
214
|
-
"instructionsPath": "/workspace/.github/instructions/
|
|
271
|
+
"instructionsPath": "/workspace/.github/instructions/beans-prime.instructions.md"
|
|
215
272
|
}
|
|
216
273
|
```
|
|
217
274
|
|
package/beans-mcp-server.cjs
CHANGED
|
@@ -22747,7 +22747,7 @@ var init_utils = __esm({
|
|
|
22747
22747
|
});
|
|
22748
22748
|
|
|
22749
22749
|
// src/internal/graphql.ts
|
|
22750
|
-
var LIST_BEANS_QUERY, CREATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION, DELETE_BEAN_MUTATION;
|
|
22750
|
+
var LIST_BEANS_QUERY, CREATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION, UPDATE_BEAN_MUTATION_WITH_IF_MATCH, DELETE_BEAN_MUTATION;
|
|
22751
22751
|
var init_graphql = __esm({
|
|
22752
22752
|
"src/internal/graphql.ts"() {
|
|
22753
22753
|
"use strict";
|
|
@@ -22765,6 +22765,11 @@ var init_graphql = __esm({
|
|
|
22765
22765
|
mutation($id: ID!, $input: UpdateBeanInput!) {
|
|
22766
22766
|
updateBean(id: $id, input: $input) { id slug path title body status type priority tags parentId blockingIds blockedByIds createdAt updatedAt etag }
|
|
22767
22767
|
}
|
|
22768
|
+
`;
|
|
22769
|
+
UPDATE_BEAN_MUTATION_WITH_IF_MATCH = `
|
|
22770
|
+
mutation($id: ID!, $input: UpdateBeanInput!, $ifMatch: String!) {
|
|
22771
|
+
updateBean(id: $id, input: $input, ifMatch: $ifMatch) { id slug path title body status type priority tags parentId blockingIds blockedByIds createdAt updatedAt etag }
|
|
22772
|
+
}
|
|
22768
22773
|
`;
|
|
22769
22774
|
DELETE_BEAN_MUTATION = `
|
|
22770
22775
|
mutation($id: ID!) {
|
|
@@ -22922,10 +22927,50 @@ Output: ${stdout.slice(0, 1e3)}`
|
|
|
22922
22927
|
if (updates.body !== void 0) {
|
|
22923
22928
|
updateInput.body = updates.body;
|
|
22924
22929
|
}
|
|
22925
|
-
const
|
|
22926
|
-
|
|
22927
|
-
|
|
22928
|
-
}
|
|
22930
|
+
const bodyMod = {};
|
|
22931
|
+
if (updates.bodyAppend !== void 0) {
|
|
22932
|
+
bodyMod.append = updates.bodyAppend;
|
|
22933
|
+
}
|
|
22934
|
+
if (Array.isArray(updates.bodyReplace) && updates.bodyReplace.length > 0) {
|
|
22935
|
+
bodyMod.replace = updates.bodyReplace;
|
|
22936
|
+
}
|
|
22937
|
+
if (Object.keys(bodyMod).length > 0) {
|
|
22938
|
+
updateInput.bodyMod = bodyMod;
|
|
22939
|
+
}
|
|
22940
|
+
let data;
|
|
22941
|
+
let errors;
|
|
22942
|
+
if (updates.ifMatch) {
|
|
22943
|
+
try {
|
|
22944
|
+
const res = await this.executeGraphQL(UPDATE_BEAN_MUTATION_WITH_IF_MATCH, {
|
|
22945
|
+
id: beanId,
|
|
22946
|
+
input: updateInput,
|
|
22947
|
+
ifMatch: updates.ifMatch
|
|
22948
|
+
});
|
|
22949
|
+
data = res.data;
|
|
22950
|
+
errors = res.errors;
|
|
22951
|
+
} catch (error48) {
|
|
22952
|
+
const message = error48.message || "";
|
|
22953
|
+
const unsupportedIfMatch = /unknown argument.*ifMatch|unknown field.*ifMatch|ifMatch.*not defined|field .*updateBean.* argument .*ifMatch/i.test(
|
|
22954
|
+
message
|
|
22955
|
+
);
|
|
22956
|
+
if (!unsupportedIfMatch) {
|
|
22957
|
+
throw error48;
|
|
22958
|
+
}
|
|
22959
|
+
const fallback = await this.executeGraphQL(UPDATE_BEAN_MUTATION, {
|
|
22960
|
+
id: beanId,
|
|
22961
|
+
input: updateInput
|
|
22962
|
+
});
|
|
22963
|
+
data = fallback.data;
|
|
22964
|
+
errors = fallback.errors;
|
|
22965
|
+
}
|
|
22966
|
+
} else {
|
|
22967
|
+
const res = await this.executeGraphQL(UPDATE_BEAN_MUTATION, {
|
|
22968
|
+
id: beanId,
|
|
22969
|
+
input: updateInput
|
|
22970
|
+
});
|
|
22971
|
+
data = res.data;
|
|
22972
|
+
errors = res.errors;
|
|
22973
|
+
}
|
|
22929
22974
|
if (errors && errors.length > 0) {
|
|
22930
22975
|
throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
|
|
22931
22976
|
}
|
|
@@ -22945,6 +22990,21 @@ Output: ${stdout.slice(0, 1e3)}`
|
|
|
22945
22990
|
const content = await (0, import_promises.readFile)(configPath, "utf8");
|
|
22946
22991
|
return { configPath, content };
|
|
22947
22992
|
}
|
|
22993
|
+
async primeInstructions() {
|
|
22994
|
+
const { stdout } = await execFileAsync(this.cliPath, ["prime"], {
|
|
22995
|
+
cwd: this.workspaceRoot,
|
|
22996
|
+
env: this.getSafeEnv(),
|
|
22997
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
22998
|
+
timeout: 3e4
|
|
22999
|
+
});
|
|
23000
|
+
return stdout.trim();
|
|
23001
|
+
}
|
|
23002
|
+
async writeInstructions(instructions) {
|
|
23003
|
+
const instructionsPath = (0, import_node_path2.join)(this.workspaceRoot, ".github", "instructions", "beans-prime.instructions.md");
|
|
23004
|
+
await (0, import_promises.mkdir)((0, import_node_path2.dirname)(instructionsPath), { recursive: true });
|
|
23005
|
+
await (0, import_promises.writeFile)(instructionsPath, instructions, "utf8");
|
|
23006
|
+
return instructionsPath;
|
|
23007
|
+
}
|
|
22948
23008
|
async graphqlSchema() {
|
|
22949
23009
|
const { stdout } = await execFileAsync(this.cliPath, ["graphql", "--schema"], {
|
|
22950
23010
|
cwd: this.workspaceRoot,
|
|
@@ -31072,6 +31132,10 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
31072
31132
|
}
|
|
31073
31133
|
};
|
|
31074
31134
|
|
|
31135
|
+
// src/server/BeansMcpServer.ts
|
|
31136
|
+
var import_node_child_process2 = require("child_process");
|
|
31137
|
+
var import_node_util2 = require("util");
|
|
31138
|
+
|
|
31075
31139
|
// src/internal/queryHelpers.ts
|
|
31076
31140
|
function sortBeansInternal(beans, mode) {
|
|
31077
31141
|
const sorted = [...beans];
|
|
@@ -31127,15 +31191,23 @@ async function handleQueryOperation(backend, params) {
|
|
|
31127
31191
|
const { operation, mode, statuses, types, search, tags, writeToWorkspaceInstructions, includeClosed } = params;
|
|
31128
31192
|
if (operation === "llm_context") {
|
|
31129
31193
|
const graphqlSchema = typeof backend.graphqlSchema === "function" ? await backend.graphqlSchema() : "";
|
|
31130
|
-
|
|
31194
|
+
let generatedInstructions = "";
|
|
31195
|
+
if (typeof backend.primeInstructions === "function") {
|
|
31196
|
+
try {
|
|
31197
|
+
generatedInstructions = await backend.primeInstructions();
|
|
31198
|
+
} catch {
|
|
31199
|
+
generatedInstructions = "";
|
|
31200
|
+
}
|
|
31201
|
+
}
|
|
31202
|
+
const instructionsPath = writeToWorkspaceInstructions && typeof backend.writeInstructions === "function" ? await backend.writeInstructions(generatedInstructions) : null;
|
|
31131
31203
|
return {
|
|
31132
31204
|
content: [
|
|
31133
31205
|
{
|
|
31134
31206
|
type: "text",
|
|
31135
|
-
text: JSON.stringify({ graphqlSchema, generatedInstructions
|
|
31207
|
+
text: JSON.stringify({ graphqlSchema, generatedInstructions, instructionsPath }, null, 2)
|
|
31136
31208
|
}
|
|
31137
31209
|
],
|
|
31138
|
-
structuredContent: { graphqlSchema, generatedInstructions
|
|
31210
|
+
structuredContent: { graphqlSchema, generatedInstructions, instructionsPath }
|
|
31139
31211
|
};
|
|
31140
31212
|
}
|
|
31141
31213
|
if (operation === "open_config") {
|
|
@@ -31181,6 +31253,31 @@ async function handleQueryOperation(backend, params) {
|
|
|
31181
31253
|
structuredContent: { query: search, count: beans2.length, beans: beans2 }
|
|
31182
31254
|
};
|
|
31183
31255
|
}
|
|
31256
|
+
if (operation === "ready") {
|
|
31257
|
+
const allBeans = await backend.list();
|
|
31258
|
+
const byId = new Map(allBeans.map((bean) => [bean.id, bean]));
|
|
31259
|
+
const candidates = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
|
|
31260
|
+
const readyBeans = candidates.filter((bean) => {
|
|
31261
|
+
if (bean.status !== "todo") {
|
|
31262
|
+
return false;
|
|
31263
|
+
}
|
|
31264
|
+
const blockedBy = bean.blockedByIds || [];
|
|
31265
|
+
if (blockedBy.length === 0) {
|
|
31266
|
+
return true;
|
|
31267
|
+
}
|
|
31268
|
+
return blockedBy.every((blockerId) => {
|
|
31269
|
+
const blocker = byId.get(blockerId);
|
|
31270
|
+
if (!blocker) {
|
|
31271
|
+
return false;
|
|
31272
|
+
}
|
|
31273
|
+
return blocker.status === "completed" || blocker.status === "scrapped";
|
|
31274
|
+
});
|
|
31275
|
+
});
|
|
31276
|
+
return {
|
|
31277
|
+
content: [{ type: "text", text: JSON.stringify({ count: readyBeans.length, beans: readyBeans }, null, 2) }],
|
|
31278
|
+
structuredContent: { count: readyBeans.length, beans: readyBeans }
|
|
31279
|
+
};
|
|
31280
|
+
}
|
|
31184
31281
|
const beans = await backend.list({ status: normalizedStatuses, type: normalizedTypes, search });
|
|
31185
31282
|
const sorted = sortBeansInternal(beans, mode ?? "status-priority-type-title");
|
|
31186
31283
|
return {
|
|
@@ -31203,7 +31300,7 @@ init_utils();
|
|
|
31203
31300
|
// package.json
|
|
31204
31301
|
var package_default = {
|
|
31205
31302
|
name: "@selfagency/beans-mcp",
|
|
31206
|
-
version: "0.
|
|
31303
|
+
version: "0.4.2",
|
|
31207
31304
|
private: false,
|
|
31208
31305
|
description: "MCP (Model Context Protocol) server for Beans issue tracker",
|
|
31209
31306
|
author: {
|
|
@@ -31258,18 +31355,18 @@ var package_default = {
|
|
|
31258
31355
|
devDependencies: {
|
|
31259
31356
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
31260
31357
|
"@octokit/rest": "^22.0.1",
|
|
31261
|
-
"@types/node": "
|
|
31262
|
-
"@vitest/coverage-v8": "^4.0
|
|
31263
|
-
"@vitest/ui": "4.0
|
|
31358
|
+
"@types/node": "25.5.0",
|
|
31359
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
31360
|
+
"@vitest/ui": "4.1.0",
|
|
31264
31361
|
husky: "^9.1.7",
|
|
31265
|
-
"lint-staged": "^16.
|
|
31362
|
+
"lint-staged": "^16.3.3",
|
|
31266
31363
|
ora: "^9.3.0",
|
|
31267
|
-
oxfmt: "^0.
|
|
31268
|
-
oxlint: "^1.
|
|
31269
|
-
"oxlint-tsgolint": "^0.
|
|
31364
|
+
oxfmt: "^0.40.0",
|
|
31365
|
+
oxlint: "^1.55.0",
|
|
31366
|
+
"oxlint-tsgolint": "^0.16.0",
|
|
31270
31367
|
tsup: "8.5.1",
|
|
31271
31368
|
typescript: "^5.9.3",
|
|
31272
|
-
vitest: "4.0
|
|
31369
|
+
vitest: "4.1.0",
|
|
31273
31370
|
zod: "4.3.6",
|
|
31274
31371
|
zx: "^8.8.5"
|
|
31275
31372
|
},
|
|
@@ -31285,6 +31382,45 @@ var package_default = {
|
|
|
31285
31382
|
};
|
|
31286
31383
|
|
|
31287
31384
|
// src/server/BeansMcpServer.ts
|
|
31385
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
31386
|
+
var PACKAGE_VERSION = package_default.version ?? "0.0.0-dev";
|
|
31387
|
+
function getSafeCliEnv(env) {
|
|
31388
|
+
const whitelist = ["PATH", "HOME", "USER", "LANG", "LC_ALL", "LC_CTYPE", "SHELL"];
|
|
31389
|
+
const safeEnv = {};
|
|
31390
|
+
for (const key of whitelist) {
|
|
31391
|
+
if (env[key]) {
|
|
31392
|
+
safeEnv[key] = env[key];
|
|
31393
|
+
}
|
|
31394
|
+
}
|
|
31395
|
+
for (const key in env) {
|
|
31396
|
+
if (key.startsWith("BEANS_")) {
|
|
31397
|
+
safeEnv[key] = env[key];
|
|
31398
|
+
}
|
|
31399
|
+
}
|
|
31400
|
+
return safeEnv;
|
|
31401
|
+
}
|
|
31402
|
+
function extractVersionFromOutput(output) {
|
|
31403
|
+
const trimmed = output.trim();
|
|
31404
|
+
if (!trimmed) {
|
|
31405
|
+
return null;
|
|
31406
|
+
}
|
|
31407
|
+
const match = trimmed.match(/(?:^|[^\d])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
|
|
31408
|
+
return match?.[1] ?? null;
|
|
31409
|
+
}
|
|
31410
|
+
async function detectBeansCliVersion(cliPath, workspaceRoot) {
|
|
31411
|
+
try {
|
|
31412
|
+
const { stdout, stderr } = await execFileAsync2(cliPath, ["version"], {
|
|
31413
|
+
cwd: workspaceRoot,
|
|
31414
|
+
env: getSafeCliEnv(process.env),
|
|
31415
|
+
maxBuffer: 1024 * 1024,
|
|
31416
|
+
timeout: 5e3
|
|
31417
|
+
});
|
|
31418
|
+
return extractVersionFromOutput(`${stdout}
|
|
31419
|
+
${stderr}`);
|
|
31420
|
+
} catch {
|
|
31421
|
+
return null;
|
|
31422
|
+
}
|
|
31423
|
+
}
|
|
31288
31424
|
async function getBeanById(backend, beanId) {
|
|
31289
31425
|
try {
|
|
31290
31426
|
const beans = await backend.list();
|
|
@@ -31304,7 +31440,35 @@ function initHandler(backend) {
|
|
|
31304
31440
|
};
|
|
31305
31441
|
}
|
|
31306
31442
|
function viewHandler(backend) {
|
|
31307
|
-
return async ({ beanId }) =>
|
|
31443
|
+
return async ({ beanId, beanIds }) => {
|
|
31444
|
+
const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
|
|
31445
|
+
if (ids.length === 0) {
|
|
31446
|
+
throw new Error("Either beanId or beanIds must be provided");
|
|
31447
|
+
}
|
|
31448
|
+
if (ids.length === 1) {
|
|
31449
|
+
const bean = await getBeanById(backend, ids[0]);
|
|
31450
|
+
return makeTextAndStructured({ bean });
|
|
31451
|
+
}
|
|
31452
|
+
const beans = await backend.list();
|
|
31453
|
+
const byId = new Map(beans.map((b) => [b.id, b]));
|
|
31454
|
+
const found = ids.map((id) => byId.get(id)).filter(Boolean);
|
|
31455
|
+
const missingBeanIds = ids.filter((id) => !byId.has(id));
|
|
31456
|
+
return makeTextAndStructured({ beans: found, missingBeanIds, count: found.length, requestedCount: ids.length });
|
|
31457
|
+
};
|
|
31458
|
+
}
|
|
31459
|
+
async function checkVersionCompatibility(cliPath, workspaceRoot, detector) {
|
|
31460
|
+
const detectedBeansVersion = await detector(cliPath, workspaceRoot);
|
|
31461
|
+
if (!detectedBeansVersion) {
|
|
31462
|
+
console.error(
|
|
31463
|
+
`[beans-mcp] warning: unable to determine Beans CLI version from \`${cliPath}\`; proceeding without version compatibility checks.`
|
|
31464
|
+
);
|
|
31465
|
+
return;
|
|
31466
|
+
}
|
|
31467
|
+
if (detectedBeansVersion !== PACKAGE_VERSION) {
|
|
31468
|
+
console.error(
|
|
31469
|
+
`[beans-mcp] warning: version mismatch detected (beans=${detectedBeansVersion}, beans-mcp=${PACKAGE_VERSION}); continuing startup.`
|
|
31470
|
+
);
|
|
31471
|
+
}
|
|
31308
31472
|
}
|
|
31309
31473
|
function createHandler(backend) {
|
|
31310
31474
|
return async (input) => makeTextAndStructured({ bean: await backend.create(input) });
|
|
@@ -31340,17 +31504,56 @@ function updateHandler(backend) {
|
|
|
31340
31504
|
clearParent: input.clearParent,
|
|
31341
31505
|
blocking: input.blocking,
|
|
31342
31506
|
blockedBy: input.blockedBy,
|
|
31343
|
-
body: input.body
|
|
31507
|
+
body: input.body,
|
|
31508
|
+
bodyAppend: input.bodyAppend,
|
|
31509
|
+
bodyReplace: input.bodyReplace,
|
|
31510
|
+
ifMatch: input.ifMatch
|
|
31344
31511
|
})
|
|
31345
31512
|
});
|
|
31346
31513
|
}
|
|
31347
31514
|
function deleteHandler(backend) {
|
|
31348
|
-
return async ({ beanId, force }) => {
|
|
31349
|
-
const
|
|
31350
|
-
if (
|
|
31351
|
-
throw new Error("
|
|
31515
|
+
return async ({ beanId, beanIds, force }) => {
|
|
31516
|
+
const ids = Array.isArray(beanIds) && beanIds.length > 0 ? beanIds : beanId ? [beanId] : [];
|
|
31517
|
+
if (ids.length === 0) {
|
|
31518
|
+
throw new Error("Either beanId or beanIds must be provided");
|
|
31519
|
+
}
|
|
31520
|
+
if (ids.length === 1) {
|
|
31521
|
+
const bean = await getBeanById(backend, ids[0]);
|
|
31522
|
+
if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
|
|
31523
|
+
throw new Error("Only draft and scrapped beans are deletable unless force=true");
|
|
31524
|
+
}
|
|
31525
|
+
return makeTextAndStructured(await backend.delete(ids[0]));
|
|
31352
31526
|
}
|
|
31353
|
-
|
|
31527
|
+
const beans = await backend.list();
|
|
31528
|
+
const byId = new Map(beans.map((b) => [b.id, b]));
|
|
31529
|
+
const results = [];
|
|
31530
|
+
for (const id of ids) {
|
|
31531
|
+
const bean = byId.get(id);
|
|
31532
|
+
if (!bean) {
|
|
31533
|
+
results.push({ beanId: id, deleted: false, error: "Bean not found" });
|
|
31534
|
+
continue;
|
|
31535
|
+
}
|
|
31536
|
+
if (!force && bean.status !== "draft" && bean.status !== "scrapped") {
|
|
31537
|
+
results.push({
|
|
31538
|
+
beanId: id,
|
|
31539
|
+
deleted: false,
|
|
31540
|
+
error: "Only draft and scrapped beans are deletable unless force=true"
|
|
31541
|
+
});
|
|
31542
|
+
continue;
|
|
31543
|
+
}
|
|
31544
|
+
try {
|
|
31545
|
+
await backend.delete(id);
|
|
31546
|
+
results.push({ beanId: id, deleted: true });
|
|
31547
|
+
} catch (error48) {
|
|
31548
|
+
results.push({ beanId: id, deleted: false, error: error48.message });
|
|
31549
|
+
}
|
|
31550
|
+
}
|
|
31551
|
+
return makeTextAndStructured({
|
|
31552
|
+
results,
|
|
31553
|
+
requestedCount: ids.length,
|
|
31554
|
+
deletedCount: results.filter((r) => r.deleted).length,
|
|
31555
|
+
failedCount: results.filter((r) => !r.deleted).length
|
|
31556
|
+
});
|
|
31354
31557
|
};
|
|
31355
31558
|
}
|
|
31356
31559
|
function queryHandler(backend) {
|
|
@@ -31411,7 +31614,12 @@ function registerTools(server, backend) {
|
|
|
31411
31614
|
{
|
|
31412
31615
|
title: "View Bean",
|
|
31413
31616
|
description: "Fetch full bean details by ID.",
|
|
31414
|
-
inputSchema: external_exports3.object({
|
|
31617
|
+
inputSchema: external_exports3.object({
|
|
31618
|
+
beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
|
|
31619
|
+
beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional()
|
|
31620
|
+
}).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
|
|
31621
|
+
message: "Either beanId or beanIds must be provided"
|
|
31622
|
+
}),
|
|
31415
31623
|
annotations: {
|
|
31416
31624
|
readOnlyHint: true,
|
|
31417
31625
|
destructiveHint: false,
|
|
@@ -31500,8 +31708,21 @@ function registerTools(server, backend) {
|
|
|
31500
31708
|
clearParent: external_exports3.boolean().optional(),
|
|
31501
31709
|
blocking: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
|
|
31502
31710
|
blockedBy: external_exports3.array(external_exports3.string().max(MAX_ID_LENGTH)).optional(),
|
|
31503
|
-
body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional()
|
|
31504
|
-
|
|
31711
|
+
body: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
|
|
31712
|
+
bodyAppend: external_exports3.string().max(MAX_DESCRIPTION_LENGTH).optional(),
|
|
31713
|
+
bodyReplace: external_exports3.array(
|
|
31714
|
+
external_exports3.object({
|
|
31715
|
+
old: external_exports3.string().max(MAX_DESCRIPTION_LENGTH),
|
|
31716
|
+
new: external_exports3.string().max(MAX_DESCRIPTION_LENGTH)
|
|
31717
|
+
})
|
|
31718
|
+
).optional(),
|
|
31719
|
+
ifMatch: external_exports3.string().max(MAX_METADATA_LENGTH).optional()
|
|
31720
|
+
}).refine(
|
|
31721
|
+
(input) => !(input.body !== void 0 && (input.bodyAppend !== void 0 || input.bodyReplace !== void 0)),
|
|
31722
|
+
{
|
|
31723
|
+
message: "body cannot be combined with bodyAppend/bodyReplace"
|
|
31724
|
+
}
|
|
31725
|
+
),
|
|
31505
31726
|
annotations: {
|
|
31506
31727
|
readOnlyHint: false,
|
|
31507
31728
|
destructiveHint: false,
|
|
@@ -31517,8 +31738,11 @@ function registerTools(server, backend) {
|
|
|
31517
31738
|
title: "Delete Bean",
|
|
31518
31739
|
description: "Delete a bean (intended for draft/scrapped beans).",
|
|
31519
31740
|
inputSchema: external_exports3.object({
|
|
31520
|
-
beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH),
|
|
31741
|
+
beanId: external_exports3.string().min(1).max(MAX_ID_LENGTH).optional(),
|
|
31742
|
+
beanIds: external_exports3.array(external_exports3.string().min(1).max(MAX_ID_LENGTH)).optional(),
|
|
31521
31743
|
force: external_exports3.boolean().default(false)
|
|
31744
|
+
}).refine((input) => Boolean(input.beanId) || Array.isArray(input.beanIds) && input.beanIds.length > 0, {
|
|
31745
|
+
message: "Either beanId or beanIds must be provided"
|
|
31522
31746
|
}),
|
|
31523
31747
|
annotations: {
|
|
31524
31748
|
readOnlyHint: false,
|
|
@@ -31535,7 +31759,7 @@ function registerTools(server, backend) {
|
|
|
31535
31759
|
title: "Query Beans",
|
|
31536
31760
|
description: "Unified query tool for refresh, filter, search, and sort operations.",
|
|
31537
31761
|
inputSchema: external_exports3.object({
|
|
31538
|
-
operation: external_exports3.enum(["refresh", "filter", "search", "sort", "llm_context", "open_config"]).default("refresh"),
|
|
31762
|
+
operation: external_exports3.enum(["refresh", "filter", "search", "sort", "ready", "llm_context", "open_config"]).default("refresh"),
|
|
31539
31763
|
mode: external_exports3.enum(["status-priority-type-title", "updated", "created", "id"]).optional(),
|
|
31540
31764
|
statuses: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
|
|
31541
31765
|
types: external_exports3.array(external_exports3.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
|
|
@@ -31617,6 +31841,12 @@ var MutableBackend = class {
|
|
|
31617
31841
|
openConfig() {
|
|
31618
31842
|
return this.inner.openConfig();
|
|
31619
31843
|
}
|
|
31844
|
+
primeInstructions() {
|
|
31845
|
+
return this.inner.primeInstructions?.() ?? Promise.resolve("");
|
|
31846
|
+
}
|
|
31847
|
+
writeInstructions(instructions) {
|
|
31848
|
+
return this.inner.writeInstructions?.(instructions) ?? Promise.resolve(null);
|
|
31849
|
+
}
|
|
31620
31850
|
graphqlSchema() {
|
|
31621
31851
|
return this.inner.graphqlSchema();
|
|
31622
31852
|
}
|
|
@@ -31654,7 +31884,7 @@ async function createBeansMcpServer(opts) {
|
|
|
31654
31884
|
const backend = opts.backend || new BeansCliBackend2(opts.workspaceRoot, opts.cliPath || "beans", opts.logDir);
|
|
31655
31885
|
const server = new McpServer({
|
|
31656
31886
|
name: opts.name || "beans-mcp-server",
|
|
31657
|
-
version: opts.version ||
|
|
31887
|
+
version: opts.version || PACKAGE_VERSION
|
|
31658
31888
|
});
|
|
31659
31889
|
registerTools(server, backend);
|
|
31660
31890
|
return { server, backend };
|
|
@@ -31726,17 +31956,17 @@ function parseCliArgs(argv) {
|
|
|
31726
31956
|
}
|
|
31727
31957
|
return { workspaceRoot, workspaceExplicit, cliPath, port, logDir };
|
|
31728
31958
|
}
|
|
31729
|
-
async function startBeansMcpServer(argv, _resolveRoots) {
|
|
31959
|
+
async function startBeansMcpServer(argv, _resolveRoots, _detectBeansVersion) {
|
|
31730
31960
|
const { BeansCliBackend: BeansCliBackend2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
|
|
31731
31961
|
const { StdioServerTransport: StdioServerTransport2 } = await Promise.resolve().then(() => (init_stdio2(), stdio_exports));
|
|
31732
31962
|
const { workspaceRoot, workspaceExplicit, cliPath, port, logDir } = parseCliArgs(argv);
|
|
31963
|
+
let effectiveWorkspaceRoot = workspaceRoot;
|
|
31733
31964
|
process.env.BEANS_VSCODE_MCP_PORT = String(port);
|
|
31734
31965
|
process.env.BEANS_MCP_PORT = String(port);
|
|
31735
31966
|
try {
|
|
31736
|
-
const version2 = package_default.version ?? "0.0.0-dev";
|
|
31737
31967
|
const workspaceLabel = workspaceExplicit ? workspaceRoot : "(auto from roots)";
|
|
31738
31968
|
console.error(
|
|
31739
|
-
`[beans-mcp] v${
|
|
31969
|
+
`[beans-mcp] v${PACKAGE_VERSION} starting (port=${port}, workspace=${workspaceLabel}, cli=${cliPath}, logDir=${logDir})`
|
|
31740
31970
|
);
|
|
31741
31971
|
} catch {
|
|
31742
31972
|
}
|
|
@@ -31753,13 +31983,17 @@ async function startBeansMcpServer(argv, _resolveRoots) {
|
|
|
31753
31983
|
const resolver = _resolveRoots ?? resolveWorkspaceFromRoots;
|
|
31754
31984
|
const rootPath = await resolver(server);
|
|
31755
31985
|
if (rootPath) {
|
|
31756
|
-
mutable.setInner(new BeansCliBackend2(rootPath, cliPath));
|
|
31986
|
+
mutable.setInner(new BeansCliBackend2(rootPath, cliPath, logDir));
|
|
31987
|
+
effectiveWorkspaceRoot = rootPath;
|
|
31757
31988
|
try {
|
|
31758
31989
|
console.error(`[beans-mcp] workspace resolved from roots: ${rootPath}`);
|
|
31759
31990
|
} catch {
|
|
31760
31991
|
}
|
|
31761
31992
|
}
|
|
31762
31993
|
}
|
|
31994
|
+
const beansVersionDetector = _detectBeansVersion ?? detectBeansCliVersion;
|
|
31995
|
+
void checkVersionCompatibility(cliPath, effectiveWorkspaceRoot, beansVersionDetector).catch(() => {
|
|
31996
|
+
});
|
|
31763
31997
|
}
|
|
31764
31998
|
|
|
31765
31999
|
// src/cli.ts
|