@selfagency/beans-mcp 0.1.2 → 0.1.3

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 (57) hide show
  1. package/.beans.yml +6 -0
  2. package/.claude/settings.local.json +18 -0
  3. package/.editorconfig +13 -0
  4. package/.github/dependabot.yml +11 -0
  5. package/.github/workflows/release.yml +235 -0
  6. package/.github/workflows/test.yml +84 -0
  7. package/.husky/pre-commit +1 -0
  8. package/.nvmrc +1 -0
  9. package/.oxfmtrc.json +11 -0
  10. package/.oxlintrc.json +37 -0
  11. package/.vscode/settings.json +3 -0
  12. package/CHANGELOG.md +160 -0
  13. package/CONTRIBUTING.md +139 -0
  14. package/codeql/codeql-custom-queries-actions/README.md +14 -0
  15. package/codeql/codeql-custom-queries-actions/codeql-pack.lock.yml +32 -0
  16. package/codeql/codeql-custom-queries-actions/codeql-pack.yml +7 -0
  17. package/codeql/codeql-custom-queries-actions/qlpack.yml +6 -0
  18. package/codeql/codeql-custom-queries-actions/queries/github-script-without-tojson.ql +18 -0
  19. package/codeql/codeql-custom-queries-actions/queries/strict-external-action-pinning.ql +18 -0
  20. package/codeql/codeql-custom-queries-javascript/README.md +14 -0
  21. package/codeql/codeql-custom-queries-javascript/codeql-pack.lock.yml +30 -0
  22. package/codeql/codeql-custom-queries-javascript/codeql-pack.yml +7 -0
  23. package/codeql/codeql-custom-queries-javascript/qlpack.yml +6 -0
  24. package/codeql/codeql-custom-queries-javascript/queries/child-process-shell-apis.ql +26 -0
  25. package/codeql/codeql-custom-queries-javascript/queries/innerhtml-assignment.ql +24 -0
  26. package/dist/README.md +307 -0
  27. package/{beans-mcp-server.cjs → dist/beans-mcp-server.cjs} +97 -0
  28. package/dist/beans-mcp-server.cjs.map +1 -0
  29. package/{index.cjs → dist/index.cjs} +97 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/{index.js → dist/index.js} +97 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/package.json +43 -0
  34. package/package.json +63 -26
  35. package/pnpm-workspace.yaml +2 -0
  36. package/scripts/release.js +433 -0
  37. package/scripts/write-dist-package.js +53 -0
  38. package/src/cli.ts +14 -0
  39. package/src/index.ts +21 -0
  40. package/src/internal/graphql.ts +33 -0
  41. package/src/internal/queryHelpers.ts +157 -0
  42. package/src/server/BeansMcpServer.ts +623 -0
  43. package/src/server/backend.ts +364 -0
  44. package/src/test/BeansMcpServer.test.ts +514 -0
  45. package/src/test/handlers.unit.test.ts +201 -0
  46. package/src/test/parseCliArgs.test.ts +69 -0
  47. package/src/test/protocol.e2e.test.ts +884 -0
  48. package/src/test/queryHelpers.test.ts +524 -0
  49. package/src/test/startBeansMcpServer.test.ts +146 -0
  50. package/src/test/tools-integration.test.ts +912 -0
  51. package/src/test/utils.test.ts +81 -0
  52. package/src/types.ts +46 -0
  53. package/src/utils.ts +20 -0
  54. package/tsconfig.json +24 -0
  55. package/tsup.config.ts +42 -0
  56. package/vitest.config.ts +18 -0
  57. /package/{index.d.ts → dist/index.d.ts} +0 -0
