bluera-knowledge 0.11.12 → 0.11.14

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluera-knowledge",
3
- "version": "0.11.12",
3
+ "version": "0.11.14",
4
4
  "description": "Clone repos, crawl docs, search locally. Fast, authoritative answers for AI coding agents.",
5
5
  "mcpServers": {
6
6
  "bluera-knowledge": {
package/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [0.11.14](https://github.com/blueraai/bluera-knowledge/compare/v0.11.6...v0.11.14) (2026-01-10)
6
+
7
+
8
+ ### Features
9
+
10
+ * **scripts:** add post-release npm validation script ([e4c29a0](https://github.com/blueraai/bluera-knowledge/commit/e4c29a0c83907de4bc293a69a58412629457fb22))
11
+ * **scripts:** add suggest, sync, serve, mcp tests to npm validation ([49d85da](https://github.com/blueraai/bluera-knowledge/commit/49d85dad1a89691060c12f152d644844baf6e6e6))
12
+ * **scripts:** log expected vs installed version in validation script ([c77d039](https://github.com/blueraai/bluera-knowledge/commit/c77d039b27a3ccf54d50006af161ac4dcfea7b21))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **cli:** plugin-api commands now respect global options ([d3cca02](https://github.com/blueraai/bluera-knowledge/commit/d3cca02ffc679ffc187b76c7682f3cc177eabdea))
18
+ * **plugin:** properly close services after command execution ([eeaf743](https://github.com/blueraai/bluera-knowledge/commit/eeaf743750be73fd9c7a9e72440b2fd0fb5a53fa))
19
+ * **scripts:** show real-time output in validation script ([8a4bdec](https://github.com/blueraai/bluera-knowledge/commit/8a4bdec8b63c504d34ba35bfe19da795f7f7fd07))
20
+ * **scripts:** use mktemp for temp directories in validation script ([3107861](https://github.com/blueraai/bluera-knowledge/commit/3107861bd7a966016fde2a121469dd84756f39be))
21
+ * **search:** add defaults for env vars so CLI works standalone ([b2d2ce5](https://github.com/blueraai/bluera-knowledge/commit/b2d2ce534e8cd2ba0fc0abdac505c912a1a76035))
22
+
23
+ ## [0.11.13](https://github.com/blueraai/bluera-knowledge/compare/v0.11.6...v0.11.13) (2026-01-10)
24
+
25
+
26
+ ### Features
27
+
28
+ * **scripts:** add post-release npm validation script ([e4c29a0](https://github.com/blueraai/bluera-knowledge/commit/e4c29a0c83907de4bc293a69a58412629457fb22))
29
+ * **scripts:** add suggest, sync, serve, mcp tests to npm validation ([49d85da](https://github.com/blueraai/bluera-knowledge/commit/49d85dad1a89691060c12f152d644844baf6e6e6))
30
+ * **scripts:** log expected vs installed version in validation script ([c77d039](https://github.com/blueraai/bluera-knowledge/commit/c77d039b27a3ccf54d50006af161ac4dcfea7b21))
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * **cli:** plugin-api commands now respect global options ([d3cca02](https://github.com/blueraai/bluera-knowledge/commit/d3cca02ffc679ffc187b76c7682f3cc177eabdea))
36
+ * **plugin:** properly close services after command execution ([eeaf743](https://github.com/blueraai/bluera-knowledge/commit/eeaf743750be73fd9c7a9e72440b2fd0fb5a53fa))
37
+ * **scripts:** show real-time output in validation script ([8a4bdec](https://github.com/blueraai/bluera-knowledge/commit/8a4bdec8b63c504d34ba35bfe19da795f7f7fd07))
38
+ * **scripts:** use mktemp for temp directories in validation script ([3107861](https://github.com/blueraai/bluera-knowledge/commit/3107861bd7a966016fde2a121469dd84756f39be))
39
+ * **search:** add defaults for env vars so CLI works standalone ([b2d2ce5](https://github.com/blueraai/bluera-knowledge/commit/b2d2ce534e8cd2ba0fc0abdac505c912a1a76035))
40
+
5
41
  ## [0.11.12](https://github.com/blueraai/bluera-knowledge/compare/v0.11.6...v0.11.12) (2026-01-10)
6
42
 
7
43
 
package/README.md CHANGED
@@ -122,6 +122,47 @@ Bluera Knowledge enables option 3 by building a searchable knowledge base from *
122
122
 
123
123
  ---
124
124
 
125
+ ## 🎯 When Claude Code Should Query BK
126
+
127
+ **The simple rule: Query BK for any question about libraries, dependencies, or reference material.**
128
+
129
+ BK is cheap (~100ms, no rate limits), authoritative (actual source code), and complete (includes tests and internal APIs). Claude Code should query it frequently for external code questions.
130
+
131
+ ### Always Query BK For:
132
+
133
+ | Question Type | Examples |
134
+ |--------------|----------|
135
+ | **Library internals** | "How does Express handle middleware errors?", "What does useEffect cleanup do?" |
136
+ | **API signatures** | "What parameters does axios.create() accept?", "What options can I pass to Hono?" |
137
+ | **Error handling** | "What errors can Zod throw?", "Why might this library return undefined?" |
138
+ | **Version behavior** | "What changed in React 18?", "Is this method deprecated?" |
139
+ | **Configuration** | "What config options exist for Vite?", "What are the defaults?" |
140
+ | **Testing patterns** | "How do the library authors test this?", "How should I mock this?" |
141
+ | **Performance/internals** | "Is this cached internally?", "What's the complexity?" |
142
+ | **Security** | "How does this library validate input?", "Is this safe against injection?" |
143
+ | **Integration** | "How do I integrate X with Y?", "What's the idiomatic way to use this?" |
144
+
145
+ ### DO NOT Query BK For:
146
+
147
+ | Question Type | Use Instead |
148
+ |--------------|-------------|
149
+ | **Your project code** | Grep/Read directly ("Where is OUR auth middleware?") |
150
+ | **General concepts** | Training data ("What is a closure?") |
151
+ | **Breaking news** | Web search ("Latest React release notes") |
152
+
153
+ ### Quick Pattern Matching:
154
+
155
+ ```
156
+ "How does [library] work..." → Query BK
157
+ "What does [library function] do..." → Query BK
158
+ "What options does [library] accept..."→ Query BK
159
+ "What errors can [library] throw..." → Query BK
160
+ "Where is [thing] in OUR code..." → Grep/Read directly
161
+ "What is [general concept]..." → Training data
162
+ ```
163
+
164
+ ---
165
+
125
166
  ## 💰 Token Efficiency
126
167
 
127
168
  Beyond speed and accuracy, Bluera Knowledge can **significantly reduce token consumption** for code-related queries—typically saving 60-75% compared to web search approaches.
@@ -196,12 +237,15 @@ Bluera Knowledge isn't always the most token-efficient choice:
196
237
 
197
238
  ### 💡 Best Practice
198
239
 
199
- Let Claude Code decide when to use Bluera Knowledge:
200
- - For **library-specific, version-specific, or implementation questions** → BK saves tokens and increases accuracy
201
- - For **general programming concepts** Training data is more efficient
202
- - For **current events** → Web search is necessary
240
+ **Default to BK for library questions.** It's cheap, fast, and authoritative:
241
+
242
+ | Question Type | Action | Why |
243
+ |--------------|--------|-----|
244
+ | Library internals, APIs, errors, versions, config | **Query BK first** | Source code is definitive, 60-85% token savings |
245
+ | General programming concepts | Skip BK | Training data is sufficient |
246
+ | Breaking news, release notes | Web search | BK only has indexed content |
203
247
 
204
- The plugin's Skills teach Claude Code these patterns, so it automatically uses the most efficient approach for each question.
248
+ The plugin's Skills teach Claude Code these patterns automatically. When in doubt about a dependency, query BK—it's faster and more accurate than guessing or web searching.
205
249
 
206
250
  ---
207
251
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluera-knowledge",
3
- "version": "0.11.12",
3
+ "version": "0.11.14",
4
4
  "description": "CLI tool for managing knowledge stores with semantic search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -7,40 +7,85 @@ description: Teaches how to use Bluera Knowledge for accessing library sources a
7
7
 
8
8
  BK provides access to **definitive library sources** for your project dependencies.
9
9
 
10
- ## Purpose: Authoritative References, Not Project Search
10
+ ## The Rule: Query BK for External Code
11
11
 
12
- **CKB is for**: Reference material and external sources
13
- - **Library sources**: Clone Vue.js/Pydantic/Hono for authoritative API reference
14
- - **Specifications**: Add project requirements, API specs, RFCs
15
- - **Documentation**: Add design docs, architecture guides, research papers
16
- - **Reference material**: Best practices, coding standards, examples
12
+ **Any question about libraries, dependencies, or indexed reference material should query BK.**
17
13
 
18
- **CKB is NOT for**: Searching your current project code
19
- - Use Grep/Read directly on project files
20
- - BK stores are for external reference material
14
+ BK is:
15
+ - **Cheap**: ~100ms response, unlimited queries, no rate limits
16
+ - **Authoritative**: Actual source code, not blog posts or training data
17
+ - **Complete**: Includes tests, examples, internal APIs, configuration
18
+
19
+ ## Always Query BK For:
20
+
21
+ **Library implementation:**
22
+ - "How does Express handle middleware errors?"
23
+ - "What does React's useEffect cleanup actually do?"
24
+ - "How is Pydantic validation implemented?"
25
+
26
+ **API signatures and options:**
27
+ - "What parameters does axios.create() accept?"
28
+ - "What options can I pass to hono.use()?"
29
+ - "What's the signature of zod.object()?"
30
+
31
+ **Error handling:**
32
+ - "What errors can this library throw?"
33
+ - "Why might this function return undefined?"
34
+ - "What validation does Zod perform?"
35
+
36
+ **Version-specific behavior:**
37
+ - "What changed in React 18?"
38
+ - "Is this deprecated in Express 5?"
39
+ - "Does my version support this?"
40
+
41
+ **Configuration:**
42
+ - "What config options exist for Vite?"
43
+ - "What are the default values?"
44
+ - "What environment variables does this use?"
45
+
46
+ **Testing:**
47
+ - "How do the library authors test this?"
48
+ - "How should I mock this in tests?"
49
+ - "What edge cases do the tests cover?"
50
+
51
+ **Performance:**
52
+ - "Is this cached internally?"
53
+ - "What's the complexity of this operation?"
54
+ - "Does this run async or sync?"
55
+
56
+ **Security:**
57
+ - "How does this validate input?"
58
+ - "Is this safe against injection?"
59
+ - "How are credentials handled?"
60
+
61
+ **Integration:**
62
+ - "How do I integrate X with Y?"
63
+ - "What's the idiomatic usage pattern?"
64
+ - "How do examples in the library do this?"
21
65
 
22
66
  ## Two Ways to Access Library Sources
23
67
 
24
- ### 1. Vector Search (MCP or slash command)
25
- Find concepts and patterns across library docs:
68
+ ### 1. Vector Search (Discovery)
69
+ Find concepts and patterns across indexed content:
26
70
  ```
27
71
  search("vue reactivity system")
28
72
  /bluera-knowledge:search "pydantic custom validators"
29
73
  ```
30
74
 
31
- ### 2. Direct File Access (Grep/Read)
75
+ ### 2. Direct File Access (Precision)
32
76
  Precise lookups in cloned library source:
33
77
  ```
34
78
  Grep: pattern="defineReactive" path=".bluera/bluera-knowledge/repos/vue/"
35
79
  Read: .bluera/bluera-knowledge/repos/pydantic/pydantic/validators.py
36
80
  ```
37
81
 
38
- ## Both Are Valid!
82
+ Both are valid! Use vector search for discovery, Grep/Read for specific functions.
39
83
 
40
- You can use **either or both** approaches on the same cloned repo:
41
- - Vector search to discover relevant files
42
- - Grep/Read to find specific functions/classes
43
- - Or just Grep/Read if you know what you're looking for
84
+ ## DO NOT Query BK For:
85
+
86
+ - **Your project code** Use Grep/Read directly
87
+ - **General concepts** Use training data ("What is a closure?")
88
+ - **Breaking news** → Use web search ("Latest React release")
44
89
 
45
90
  ## Example Workflow
46
91
 
@@ -52,3 +97,14 @@ Claude:
52
97
  3. Read file: `.bluera/bluera-knowledge/repos/vue/packages/reactivity/src/computed.ts`
53
98
  4. Grep for implementation: pattern="class ComputedRefImpl"
54
99
  5. Explain with authoritative source code examples
100
+
101
+ ## Quick Reference
102
+
103
+ ```
104
+ [library] question → Query BK
105
+ [your code] question → Grep/Read directly
106
+ [concept] question → Training data
107
+ [news/updates] question → Web search
108
+ ```
109
+
110
+ BK is cheap and fast. Query it liberally for library questions.
@@ -3,64 +3,158 @@ name: when-to-query
3
3
  description: Decision guide for when to query Bluera Knowledge stores vs using Grep/Read on current project. Query BK for library/dependency questions and reference material. Use Grep/Read for current project code, debugging, and implementation details. Includes setup instructions and mental model.
4
4
  ---
5
5
 
6
- # When to Query BK vs Current Project
6
+ # When to Query Bluera Knowledge
7
7
 
8
- ## Query BK When:
8
+ ## The Rule: BK First for External Code
9
9
 
10
- **Questions about libraries/dependencies:**
11
- - "How does Vue's reactivity system work?"
12
- - "What are Pydantic's built-in validators?"
13
- - "How should I use Pino's child loggers?"
14
- - "What middleware does Hono provide?"
10
+ **When the question involves libraries, dependencies, or reference material, query BK first.**
15
11
 
16
- **Reference material questions:**
17
- - "What does the API spec say about authentication?"
18
- - "What are the project requirements for error handling?"
19
- - "How does the architecture doc describe the data flow?"
20
- - "What coding standards apply to this project?"
12
+ BK provides authoritative source code from the actual libraries. This is:
13
+ - **More accurate** than training data (which may be outdated)
14
+ - **Faster** than web search (~100ms vs 2-5 seconds)
15
+ - **More complete** than documentation sites (includes tests, examples, internal APIs)
16
+ - **Zero rate limits** (local, unlimited queries)
21
17
 
22
- **Learning library APIs:**
23
- - Discovering available options/configs
24
- - Finding usage examples from library itself
25
- - Understanding internal implementation
18
+ ---
26
19
 
27
- **Verifying specifications:**
28
- - Checking exact requirements
29
- - Finding edge cases in specs
30
- - Understanding design decisions
20
+ ## ALWAYS Query BK For:
21
+
22
+ ### Library Implementation Questions
23
+ - "How does Express handle middleware errors?"
24
+ - "What does `useEffect` cleanup actually do internally?"
25
+ - "How is Pydantic validation implemented?"
26
+ - "What happens when lodash `debounce` is called?"
27
+ - "How does React's reconciliation work?"
28
+
29
+ ### API and Method Questions
30
+ - "What parameters does `axios.create()` accept?"
31
+ - "What's the signature of `zod.object()`?"
32
+ - "What options can I pass to `hono.use()`?"
33
+ - "What events does EventEmitter emit?"
34
+ - "What methods are available on `prisma.client`?"
35
+
36
+ ### Error and Exception Handling
37
+ - "What errors can this library throw?"
38
+ - "How do I catch validation errors in Zod?"
39
+ - "What does this error code mean in library X?"
40
+ - "Why might this function return undefined?"
41
+ - "What validation does this library perform?"
42
+
43
+ ### Version-Specific Behavior
44
+ - "What changed in React 18's concurrent mode?"
45
+ - "How does this work in Express 4 vs 5?"
46
+ - "Is this method deprecated in the latest version?"
47
+ - "What's the migration path from v2 to v3?"
48
+ - "Does my version support this feature?"
49
+
50
+ ### Configuration and Options
51
+ - "What configuration options exist for Vite?"
52
+ - "What are the default values for these options?"
53
+ - "How do I customize the behavior of X?"
54
+ - "What environment variables does this library use?"
55
+ - "What's the full schema for this config?"
56
+
57
+ ### Testing Patterns
58
+ - "How do the library authors test this feature?"
59
+ - "How should I mock this library in tests?"
60
+ - "What fixtures do I need for testing this integration?"
61
+ - "What edge cases does the library's test suite cover?"
62
+
63
+ ### Performance and Internals
64
+ - "Is this operation cached internally?"
65
+ - "What's the time complexity of this method?"
66
+ - "How is this optimized in the library?"
67
+ - "Does this run synchronously or asynchronously?"
68
+ - "What's the memory footprint of this?"
69
+
70
+ ### Security and Validation
71
+ - "How does this library validate input?"
72
+ - "What sanitization is applied?"
73
+ - "How are credentials handled internally?"
74
+ - "Is this safe against injection attacks?"
75
+
76
+ ### Integration and Patterns
77
+ - "How do I integrate library X with library Y?"
78
+ - "What's the idiomatic way to use this API?"
79
+ - "How do examples in the library do this?"
80
+ - "What patterns does this library use?"
81
+ - "What's the recommended project structure?"
82
+
83
+ ### Reference Material
84
+ - "What does the API spec say about X?"
85
+ - "What are the project requirements for Y?"
86
+ - "How does the architecture doc describe Z?"
87
+ - "What coding standards apply here?"
31
88
 
32
- ## Query Current Project (Grep/Read) When:
89
+ ---
33
90
 
34
- **Working on YOUR code:**
35
- - "Where is the authentication middleware?"
36
- - "Find all API endpoints"
37
- - "Show me the database models"
91
+ ## DO NOT Query BK For:
38
92
 
39
- **Debugging YOUR code:**
40
- - Reading error traces
41
- - Following call stacks
42
- - Checking variable usage
93
+ ### Current Project Code
94
+ Use Grep/Read directly:
95
+ - "Where is the authentication middleware in THIS project?"
96
+ - "Show me OUR database models"
97
+ - "Find all API endpoints WE defined"
43
98
 
44
- ## Setup First: Add Important Dependencies
99
+ ### General Concepts
100
+ Use training data (no tool needed):
101
+ - "What is a closure in JavaScript?"
102
+ - "Explain dependency injection"
103
+ - "What is REST?"
45
104
 
46
- Before BK is useful, you need to add library sources:
105
+ ### Current Events
106
+ Use web search:
107
+ - "What's new in Next.js 15?"
108
+ - "Latest release notes for TypeScript"
109
+ - "Security advisory for npm packages"
47
110
 
111
+ ---
112
+
113
+ ## Setup: Index Your Dependencies
114
+
115
+ BK only knows what you've indexed. Add your key dependencies:
116
+
117
+ ```bash
118
+ # Get suggestions based on package.json
119
+ /bluera-knowledge:suggest
120
+
121
+ # Add important libraries
122
+ /bluera-knowledge:add-repo https://github.com/expressjs/express
123
+ /bluera-knowledge:add-repo https://github.com/honojs/hono
124
+
125
+ # Index local docs
126
+ /bluera-knowledge:add-folder ./docs --name=project-docs
127
+
128
+ # Verify what's indexed
129
+ /bluera-knowledge:stores
48
130
  ```
49
- /bk:suggest # Get recommendations
50
- /bk:add-repo <url> --name=<lib> # Add important libs
51
- /bk:stores # Verify what's indexed
52
- ```
131
+
132
+ ---
133
+
134
+ ## Quick Reference
135
+
136
+ | Question Pattern | Use |
137
+ |-----------------|-----|
138
+ | "How does [library] work..." | BK |
139
+ | "What does [library function] do..." | BK |
140
+ | "What options/params does [library] accept..." | BK |
141
+ | "What errors can [library] throw..." | BK |
142
+ | "How should I use [library API]..." | BK |
143
+ | "What changed in [library version]..." | BK |
144
+ | "How do I integrate [library]..." | BK |
145
+ | "Where is [thing] in OUR code..." | Grep/Read |
146
+ | "What is [general concept]..." | Training data |
147
+ | "What's new in [library] today..." | Web search |
148
+
149
+ ---
53
150
 
54
151
  ## Mental Model
55
152
 
56
153
  ```
57
- Current Project FilesGrep/Read directly
58
- vs
59
- Library Sources (Vue, Pydantic, etc.) BK (vector search OR Grep/Read)
154
+ External Code (libraries, deps, specs) Query BK
155
+ Your Project Code → Grep/Read directly
156
+ General Knowledge Use training data
157
+ Breaking News → Web search
60
158
  ```
61
159
 
62
- BK gives you both ways to access library sources:
63
- 1. Semantic search for discovery
64
- 2. Grep/Read for precision
65
-
66
- Use whichever works best for your question!
160
+ BK is cheap, fast, and authoritative. When in doubt about a library, query BK.
@@ -0,0 +1,250 @@
1
+ import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
2
+ import { spawn } from 'node:child_process';
3
+ import { rm, mkdtemp, writeFile, mkdir } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import * as readline from 'node:readline';
7
+
8
+ /**
9
+ * MCP Server Integration Tests
10
+ *
11
+ * Tests that the MCP server actually starts and accepts JSON-RPC messages.
12
+ * Unlike unit tests that mock runMCPServer, these tests spawn a real
13
+ * MCP server process and communicate via stdin/stdout.
14
+ */
15
+ describe('MCP Integration', () => {
16
+ let tempDir: string;
17
+ let testFilesDir: string;
18
+
19
+ beforeAll(async () => {
20
+ tempDir = await mkdtemp(join(tmpdir(), 'mcp-test-'));
21
+ testFilesDir = join(tempDir, 'files');
22
+ await mkdir(testFilesDir, { recursive: true });
23
+ await writeFile(
24
+ join(testFilesDir, 'test.md'),
25
+ '# Test Document\n\nContent for MCP integration testing.'
26
+ );
27
+ }, 30000);
28
+
29
+ afterAll(async () => {
30
+ await rm(tempDir, { recursive: true, force: true });
31
+ });
32
+
33
+ interface JSONRPCResponse {
34
+ jsonrpc: string;
35
+ id: number;
36
+ result?: unknown;
37
+ error?: { code: number; message: string };
38
+ }
39
+
40
+ interface MCPClient {
41
+ proc: ChildProcess;
42
+ sendRequest: (method: string, params?: Record<string, unknown>) => Promise<JSONRPCResponse>;
43
+ close: () => void;
44
+ }
45
+
46
+ /**
47
+ * Start the MCP server and return a client with message helpers
48
+ */
49
+ function startMCPServer(): MCPClient {
50
+ const proc = spawn('node', ['dist/mcp/server.js'], {
51
+ stdio: ['pipe', 'pipe', 'pipe'],
52
+ env: {
53
+ ...process.env,
54
+ PROJECT_ROOT: tempDir,
55
+ DATA_DIR: join(tempDir, 'data'),
56
+ CONFIG_PATH: join(tempDir, 'config.json'),
57
+ },
58
+ });
59
+
60
+ // Set up a single readline interface for the entire process lifetime
61
+ const rl = readline.createInterface({ input: proc.stdout! });
62
+ const pendingRequests = new Map<
63
+ number,
64
+ { resolve: (r: JSONRPCResponse) => void; reject: (e: Error) => void }
65
+ >();
66
+ let requestId = 0;
67
+
68
+ rl.on('line', (line: string) => {
69
+ try {
70
+ const response = JSON.parse(line) as JSONRPCResponse;
71
+ const pending = pendingRequests.get(response.id);
72
+ if (pending !== undefined) {
73
+ pendingRequests.delete(response.id);
74
+ pending.resolve(response);
75
+ }
76
+ } catch {
77
+ // Ignore non-JSON lines (like log output)
78
+ }
79
+ });
80
+
81
+ return {
82
+ proc,
83
+ sendRequest: (
84
+ method: string,
85
+ params: Record<string, unknown> = {}
86
+ ): Promise<JSONRPCResponse> => {
87
+ return new Promise((resolve, reject) => {
88
+ if (proc.stdin === null) {
89
+ reject(new Error('Process stdin not available'));
90
+ return;
91
+ }
92
+
93
+ const id = ++requestId;
94
+ const timeout = setTimeout(() => {
95
+ pendingRequests.delete(id);
96
+ reject(new Error(`Timeout waiting for response to ${method}`));
97
+ }, 30000);
98
+
99
+ pendingRequests.set(id, {
100
+ resolve: (r) => {
101
+ clearTimeout(timeout);
102
+ resolve(r);
103
+ },
104
+ reject: (e) => {
105
+ clearTimeout(timeout);
106
+ reject(e);
107
+ },
108
+ });
109
+
110
+ const request = JSON.stringify({
111
+ jsonrpc: '2.0',
112
+ id,
113
+ method,
114
+ params,
115
+ });
116
+
117
+ proc.stdin.write(request + '\n');
118
+ });
119
+ },
120
+ close: (): void => {
121
+ rl.close();
122
+ proc.kill('SIGTERM');
123
+ },
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Wait for process to be ready
129
+ */
130
+ async function waitForReady(client: MCPClient, timeoutMs = 10000): Promise<void> {
131
+ return new Promise((resolve, reject) => {
132
+ const timeout = setTimeout(() => {
133
+ reject(new Error('Timeout waiting for MCP server to start'));
134
+ }, timeoutMs);
135
+
136
+ // MCP servers typically become ready quickly
137
+ // Give it a moment to initialize
138
+ setTimeout(() => {
139
+ clearTimeout(timeout);
140
+ resolve();
141
+ }, 1000);
142
+
143
+ client.proc.on('error', (err) => {
144
+ clearTimeout(timeout);
145
+ reject(err);
146
+ });
147
+
148
+ client.proc.on('exit', (code) => {
149
+ clearTimeout(timeout);
150
+ if (code !== 0 && code !== null) {
151
+ reject(new Error(`MCP server exited with code ${code}`));
152
+ }
153
+ });
154
+ });
155
+ }
156
+
157
+ let mcpClient: MCPClient | null = null;
158
+
159
+ afterEach(async () => {
160
+ // Clean up MCP client if still running
161
+ if (mcpClient !== null) {
162
+ mcpClient.close();
163
+ await new Promise<void>((resolve) => {
164
+ if (mcpClient !== null) {
165
+ mcpClient.proc.on('exit', () => resolve());
166
+ setTimeout(() => {
167
+ if (mcpClient !== null) {
168
+ mcpClient.proc.kill('SIGKILL');
169
+ }
170
+ resolve();
171
+ }, 2000);
172
+ } else {
173
+ resolve();
174
+ }
175
+ });
176
+ mcpClient = null;
177
+ }
178
+ });
179
+
180
+ it('starts, lists tools, and handles tool execution', async () => {
181
+ mcpClient = startMCPServer();
182
+ await waitForReady(mcpClient);
183
+
184
+ // 1. Initialize
185
+ const initResponse = await mcpClient.sendRequest('initialize', {
186
+ protocolVersion: '2024-11-05',
187
+ capabilities: {},
188
+ clientInfo: { name: 'test-client', version: '1.0.0' },
189
+ });
190
+
191
+ expect(initResponse.jsonrpc).toBe('2.0');
192
+ expect(initResponse.error).toBeUndefined();
193
+ expect(initResponse.result).toBeDefined();
194
+
195
+ const initResult = initResponse.result as {
196
+ protocolVersion: string;
197
+ serverInfo: { name: string; version: string };
198
+ capabilities: { tools: Record<string, unknown> };
199
+ };
200
+ expect(initResult.serverInfo.name).toBe('bluera-knowledge');
201
+ expect(initResult.capabilities.tools).toBeDefined();
202
+
203
+ // 2. List tools
204
+ const toolsResponse = await mcpClient.sendRequest('tools/list', {});
205
+
206
+ expect(toolsResponse.error).toBeUndefined();
207
+ expect(toolsResponse.result).toBeDefined();
208
+
209
+ const toolsResult = toolsResponse.result as {
210
+ tools: Array<{ name: string; description: string }>;
211
+ };
212
+ expect(Array.isArray(toolsResult.tools)).toBe(true);
213
+
214
+ const toolNames = toolsResult.tools.map((t) => t.name);
215
+ expect(toolNames).toContain('search');
216
+ expect(toolNames).toContain('get_full_context');
217
+ expect(toolNames).toContain('execute');
218
+
219
+ // 3. Call search tool
220
+ const searchResponse = await mcpClient.sendRequest('tools/call', {
221
+ name: 'search',
222
+ arguments: {
223
+ query: 'test query',
224
+ limit: 5,
225
+ },
226
+ });
227
+
228
+ expect(searchResponse.error).toBeUndefined();
229
+ expect(searchResponse.result).toBeDefined();
230
+
231
+ const searchResult = searchResponse.result as {
232
+ content: Array<{ type: string; text: string }>;
233
+ };
234
+ expect(Array.isArray(searchResult.content)).toBe(true);
235
+
236
+ // 4. Call execute with help command
237
+ const helpResponse = await mcpClient.sendRequest('tools/call', {
238
+ name: 'execute',
239
+ arguments: {
240
+ command: 'help',
241
+ },
242
+ });
243
+
244
+ expect(helpResponse.error).toBeUndefined();
245
+ expect(helpResponse.result).toBeDefined();
246
+
247
+ const helpResult = helpResponse.result as { content: Array<{ type: string; text: string }> };
248
+ expect(helpResult.content[0]?.text).toContain('Available commands');
249
+ }, 120000);
250
+ });
@@ -0,0 +1,260 @@
1
+ import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
2
+ import { spawn, type ChildProcess } from 'node:child_process';
3
+ import { rm, mkdtemp, writeFile, mkdir } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+
7
+ /**
8
+ * Serve Command Integration Tests
9
+ *
10
+ * Tests that the HTTP server actually starts, responds to requests,
11
+ * and shuts down cleanly. Unlike unit tests that mock the serve function,
12
+ * these tests spawn a real server process and make actual HTTP requests.
13
+ */
14
+ describe('Serve Integration', () => {
15
+ let tempDir: string;
16
+ let testFilesDir: string;
17
+ let serverProcess: ChildProcess | null = null;
18
+ const TEST_PORT = 19877; // Use a unique port to avoid conflicts
19
+
20
+ beforeAll(async () => {
21
+ tempDir = await mkdtemp(join(tmpdir(), 'serve-test-'));
22
+ testFilesDir = join(tempDir, 'files');
23
+ await mkdir(testFilesDir, { recursive: true });
24
+ await writeFile(
25
+ join(testFilesDir, 'test.md'),
26
+ '# Test Document\n\nContent for serve integration testing.'
27
+ );
28
+ }, 30000);
29
+
30
+ afterAll(async () => {
31
+ await rm(tempDir, { recursive: true, force: true });
32
+ });
33
+
34
+ afterEach(async () => {
35
+ // Clean up server process if still running
36
+ if (serverProcess !== null) {
37
+ serverProcess.kill('SIGTERM');
38
+ await new Promise<void>((resolve) => {
39
+ if (serverProcess !== null) {
40
+ serverProcess.on('exit', () => resolve());
41
+ // Force kill after timeout
42
+ setTimeout(() => {
43
+ if (serverProcess !== null) {
44
+ serverProcess.kill('SIGKILL');
45
+ }
46
+ resolve();
47
+ }, 2000);
48
+ } else {
49
+ resolve();
50
+ }
51
+ });
52
+ serverProcess = null;
53
+ }
54
+ });
55
+
56
+ /**
57
+ * Start the serve command and wait for it to be ready
58
+ */
59
+ async function startServer(port: number): Promise<ChildProcess> {
60
+ return new Promise((resolve, reject) => {
61
+ const proc = spawn(
62
+ 'node',
63
+ ['dist/index.js', 'serve', '--port', String(port), '--data-dir', tempDir],
64
+ {
65
+ stdio: ['pipe', 'pipe', 'pipe'],
66
+ }
67
+ );
68
+
69
+ let resolved = false;
70
+ const timeout = setTimeout(() => {
71
+ if (!resolved) {
72
+ reject(new Error('Server startup timeout'));
73
+ proc.kill();
74
+ }
75
+ }, 30000);
76
+
77
+ proc.stdout?.on('data', (data: Buffer) => {
78
+ const output = data.toString();
79
+ if (output.includes('Starting server')) {
80
+ resolved = true;
81
+ clearTimeout(timeout);
82
+ // Give it a moment to actually start listening
83
+ setTimeout(() => resolve(proc), 500);
84
+ }
85
+ });
86
+
87
+ proc.stderr?.on('data', (data: Buffer) => {
88
+ // Log stderr but don't fail - some warnings are expected
89
+ console.error('Server stderr:', data.toString());
90
+ });
91
+
92
+ proc.on('error', (err) => {
93
+ if (!resolved) {
94
+ clearTimeout(timeout);
95
+ reject(err);
96
+ }
97
+ });
98
+
99
+ proc.on('exit', (code) => {
100
+ if (!resolved) {
101
+ clearTimeout(timeout);
102
+ reject(new Error(`Server exited with code ${String(code)}`));
103
+ }
104
+ });
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Make an HTTP request with timeout
110
+ */
111
+ async function fetchWithTimeout(
112
+ url: string,
113
+ options: RequestInit = {},
114
+ timeoutMs = 5000
115
+ ): Promise<Response> {
116
+ const controller = new AbortController();
117
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
118
+ try {
119
+ return await fetch(url, { ...options, signal: controller.signal });
120
+ } finally {
121
+ clearTimeout(timeoutId);
122
+ }
123
+ }
124
+
125
+ describe('Server Startup and Health', () => {
126
+ it('starts server and responds to /health', async () => {
127
+ serverProcess = await startServer(TEST_PORT);
128
+
129
+ const response = await fetchWithTimeout(`http://127.0.0.1:${TEST_PORT}/health`);
130
+ expect(response.ok).toBe(true);
131
+
132
+ const body = (await response.json()) as { status: string };
133
+ expect(body.status).toBe('ok');
134
+ }, 60000);
135
+
136
+ it('responds with CORS headers', async () => {
137
+ serverProcess = await startServer(TEST_PORT + 1);
138
+
139
+ const response = await fetchWithTimeout(`http://127.0.0.1:${TEST_PORT + 1}/health`, {
140
+ method: 'OPTIONS',
141
+ headers: {
142
+ Origin: 'http://localhost:3000',
143
+ 'Access-Control-Request-Method': 'GET',
144
+ },
145
+ });
146
+
147
+ // CORS preflight should succeed
148
+ expect(response.ok).toBe(true);
149
+ }, 60000);
150
+ });
151
+
152
+ describe('Store API', () => {
153
+ beforeEach(async () => {
154
+ serverProcess = await startServer(TEST_PORT + 2);
155
+ });
156
+
157
+ it('lists stores via GET /api/stores', async () => {
158
+ const response = await fetchWithTimeout(`http://127.0.0.1:${TEST_PORT + 2}/api/stores`);
159
+ expect(response.ok).toBe(true);
160
+
161
+ const body = (await response.json()) as unknown[];
162
+ expect(Array.isArray(body)).toBe(true);
163
+ }, 60000);
164
+
165
+ it('creates store via POST /api/stores', async () => {
166
+ const response = await fetchWithTimeout(`http://127.0.0.1:${TEST_PORT + 2}/api/stores`, {
167
+ method: 'POST',
168
+ headers: { 'Content-Type': 'application/json' },
169
+ body: JSON.stringify({
170
+ name: 'serve-test-store',
171
+ type: 'file',
172
+ path: testFilesDir,
173
+ }),
174
+ });
175
+
176
+ expect(response.status).toBe(201);
177
+ const body = (await response.json()) as { id: string; name: string };
178
+ expect(body.name).toBe('serve-test-store');
179
+ expect(body.id).toBeDefined();
180
+ }, 60000);
181
+
182
+ it('returns 400 for invalid store creation', async () => {
183
+ const response = await fetchWithTimeout(`http://127.0.0.1:${TEST_PORT + 2}/api/stores`, {
184
+ method: 'POST',
185
+ headers: { 'Content-Type': 'application/json' },
186
+ body: JSON.stringify({
187
+ name: 'invalid-store',
188
+ type: 'file',
189
+ // Missing required 'path' for file type
190
+ }),
191
+ });
192
+
193
+ expect(response.status).toBe(400);
194
+ }, 60000);
195
+ });
196
+
197
+ describe('Search API', () => {
198
+ it('handles search request via POST /api/search', async () => {
199
+ serverProcess = await startServer(TEST_PORT + 3);
200
+
201
+ // Search with no stores indexed - should return empty results
202
+ const response = await fetchWithTimeout(`http://127.0.0.1:${TEST_PORT + 3}/api/search`, {
203
+ method: 'POST',
204
+ headers: { 'Content-Type': 'application/json' },
205
+ body: JSON.stringify({
206
+ query: 'test query',
207
+ limit: 5,
208
+ }),
209
+ });
210
+
211
+ expect(response.ok).toBe(true);
212
+ const body = (await response.json()) as { results: unknown[] };
213
+ expect(body.results).toBeDefined();
214
+ expect(Array.isArray(body.results)).toBe(true);
215
+ }, 60000);
216
+
217
+ it('returns 400 for invalid search request', async () => {
218
+ serverProcess = await startServer(TEST_PORT + 4);
219
+
220
+ const response = await fetchWithTimeout(`http://127.0.0.1:${TEST_PORT + 4}/api/search`, {
221
+ method: 'POST',
222
+ headers: { 'Content-Type': 'application/json' },
223
+ body: JSON.stringify({
224
+ // Missing required 'query'
225
+ }),
226
+ });
227
+
228
+ expect(response.status).toBe(400);
229
+ }, 60000);
230
+ });
231
+
232
+ describe('Graceful Shutdown', () => {
233
+ it('shuts down cleanly on SIGTERM', async () => {
234
+ serverProcess = await startServer(TEST_PORT + 5);
235
+
236
+ // Verify server is running
237
+ const response = await fetchWithTimeout(`http://127.0.0.1:${TEST_PORT + 5}/health`);
238
+ expect(response.ok).toBe(true);
239
+
240
+ // Send SIGTERM
241
+ serverProcess.kill('SIGTERM');
242
+
243
+ // Wait for process to exit
244
+ const exitCode = await new Promise<number | null>((resolve) => {
245
+ if (serverProcess !== null) {
246
+ serverProcess.on('exit', (code) => resolve(code));
247
+ // Allow more time for graceful shutdown
248
+ setTimeout(() => resolve(null), 10000);
249
+ } else {
250
+ resolve(null);
251
+ }
252
+ });
253
+
254
+ // Process should exit cleanly (code 0) or be killed (null)
255
+ // On some systems the process may not exit with 0 due to async cleanup
256
+ expect(exitCode === 0 || exitCode === null).toBe(true);
257
+ serverProcess = null; // Already exited
258
+ }, 60000);
259
+ });
260
+ });