package/.beans.yml ADDED
@@ -0,0 +1,6 @@
1
+ beans:
2
+ path: .beans
3
+ prefix: beans-mcp-server-
4
+ id_length: 4
5
+ default_status: todo
6
+ default_type: task
@@ -0,0 +1,18 @@
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 ADDED
@@ -0,0 +1,13 @@
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
@@ -0,0 +1,11 @@
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"
@@ -0,0 +1,235 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+ workflow_dispatch:
7
+ inputs:
8
+ version:
9
+ description: "Version to release (e.g. 1.2.3)"
10
+ required: true
11
+ type: string
12
+
13
+ permissions:
14
+ contents: read
15
+ actions: read
16
+
17
+ concurrency:
18
+ group: release-${{ github.ref }}
19
+ cancel-in-progress: true
20
+
21
+ jobs:
22
+ detect-tag:
23
+ name: Detect release tag and readiness
24
+ runs-on: ubuntu-latest
25
+ timeout-minutes: 5
26
+ permissions:
27
+ contents: write
28
+ actions: read
29
+
30
+ outputs:
31
+ release_tag: ${{ steps.tag.outputs.release_tag }}
32
+ ready: ${{ steps.tag.outputs.ready }}
33
+
34
+ steps:
35
+ - name: Resolve tag and verify CI readiness
36
+ id: tag
37
+ uses: actions/github-script@v8
38
+ with:
39
+ github-token: ${{ secrets.GITHUB_TOKEN }}
40
+ script: |
41
+ const owner = context.repo.owner;
42
+ const repo = context.repo.repo;
43
+
44
+ let tagName;
45
+
46
+ if (context.eventName === 'workflow_dispatch') {
47
+ const versionInput = '${{ inputs.version }}'.trim();
48
+ if (!versionInput) {
49
+ core.setFailed('version input is required for workflow_dispatch.');
50
+ return;
51
+ }
52
+ tagName = versionInput.startsWith('v') ? versionInput : `v${versionInput}`;
53
+
54
+ // Resolve HEAD of main
55
+ const main = await github.rest.repos.getBranch({ owner, repo, branch: 'main' });
56
+ const mainSha = main.data.commit.sha;
57
+
58
+ // Create the tag via API
59
+ core.info(`Creating tag ${tagName} at ${mainSha}...`);
60
+ await github.rest.git.createRef({
61
+ owner,
62
+ repo,
63
+ ref: `refs/tags/${tagName}`,
64
+ sha: mainSha,
65
+ });
66
+ core.info(`Tag ${tagName} created.`);
67
+ } else {
68
+ tagName = context.ref.replace('refs/tags/', '');
69
+ }
70
+
71
+ if (!tagName || !tagName.startsWith('v')) {
72
+ core.info(`Ref '${context.ref}' is not a release tag; skipping.`);
73
+ core.setOutput('release_tag', '');
74
+ core.setOutput('ready', 'false');
75
+ return;
76
+ }
77
+
78
+ const refData = await github.rest.git.getRef({
79
+ owner,
80
+ repo,
81
+ ref: `tags/${tagName}`,
82
+ });
83
+
84
+ const refObj = refData.data.object;
85
+ const taggedSha =
86
+ refObj.type === 'tag'
87
+ ? (await github.rest.git.getTag({ owner, repo, tag_sha: refObj.sha })).data.object.sha
88
+ : refObj.sha;
89
+ const main = await github.rest.repos.getBranch({ owner, repo, branch: 'main' });
90
+ const mainSha = main.data.commit.sha;
91
+
92
+ if (taggedSha !== mainSha) {
93
+ core.setFailed(
94
+ `Release blocked: tag ${tagName} points to ${taggedSha}, but latest main is ${mainSha}.`
95
+ );
96
+ return;
97
+ }
98
+
99
+ // Verify the full test suite passed on this commit.
100
+ const requiredWorkflows = ['Test & Build'];
101
+
102
+ for (const workflowName of requiredWorkflows) {
103
+ const workflowsResp = await github.rest.actions.listRepoWorkflows({
104
+ owner,
105
+ repo,
106
+ per_page: 100,
107
+ });
108
+
109
+ const workflow = workflowsResp.data.workflows.find(w => w.name === workflowName);
110
+ if (!workflow) {
111
+ core.setFailed(`Release blocked: required workflow '${workflowName}' not found in repository.`);
112
+ return;
113
+ }
114
+
115
+ const runsResp = await github.rest.actions.listWorkflowRuns({
116
+ owner,
117
+ repo,
118
+ workflow_id: workflow.id,
119
+ branch: 'main',
120
+ head_sha: mainSha,
121
+ status: 'completed',
122
+ per_page: 10,
123
+ });
124
+
125
+ const run = runsResp.data.workflow_runs[0];
126
+ if (!run) {
127
+ core.setFailed(
128
+ `Release blocked: required workflow '${workflowName}' has no completed run for ${mainSha}.`
129
+ );
130
+ return;
131
+ }
132
+ if (run.conclusion !== 'success') {
133
+ core.setFailed(
134
+ `Release blocked: workflow '${workflowName}' concluded with '${run.conclusion}' on ${mainSha}.`
135
+ );
136
+ return;
137
+ }
138
+ }
139
+
140
+ core.info(`Release tag detected: ${tagName} on latest main commit ${mainSha}; all checks passed.`);
141
+ core.setOutput('release_tag', tagName);
142
+ core.setOutput('ready', 'true');
143
+
144
+ release:
145
+ name: Build and Release
146
+ needs: detect-tag
147
+ if: needs.detect-tag.outputs.release_tag != '' && needs.detect-tag.outputs.ready == 'true'
148
+ runs-on: ubuntu-latest
149
+ timeout-minutes: 10
150
+ permissions:
151
+ contents: write
152
+
153
+ steps:
154
+ - name: Checkout repository
155
+ uses: actions/checkout@v4
156
+ with:
157
+ ref: ${{ needs.detect-tag.outputs.release_tag }}
158
+ fetch-depth: 0
159
+
160
+ - name: Generate release notes
161
+ id: changelog_notes
162
+ uses: actions/github-script@v8
163
+ with:
164
+ github-token: ${{ secrets.GITHUB_TOKEN }}
165
+ script: |
166
+ const owner = context.repo.owner;
167
+ const repo = context.repo.repo;
168
+ const currentTag = '${{ needs.detect-tag.outputs.release_tag }}';
169
+
170
+ const tagsResp = await github.rest.repos.listTags({
171
+ owner,
172
+ repo,
173
+ per_page: 100,
174
+ });
175
+ const tags = tagsResp.data;
176
+
177
+ const index = tags.findIndex(tag => tag.name === currentTag);
178
+ const previousTag =
179
+ index >= 0
180
+ ? (tags.slice(index + 1).find(tag => tag.name.startsWith('v'))?.name ?? '')
181
+ : '';
182
+
183
+ const notesResponse = await github.rest.repos.generateReleaseNotes({
184
+ owner,
185
+ repo,
186
+ tag_name: currentTag,
187
+ ...(previousTag ? { previous_tag_name: previousTag } : {}),
188
+ target_commitish: 'main',
189
+ });
190
+
191
+ core.info(
192
+ previousTag
193
+ ? `Generated changelog notes from ${previousTag} to ${currentTag}.`
194
+ : `Generated changelog notes for ${currentTag} (no previous tag found).`
195
+ );
196
+
197
+ core.setOutput('previous_tag', previousTag);
198
+ core.setOutput('notes', notesResponse.data.body ?? '');
199
+
200
+ - name: Write release notes to file
201
+ run: printf '%s' "$RELEASE_NOTES" > release-notes.md
202
+ env:
203
+ RELEASE_NOTES: ${{ steps.changelog_notes.outputs.notes }}
204
+
205
+ - name: Setup pnpm
206
+ uses: pnpm/action-setup@v4
207
+ with:
208
+ version: 10
209
+
210
+ - name: Setup Node.js
211
+ uses: actions/setup-node@v4
212
+ with:
213
+ node-version: "20"
214
+ cache: "pnpm"
215
+
216
+ - name: Install dependencies
217
+ run: pnpm install --frozen-lockfile
218
+
219
+ - name: Build
220
+ run: pnpm build
221
+
222
+ - name: Pack npm tarball
223
+ run: npm pack ./dist --pack-destination .
224
+
225
+ - name: Create GitHub Release
226
+ uses: softprops/action-gh-release@v2
227
+ with:
228
+ tag_name: ${{ needs.detect-tag.outputs.release_tag }}
229
+ body_path: release-notes.md
230
+ files: "*.tgz"
231
+ draft: false
232
+ prerelease: ${{ contains(needs.detect-tag.outputs.release_tag, '-') }}
233
+ env:
234
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
235
+
@@ -0,0 +1,84 @@
1
+ name: Test & Build
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+
11
+ permissions:
12
+ contents: read
13
+ pull-requests: write
14
+
15
+ jobs:
16
+ test:
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ matrix:
20
+ node-version: [20.x]
21
+
22
+ steps:
23
+ - name: Checkout
24
+ uses: actions/checkout@v4
25
+
26
+ - name: Setup Node.js ${{ matrix.node-version }}
27
+ uses: actions/setup-node@v4
28
+ with:
29
+ node-version: ${{ matrix.node-version }}
30
+
31
+ - name: Setup pnpm
32
+ uses: pnpm/action-setup@v4
33
+ with:
34
+ version: 10
35
+
36
+ - name: Get pnpm store directory
37
+ id: pnpm-store
38
+ shell: bash
39
+ run: |
40
+ # pnpm may return a non-zero code in some environments; fall back to a sane default
41
+ STORE_PATH="$(pnpm store path 2>/dev/null || echo "${HOME}/.pnpm-store")"
42
+ echo "store-path=$STORE_PATH" >> $GITHUB_OUTPUT
43
+
44
+ - name: Cache pnpm store
45
+ uses: actions/cache@v4
46
+ with:
47
+ path: ${{ steps.pnpm-store.outputs.store-path }}
48
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
49
+ restore-keys: ${{ runner.os }}-pnpm-store-
50
+
51
+ - name: Install dependencies
52
+ run: pnpm install --frozen-lockfile
53
+
54
+ - name: Type check
55
+ run: pnpm type-check
56
+
57
+ - name: Lint
58
+ run: pnpm lint
59
+ continue-on-error: true
60
+
61
+ - name: Build
62
+ run: pnpm build
63
+
64
+ - name: Verify build outputs
65
+ run: |
66
+ test -f dist/index.js || exit 1
67
+ test -f dist/index.cjs || exit 1
68
+ test -f dist/beans-mcp-server.cjs || exit 1
69
+ test -f dist/index.d.ts || exit 1
70
+ echo "✓ All build outputs verified"
71
+
72
+ - name: Test
73
+ run: pnpm test:coverage --reporter=junit --outputFile=test-report.junit.xml
74
+
75
+ - name: Upload test results to Codecov
76
+ if: ${{ !cancelled() }}
77
+ uses: codecov/test-results-action@v1
78
+ with:
79
+ token: ${{ secrets.CODECOV_TOKEN }}
80
+
81
+ - name: Upload coverage reports to Codecov
82
+ uses: codecov/codecov-action@v5
83
+ with:
84
+ token: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1 @@
1
+ npx lint-staged
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 20
package/.oxfmtrc.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
3
+ "ignorePatterns": ["node_modules", "dist"],
4
+ "arrowParens": "avoid",
5
+ "bracketSpacing": true,
6
+ "printWidth": 120,
7
+ "proseWrap": "preserve",
8
+ "semi": true,
9
+ "singleQuote": true,
10
+ "tabWidth": 2
11
+ }
package/.oxlintrc.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": [
4
+ "unicorn",
5
+ "typescript",
6
+ "oxc",
7
+ "import",
8
+ "promise",
9
+ "node",
10
+ "vitest"
11
+ ],
12
+ "categories": {},
13
+ "rules": {},
14
+ "settings": {
15
+ "jsdoc": {
16
+ "ignorePrivate": false,
17
+ "ignoreInternal": false,
18
+ "ignoreReplacesDocs": true,
19
+ "overrideReplacesDocs": true,
20
+ "augmentsExtendsReplacesDocs": false,
21
+ "implementsReplacesDocs": false,
22
+ "exemptDestructuredRootsFromChecks": false,
23
+ "tagNamePreference": {}
24
+ },
25
+ "vitest": {
26
+ "typecheck": false
27
+ }
28
+ },
29
+ "env": {
30
+ "builtin": true
31
+ },
32
+ "globals": {},
33
+ "ignorePatterns": [
34
+ "node_modules",
35
+ "dist"
36
+ ]
37
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "editor.defaultFormatter": "oxc.oxc-vscode"
3
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,160 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.3] - 2026-02-27
11
+
12
+ **Full Changelog**: https://github.com/selfagency/beans-mcp/compare/v0.1.2...v0.1.3
13
+
14
+ _Source: changes from v0.1.2 to v0.1.3._
15
+
16
+
17
+ ## [0.1.2] - 2026-02-27
18
+
19
+ ## What's Changed
20
+ * feat: support body updates and de-duplicate MCP results by @selfagency in https://github.com/selfagency/beans-mcp/pull/1
21
+
22
+ ## New Contributors
23
+ * @selfagency made their first contribution in https://github.com/selfagency/beans-mcp/pull/1
24
+
25
+ **Full Changelog**: https://github.com/selfagency/beans-mcp/compare/v0.1.1...v0.1.2
26
+
27
+ _Source: changes from v0.1.1 to v0.1.2._
28
+
29
+
30
+ ## [0.1.1] - 2026-02-25
31
+
32
+ **Full Changelog**: https://github.com/selfagency/beans-mcp/compare/v0.1.0...v0.1.1
33
+
34
+ _Source: changes from v0.1.0 to v0.1.1._
35
+
36
+
37
+ ## [0.1.0] - 2026-02-25
38
+
39
+ **Full Changelog**: https://github.com/selfagency/beans-mcp/commits/v0.1.0
40
+
41
+
42
+ Initial public release. Extracted and substantially reworked from the
43
+ [selfagency.beans-vscode](https://marketplace.visualstudio.com/items?itemName=selfagency.beans-vscode)
44
+ VS Code extension's embedded MCP server into a standalone, independently
45
+ installable package.
46
+
47
+ ### Added
48
+
49
+ #### MCP Tools
50
+
51
+ All 14 Beans MCP tools are implemented and registered:
52
+
53
+ - `beans_init` — Initialize the workspace (optional prefix).
54
+ - `beans_list` — List beans with filtering by status, type, tags, and search.
55
+ - `beans_view` — View a single bean by ID.
56
+ - `beans_create` — Create a new bean.
57
+ - `beans_update` / `beans_edit` — Update an existing bean (aliases).
58
+ - `beans_reopen` — Reopen a completed or scrapped bean.
59
+ - `beans_delete` — Delete a draft or scrapped bean.
60
+ - `beans_set_status` — Set a bean's status directly.
61
+ - `beans_query` — Run llm_context, refresh, and workspace-instructions operations.
62
+ - `beans_bean_file` — Read, edit, create, or delete raw bean markdown files.
63
+ - `beans_output` — Read the Beans CLI output log.
64
+ - `beans_open_config` — Return the workspace config file path and content.
65
+ - `beans_graphql_schema` — Return the Beans GraphQL schema.
66
+
67
+ #### Public API
68
+
69
+ - `createBeansMcpServer(opts)` — Programmatic factory for embedding a Beans
70
+ MCP server in other applications; accepts an optional `backend` parameter
71
+ for dependency injection.
72
+ - `startBeansMcpServer(argv)` — CLI entrypoint; launches the server with a
73
+ `StdioServerTransport`.
74
+ - `parseCliArgs(argv)` — Parse and validate CLI arguments; returns a
75
+ `workspaceExplicit` flag so callers can distinguish user-supplied roots from
76
+ the cwd default.
77
+ - `BeansCliBackend` — Concrete backend that shells out to the `beans` CLI.
78
+ - `BackendInterface` — Interface for custom backend implementations.
79
+ - `MutableBackend` — Thin delegation wrapper whose inner backend can be
80
+ hot-swapped after MCP roots discovery without re-registering tools.
81
+ - `resolveWorkspaceFromRoots(server)` — Queries the connected client's
82
+ declared MCP roots and returns the first `file://` path as a local workspace
83
+ path, or `null` if none are declared.
84
+ - `sortBeans`, `isPathWithinRoot`, `makeTextAndStructured` — Utility helpers.
85
+
86
+ #### Workspace Resolution
87
+
88
+ The server resolves its workspace in priority order:
89
+
90
+ 1. `--workspace-root` / positional CLI argument (explicit)
91
+ 2. MCP roots declared by the connected client (`roots/list`)
92
+ 3. `process.cwd()` (fallback)
93
+
94
+ This enables using the server without CLI arguments: AI clients that declare
95
+ MCP roots (e.g. Cursor, Claude Desktop) automatically provide the workspace
96
+ path after connecting.
97
+
98
+ #### CLI
99
+
100
+ - `beans-mcp` binary accepts:
101
+ - Positional or `--workspace-root` for the workspace path.
102
+ - `--cli-path` — path to the `beans` executable (default: `beans`).
103
+ - `--port` — MCP server port (default: 39173).
104
+ - `--log-dir` — log directory (defaults to workspace root).
105
+ - `-h` / `--help` — print usage and exit.
106
+
107
+ #### Build
108
+
109
+ - Multi-config `tsup.config.ts` produces three outputs:
110
+ - ESM library (`dist/index.js` + `dist/index.d.ts`)
111
+ - CJS library (`dist/index.cjs`)
112
+ - CJS CLI binary (`dist/beans-mcp-server.cjs`) with `#!/usr/bin/env node` shebang
113
+ - All CJS configs use `target: 'node18'`, `splitting: false`, `cjsInterop: true`.
114
+ - `postbuild` script writes a trimmed `dist/package.json` with correct `bin`,
115
+ `exports`, `main`, `module`, and `types` fields.
116
+
117
+ #### Tests
118
+
119
+ - **Protocol E2E tests** (`src/test/protocol.e2e.test.ts`) — 52 tests using
120
+ `InMemoryTransport` + MCP `Client` to exercise the full JSON-RPC wire format,
121
+ Zod input validation, backend error surfacing as `{ isError: true }` tool
122
+ results, and the MCP roots protocol.
123
+ - **`startBeansMcpServer` integration tests** (`src/test/startBeansMcpServer.test.ts`)
124
+ — mocked dynamic imports for `BeansCliBackend` and `StdioServerTransport`.
125
+ - Handler unit tests — exported handler factories tested in isolation.
126
+ - `MutableBackend` unit tests — delegation and `setInner` swap behaviour.
127
+ - `resolveWorkspaceFromRoots` unit tests — all branches (found, skipped,
128
+ empty list, throws).
129
+ - `parseCliArgs` tests — `workspaceExplicit` flag, `--help`/`-h` output and
130
+ exit code.
131
+ - Statement and function coverage: **100%** for `BeansMcpServer.ts`.
132
+
133
+ #### CI
134
+
135
+ - GitHub Actions workflow runs lint, type-check, build, and test on Node 18
136
+ and 22 across Ubuntu and macOS.
137
+ - pnpm store cache keyed on lockfile hash with `~/.pnpm-store` fallback.
138
+
139
+ ### Changed
140
+
141
+ - Tool IDs renamed to remove the `_vscode` suffix carried over from the
142
+ extension (e.g. `beans_init_vscode` → `beans_init`).
143
+ - `--log-dir` now defaults to the workspace root when omitted.
144
+ - `cli.ts` simplified: removed the `isMainModule` guard; always invokes
145
+ `startBeansMcpServer`.
146
+ - Bin command renamed from `beans-mcp-server` to `beans-mcp`.
147
+
148
+ ### Fixed
149
+
150
+ - Build script was overriding `tsup.config.ts` with inline CLI flags, causing
151
+ the CLI binary to never be produced. Fixed by setting `"build": "tsup"`.
152
+ - `package.json` exports paths corrected to include the `dist/` prefix.
153
+ - Eliminated all `any` types: `queryHandler` opts, `backend.ts` filter
154
+ parameter, and `queryHelpers.ts` return type narrowed to
155
+ `Record<string, unknown>`.
156
+ - README: corrected package import name (`@selfagency/beans-mcp`), server
157
+ default name (`beans-mcp-server`), removed the non-existent `allowedRoots`
158
+ option from the `createBeansMcpServer` docs.
159
+
160
+ [Unreleased]: https://github.com/selfagency/beans-mcp/compare/v0.1.0...HEAD