onyx-kb-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -0
- package/README.md +166 -0
- package/build/__tests__/config/config.test.d.ts +1 -0
- package/build/__tests__/config/config.test.js +56 -0
- package/build/__tests__/config/config.test.js.map +1 -0
- package/build/__tests__/index.test.d.ts +1 -0
- package/build/__tests__/index.test.js +25 -0
- package/build/__tests__/index.test.js.map +1 -0
- package/build/__tests__/integration/server.test.d.ts +1 -0
- package/build/__tests__/integration/server.test.js +73 -0
- package/build/__tests__/integration/server.test.js.map +1 -0
- package/build/__tests__/services/onyxApi.test.d.ts +1 -0
- package/build/__tests__/services/onyxApi.test.js +317 -0
- package/build/__tests__/services/onyxApi.test.js.map +1 -0
- package/build/__tests__/tools/tools.test.d.ts +1 -0
- package/build/__tests__/tools/tools.test.js +177 -0
- package/build/__tests__/tools/tools.test.js.map +1 -0
- package/build/config/index.d.ts +20 -0
- package/build/config/index.js +29 -0
- package/build/config/index.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +24 -0
- package/build/index.js.map +1 -0
- package/build/server.d.ts +21 -0
- package/build/server.js +85 -0
- package/build/server.js.map +1 -0
- package/build/services/onyxApi.d.ts +82 -0
- package/build/services/onyxApi.js +367 -0
- package/build/services/onyxApi.js.map +1 -0
- package/build/tools/chatTool.d.ts +25 -0
- package/build/tools/chatTool.js +84 -0
- package/build/tools/chatTool.js.map +1 -0
- package/build/tools/index.d.ts +7 -0
- package/build/tools/index.js +8 -0
- package/build/tools/index.js.map +1 -0
- package/build/tools/searchTool.d.ts +20 -0
- package/build/tools/searchTool.js +55 -0
- package/build/tools/searchTool.js.map +1 -0
- package/build/tools/summarizeTool.d.ts +20 -0
- package/build/tools/summarizeTool.js +106 -0
- package/build/tools/summarizeTool.js.map +1 -0
- package/build/types/index.d.ts +198 -0
- package/build/types/index.js +111 -0
- package/build/types/index.js.map +1 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
MIT License
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# onyx-kb-mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that provides seamless integration with [Onyx](https://github.com/onyx-dot-app/onyx) AI knowledge bases. Enable any MCP-compatible client (Claude Desktop, Claude Code, Cline, etc.) to search, query, and summarize your organization's knowledge base.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/onyx-kb-mcp)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Semantic Search** (`search_arq`) - Search your Onyx document sets with configurable context retrieval
|
|
10
|
+
- **Q&A with RAG** (`ask_arq`) - Get comprehensive answers with sources using Onyx's LLM + RAG capabilities
|
|
11
|
+
- **Executive Summaries** (`summarize_arq`) - Generate concise bullet-point summaries on any topic
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Prerequisites
|
|
16
|
+
|
|
17
|
+
- Node.js v16 or higher
|
|
18
|
+
- An Onyx instance with API access
|
|
19
|
+
- An Onyx API token
|
|
20
|
+
|
|
21
|
+
### Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g onyx-kb-mcp
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or use directly with npx (no installation required):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx onyx-kb-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## MCP Client Configuration
|
|
34
|
+
|
|
35
|
+
### Claude Desktop
|
|
36
|
+
|
|
37
|
+
Add to your Claude Desktop config file (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"onyx": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["-y", "onyx-kb-mcp"],
|
|
45
|
+
"env": {
|
|
46
|
+
"ONYX_API_TOKEN": "your-api-token-here",
|
|
47
|
+
"ONYX_API_URL": "http://localhost:8080/api"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Claude Code
|
|
55
|
+
|
|
56
|
+
Add to your Claude Code MCP settings (`~/.claude/settings.json`):
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"onyx": {
|
|
62
|
+
"command": "npx",
|
|
63
|
+
"args": ["-y", "onyx-kb-mcp"],
|
|
64
|
+
"env": {
|
|
65
|
+
"ONYX_API_TOKEN": "your-api-token-here",
|
|
66
|
+
"ONYX_API_URL": "http://localhost:8080/api"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Smithery
|
|
74
|
+
|
|
75
|
+
This server is compatible with [Smithery](https://smithery.ai/). See `smithery.yaml` for configuration details.
|
|
76
|
+
|
|
77
|
+
## Environment Variables
|
|
78
|
+
|
|
79
|
+
| Variable | Required | Default | Description |
|
|
80
|
+
|----------|----------|---------|-------------|
|
|
81
|
+
| `ONYX_API_TOKEN` | Yes | - | Your Onyx API authentication token |
|
|
82
|
+
| `ONYX_API_URL` | No | `http://localhost:8080/api` | URL of your Onyx API endpoint |
|
|
83
|
+
| `DEFAULT_PERSONA_ID` | No | `15` | Default persona ID for chat interactions |
|
|
84
|
+
| `DEBUG` | No | `false` | Enable debug logging |
|
|
85
|
+
|
|
86
|
+
## Available Tools
|
|
87
|
+
|
|
88
|
+
### search_arq
|
|
89
|
+
|
|
90
|
+
Search your knowledge base for relevant documents with configurable context retrieval.
|
|
91
|
+
|
|
92
|
+
| Parameter | Type | Required | Default | Description |
|
|
93
|
+
|-----------|------|----------|---------|-------------|
|
|
94
|
+
| `query` | string | Yes | - | Search query |
|
|
95
|
+
| `documentSets` | string[] | No | all | Document sets to search |
|
|
96
|
+
| `maxResults` | integer | No | 10 | Maximum results (1-20) |
|
|
97
|
+
| `chunksAbove` | integer | No | 1 | Context chunks above match |
|
|
98
|
+
| `chunksBelow` | integer | No | 1 | Context chunks below match |
|
|
99
|
+
| `retrieveFullDocuments` | boolean | No | false | Return full documents |
|
|
100
|
+
|
|
101
|
+
### ask_arq
|
|
102
|
+
|
|
103
|
+
Ask questions and receive comprehensive answers with source citations.
|
|
104
|
+
|
|
105
|
+
| Parameter | Type | Required | Default | Description |
|
|
106
|
+
|-----------|------|----------|---------|-------------|
|
|
107
|
+
| `query` | string | Yes | - | Question to ask |
|
|
108
|
+
| `personaId` | integer | No | 15 | Persona ID to use |
|
|
109
|
+
| `personaName` | string | No | - | Persona name (alternative to ID) |
|
|
110
|
+
| `documentSets` | string[] | No | all | Document sets to search |
|
|
111
|
+
| `chatSessionId` | string | No | - | Session ID for conversation continuity |
|
|
112
|
+
| `enableAutoDetectFilters` | boolean | No | true | Auto-detect search filters |
|
|
113
|
+
|
|
114
|
+
### summarize_arq
|
|
115
|
+
|
|
116
|
+
Generate executive summaries with bullet points and source links.
|
|
117
|
+
|
|
118
|
+
| Parameter | Type | Required | Default | Description |
|
|
119
|
+
|-----------|------|----------|---------|-------------|
|
|
120
|
+
| `topic` | string | Yes | - | Topic to summarize |
|
|
121
|
+
| `maxPoints` | integer | No | 5 | Max bullet points (3-10) |
|
|
122
|
+
|
|
123
|
+
## Usage Examples
|
|
124
|
+
|
|
125
|
+
Once configured, you can use natural language in your MCP client:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
search arq for authentication documentation
|
|
129
|
+
|
|
130
|
+
ask arq: how does our deployment process work?
|
|
131
|
+
|
|
132
|
+
ask arq using "Engineering Assistant": explain the API architecture
|
|
133
|
+
|
|
134
|
+
summarize arq onboarding process in 5 points
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Clone the repository
|
|
141
|
+
git clone https://github.com/arqitech/onyx-kb-mcp.git
|
|
142
|
+
cd onyx-kb-mcp
|
|
143
|
+
|
|
144
|
+
# Install dependencies
|
|
145
|
+
npm install
|
|
146
|
+
|
|
147
|
+
# Build
|
|
148
|
+
npm run build
|
|
149
|
+
|
|
150
|
+
# Run in development mode
|
|
151
|
+
npm run dev
|
|
152
|
+
|
|
153
|
+
# Run tests
|
|
154
|
+
npm test
|
|
155
|
+
|
|
156
|
+
# Lint code
|
|
157
|
+
npm run lint
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT License - see [LICENSE](./LICENSE) for details.
|
|
163
|
+
|
|
164
|
+
## Author
|
|
165
|
+
|
|
166
|
+
[Arqitech](https://arqitech.com)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the config module
|
|
3
|
+
*/
|
|
4
|
+
import { loadConfig, SERVER_CONFIG, DEBUG } from '../../config/index.js';
|
|
5
|
+
// Save original environment
|
|
6
|
+
const originalEnv = process.env;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
// Reset environment variables before each test
|
|
9
|
+
process.env = { ...originalEnv };
|
|
10
|
+
delete process.env.ONYX_API_TOKEN;
|
|
11
|
+
delete process.env.ONYX_API_URL;
|
|
12
|
+
delete process.env.DEBUG;
|
|
13
|
+
});
|
|
14
|
+
afterAll(() => {
|
|
15
|
+
// Restore original environment
|
|
16
|
+
process.env = originalEnv;
|
|
17
|
+
});
|
|
18
|
+
describe('Config Module', () => {
|
|
19
|
+
describe('loadConfig', () => {
|
|
20
|
+
it('should load default config when no environment variables are set', () => {
|
|
21
|
+
const config = loadConfig();
|
|
22
|
+
expect(config).toEqual({
|
|
23
|
+
apiUrl: 'http://localhost:8080/api',
|
|
24
|
+
apiToken: '',
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
it('should use environment variables when set', () => {
|
|
28
|
+
process.env.ONYX_API_TOKEN = 'test-token';
|
|
29
|
+
process.env.ONYX_API_URL = 'http://test-url.com/api';
|
|
30
|
+
const config = loadConfig();
|
|
31
|
+
expect(config).toEqual({
|
|
32
|
+
apiUrl: 'http://test-url.com/api',
|
|
33
|
+
apiToken: 'test-token',
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('SERVER_CONFIG', () => {
|
|
38
|
+
it('should have the correct server configuration', () => {
|
|
39
|
+
expect(SERVER_CONFIG).toHaveProperty('name', 'onyx-mcp-server');
|
|
40
|
+
expect(SERVER_CONFIG).toHaveProperty('version', '1.0.0');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('DEBUG', () => {
|
|
44
|
+
it('should be false by default', () => {
|
|
45
|
+
expect(DEBUG).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
it('should be true when DEBUG environment variable is set to "true"', () => {
|
|
48
|
+
// Set the DEBUG environment variable
|
|
49
|
+
process.env.DEBUG = 'true';
|
|
50
|
+
// Re-import the module to get the updated value
|
|
51
|
+
// Note: This is a simplified test since we can't easily reset modules in Jest ESM
|
|
52
|
+
expect(process.env.DEBUG).toBe('true');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../../src/__tests__/config/config.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEzE,4BAA4B;AAC5B,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;AAEhC,UAAU,CAAC,GAAG,EAAE;IACd,+CAA+C;IAC/C,OAAO,CAAC,GAAG,GAAG,EAAE,GAAG,WAAW,EAAE,CAAC;IACjC,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAChC,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AAC3B,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,GAAG,EAAE;IACZ,+BAA+B;IAC/B,OAAO,CAAC,GAAG,GAAG,WAAW,CAAC;AAC5B,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;YAC1E,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;YAC5B,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;gBACrB,MAAM,EAAE,2BAA2B;gBACnC,QAAQ,EAAE,EAAE;aACb,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,YAAY,CAAC;YAC1C,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,yBAAyB,CAAC;YAErD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;YAC5B,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;gBACrB,MAAM,EAAE,yBAAyB;gBACjC,QAAQ,EAAE,YAAY;aACvB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,CAAC,aAAa,CAAC,CAAC,cAAc,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;YAChE,MAAM,CAAC,aAAa,CAAC,CAAC,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;QACrB,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;YACzE,qCAAqC;YACrC,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC;YAE3B,gDAAgD;YAChD,kFAAkF;YAClF,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the main entry point
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
// ES modules equivalent of __dirname
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
describe('Main Entry Point', () => {
|
|
11
|
+
it('should have a main function that starts the server', async () => {
|
|
12
|
+
// Simple test to verify the file exists and has the expected content
|
|
13
|
+
const indexPath = path.resolve(__dirname, '../index.ts');
|
|
14
|
+
const fileExists = fs.existsSync(indexPath);
|
|
15
|
+
expect(fileExists).toBe(true);
|
|
16
|
+
// Read the file content
|
|
17
|
+
const content = fs.readFileSync(indexPath, 'utf8');
|
|
18
|
+
// Verify it contains the expected functions and imports
|
|
19
|
+
expect(content).toContain('async function main()');
|
|
20
|
+
expect(content).toContain('import { StdioServerTransport }');
|
|
21
|
+
expect(content).toContain('import { OnyxMcpServer }');
|
|
22
|
+
expect(content).toContain('main().catch(console.error)');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,qCAAqC;AACrC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3C,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,qEAAqE;QACrE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QACzD,MAAM,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC5C,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE9B,wBAAwB;QACxB,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAEnD,wDAAwD;QACxD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;QACnD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAC;QAC7D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;QACtD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the Onyx MCP Server
|
|
3
|
+
*/
|
|
4
|
+
import { OnyxMcpServer } from '../../server.js';
|
|
5
|
+
import nock from 'nock';
|
|
6
|
+
describe('OnyxMcpServer Integration', () => {
|
|
7
|
+
let server;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// Set up environment variables
|
|
10
|
+
process.env.ONYX_API_TOKEN = 'test-token';
|
|
11
|
+
process.env.ONYX_API_URL = 'http://test-api.com/api';
|
|
12
|
+
// Mock all HTTP requests
|
|
13
|
+
nock.disableNetConnect();
|
|
14
|
+
// Create server and transport
|
|
15
|
+
server = new OnyxMcpServer();
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
nock.cleanAll();
|
|
19
|
+
nock.enableNetConnect();
|
|
20
|
+
delete process.env.ONYX_API_TOKEN;
|
|
21
|
+
delete process.env.ONYX_API_URL;
|
|
22
|
+
});
|
|
23
|
+
it('should initialize and run the server', async () => {
|
|
24
|
+
// Run the server with the mock transport
|
|
25
|
+
// We're just testing that the server can be created without errors
|
|
26
|
+
expect(server).toBeDefined();
|
|
27
|
+
// Verify that the server has the expected properties
|
|
28
|
+
expect(server).toHaveProperty('server');
|
|
29
|
+
});
|
|
30
|
+
it('should handle list tools request', async () => {
|
|
31
|
+
// Run the server with the mock transport
|
|
32
|
+
// We're just testing that the server can be created without errors
|
|
33
|
+
expect(server).toBeDefined();
|
|
34
|
+
// Verify that the server has the expected properties
|
|
35
|
+
expect(server).toHaveProperty('server');
|
|
36
|
+
});
|
|
37
|
+
it('should handle call tool request for search_onyx', async () => {
|
|
38
|
+
// Set up API mocks
|
|
39
|
+
nock('http://test-api.com')
|
|
40
|
+
.post('/api/admin/search')
|
|
41
|
+
.reply(200, { documents: [] });
|
|
42
|
+
// Run the server
|
|
43
|
+
// We're just testing that the server can be created without errors
|
|
44
|
+
expect(server).toBeDefined();
|
|
45
|
+
// Verify that the server has the expected properties
|
|
46
|
+
expect(server).toHaveProperty('server');
|
|
47
|
+
});
|
|
48
|
+
it('should handle call tool request for chat_with_onyx', async () => {
|
|
49
|
+
// Set up API mocks
|
|
50
|
+
nock('http://test-api.com')
|
|
51
|
+
.post('/api/chat/create-chat-session')
|
|
52
|
+
.reply(200, { chat_session_id: 'test-session-id' });
|
|
53
|
+
nock('http://test-api.com')
|
|
54
|
+
.post('/api/chat/send-message')
|
|
55
|
+
.reply(200, JSON.stringify({
|
|
56
|
+
answer: 'Test answer',
|
|
57
|
+
documents: []
|
|
58
|
+
}));
|
|
59
|
+
// Run the server
|
|
60
|
+
// We're just testing that the server can be created without errors
|
|
61
|
+
expect(server).toBeDefined();
|
|
62
|
+
// Verify that the server has the expected properties
|
|
63
|
+
expect(server).toHaveProperty('server');
|
|
64
|
+
});
|
|
65
|
+
it('should handle call tool request for unknown tool', async () => {
|
|
66
|
+
// Run the server
|
|
67
|
+
// We're just testing that the server can be created without errors
|
|
68
|
+
expect(server).toBeDefined();
|
|
69
|
+
// Verify that the server has the expected properties
|
|
70
|
+
expect(server).toHaveProperty('server');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=server.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/server.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the OnyxApiService
|
|
3
|
+
*/
|
|
4
|
+
import nock from 'nock';
|
|
5
|
+
import { OnyxApiService } from '../../services/onyxApi.js';
|
|
6
|
+
// Mock config for testing
|
|
7
|
+
const mockConfig = {
|
|
8
|
+
apiUrl: 'http://test-api.com/api',
|
|
9
|
+
apiToken: 'test-token',
|
|
10
|
+
};
|
|
11
|
+
describe('OnyxApiService', () => {
|
|
12
|
+
let onyxApiService;
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Create service instance
|
|
15
|
+
onyxApiService = new OnyxApiService(mockConfig);
|
|
16
|
+
// Disable real HTTP requests
|
|
17
|
+
nock.disableNetConnect();
|
|
18
|
+
});
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
// Clean up nock after each test
|
|
21
|
+
nock.cleanAll();
|
|
22
|
+
nock.enableNetConnect();
|
|
23
|
+
});
|
|
24
|
+
describe('searchOnyx', () => {
|
|
25
|
+
it('should make a POST request to the search endpoint with correct parameters', async () => {
|
|
26
|
+
// Mock the search API response
|
|
27
|
+
const mockResponse = {
|
|
28
|
+
documents: [
|
|
29
|
+
{
|
|
30
|
+
document_id: 'doc1',
|
|
31
|
+
chunk_ind: 1,
|
|
32
|
+
semantic_identifier: 'Test Document',
|
|
33
|
+
blurb: 'Test blurb',
|
|
34
|
+
source_type: 'test',
|
|
35
|
+
score: 0.95,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
// Set up the mock
|
|
40
|
+
nock('http://test-api.com')
|
|
41
|
+
.post('/api/admin/search')
|
|
42
|
+
.reply(200, mockResponse);
|
|
43
|
+
// Call the method
|
|
44
|
+
const result = await onyxApiService.searchOnyx('test query', [], 1, 2);
|
|
45
|
+
// Verify the result
|
|
46
|
+
expect(result).toEqual(mockResponse.documents);
|
|
47
|
+
});
|
|
48
|
+
it('should include document sets in the request when provided', async () => {
|
|
49
|
+
// Mock the search API response
|
|
50
|
+
const mockResponse = {
|
|
51
|
+
documents: [
|
|
52
|
+
{
|
|
53
|
+
document_id: 'doc1',
|
|
54
|
+
chunk_ind: 1,
|
|
55
|
+
semantic_identifier: 'Test Document',
|
|
56
|
+
blurb: 'Test blurb',
|
|
57
|
+
source_type: 'test',
|
|
58
|
+
score: 0.95,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
// Set up the mock
|
|
63
|
+
nock('http://test-api.com')
|
|
64
|
+
.post('/api/admin/search')
|
|
65
|
+
.reply(200, mockResponse);
|
|
66
|
+
// Call the method with document sets
|
|
67
|
+
const result = await onyxApiService.searchOnyx('test query', ['Test Set']);
|
|
68
|
+
// Verify the result
|
|
69
|
+
expect(result).toEqual(mockResponse.documents);
|
|
70
|
+
});
|
|
71
|
+
it('should throw an error when the API request fails', async () => {
|
|
72
|
+
// Set up the mock to return an error
|
|
73
|
+
nock('http://test-api.com')
|
|
74
|
+
.post('/api/admin/search')
|
|
75
|
+
.reply(500, { error: 'Internal server error' });
|
|
76
|
+
// Call the method and expect it to throw
|
|
77
|
+
await expect(onyxApiService.searchOnyx('test query')).rejects.toThrow('Failed to search Onyx');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('fetchDocumentChunk', () => {
|
|
81
|
+
it('should make a GET request to the chunk-info endpoint with correct parameters', async () => {
|
|
82
|
+
// Mock the chunk-info API response
|
|
83
|
+
const mockResponse = {
|
|
84
|
+
content: 'Test chunk content',
|
|
85
|
+
num_tokens: 10,
|
|
86
|
+
};
|
|
87
|
+
// Set up the mock
|
|
88
|
+
nock('http://test-api.com')
|
|
89
|
+
.get('/api/document/chunk-info')
|
|
90
|
+
.query({
|
|
91
|
+
document_id: 'doc1',
|
|
92
|
+
chunk_id: 1,
|
|
93
|
+
})
|
|
94
|
+
.reply(200, mockResponse);
|
|
95
|
+
// Call the method
|
|
96
|
+
const result = await onyxApiService.fetchDocumentChunk('doc1', 1);
|
|
97
|
+
// Verify the result
|
|
98
|
+
expect(result).toEqual(mockResponse.content);
|
|
99
|
+
});
|
|
100
|
+
it('should return empty string when the API request fails', async () => {
|
|
101
|
+
// Set up the mock to return an error
|
|
102
|
+
nock('http://test-api.com')
|
|
103
|
+
.get('/api/document/chunk-info')
|
|
104
|
+
.query({
|
|
105
|
+
document_id: 'doc1',
|
|
106
|
+
chunk_id: 1,
|
|
107
|
+
})
|
|
108
|
+
.reply(500, { error: 'Internal server error' });
|
|
109
|
+
// Call the method
|
|
110
|
+
const result = await onyxApiService.fetchDocumentChunk('doc1', 1);
|
|
111
|
+
// Verify the result
|
|
112
|
+
expect(result).toEqual('');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('fetchDocumentContent', () => {
|
|
116
|
+
it('should fetch document size info and then all chunks', async () => {
|
|
117
|
+
// Mock the document-size-info API response
|
|
118
|
+
const mockSizeResponse = {
|
|
119
|
+
num_chunks: 2,
|
|
120
|
+
total_tokens: 20
|
|
121
|
+
};
|
|
122
|
+
// Mock the chunk-info API responses
|
|
123
|
+
const mockChunk1Response = {
|
|
124
|
+
content: 'Chunk 1 content',
|
|
125
|
+
num_tokens: 10
|
|
126
|
+
};
|
|
127
|
+
const mockChunk2Response = {
|
|
128
|
+
content: 'Chunk 2 content',
|
|
129
|
+
num_tokens: 10
|
|
130
|
+
};
|
|
131
|
+
// Set up the mocks
|
|
132
|
+
nock('http://test-api.com')
|
|
133
|
+
.get('/api/document/document-size-info')
|
|
134
|
+
.query({
|
|
135
|
+
document_id: 'doc1'
|
|
136
|
+
})
|
|
137
|
+
.reply(200, mockSizeResponse);
|
|
138
|
+
nock('http://test-api.com')
|
|
139
|
+
.get('/api/document/chunk-info')
|
|
140
|
+
.query({
|
|
141
|
+
document_id: 'doc1',
|
|
142
|
+
chunk_id: 0
|
|
143
|
+
})
|
|
144
|
+
.reply(200, mockChunk1Response);
|
|
145
|
+
nock('http://test-api.com')
|
|
146
|
+
.get('/api/document/chunk-info')
|
|
147
|
+
.query({
|
|
148
|
+
document_id: 'doc1',
|
|
149
|
+
chunk_id: 1
|
|
150
|
+
})
|
|
151
|
+
.reply(200, mockChunk2Response);
|
|
152
|
+
// Call the method
|
|
153
|
+
const result = await onyxApiService.fetchDocumentContent('doc1');
|
|
154
|
+
// Verify the result
|
|
155
|
+
expect(result).toContain(mockChunk1Response.content);
|
|
156
|
+
expect(result).toContain(mockChunk2Response.content);
|
|
157
|
+
});
|
|
158
|
+
it('should return empty string when the API request fails', async () => {
|
|
159
|
+
// Set up the mock to return an error
|
|
160
|
+
nock('http://test-api.com')
|
|
161
|
+
.get('/api/document/document-size-info')
|
|
162
|
+
.query({
|
|
163
|
+
document_id: 'doc1'
|
|
164
|
+
})
|
|
165
|
+
.reply(500, { error: 'Internal server error' });
|
|
166
|
+
// Call the method
|
|
167
|
+
const result = await onyxApiService.fetchDocumentContent('doc1');
|
|
168
|
+
// Verify the result
|
|
169
|
+
expect(result).toEqual('');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
describe('createChatSession', () => {
|
|
173
|
+
it('should make a POST request to create a chat session', async () => {
|
|
174
|
+
// Mock the create-chat-session API response
|
|
175
|
+
const mockResponse = {
|
|
176
|
+
chat_session_id: 'test-session-id'
|
|
177
|
+
};
|
|
178
|
+
// Set up the mock
|
|
179
|
+
nock('http://test-api.com')
|
|
180
|
+
.post('/api/chat/create-chat-session')
|
|
181
|
+
.reply(200, mockResponse);
|
|
182
|
+
// Call the method
|
|
183
|
+
const result = await onyxApiService.createChatSession(15);
|
|
184
|
+
// Verify the result
|
|
185
|
+
expect(result).toEqual(mockResponse.chat_session_id);
|
|
186
|
+
});
|
|
187
|
+
it('should throw an error when the API request fails', async () => {
|
|
188
|
+
// Set up the mock to return an error
|
|
189
|
+
nock('http://test-api.com')
|
|
190
|
+
.post('/api/chat/create-chat-session')
|
|
191
|
+
.reply(500, { error: 'Internal server error' });
|
|
192
|
+
// Call the method and expect it to throw
|
|
193
|
+
await expect(onyxApiService.createChatSession(15)).rejects.toThrow('Failed to create chat session');
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
describe('sendChatMessage', () => {
|
|
197
|
+
it('should make a POST request to send a chat message and parse JSON response', async () => {
|
|
198
|
+
// Mock the send-message API response (single JSON)
|
|
199
|
+
const mockResponse = JSON.stringify({
|
|
200
|
+
answer: 'Test answer',
|
|
201
|
+
documents: [
|
|
202
|
+
{ document_id: 'doc1', semantic_identifier: 'Test Document 1' },
|
|
203
|
+
{ document_id: 'doc2', semantic_identifier: 'Test Document 2' }
|
|
204
|
+
]
|
|
205
|
+
});
|
|
206
|
+
// Set up the mock
|
|
207
|
+
nock('http://test-api.com')
|
|
208
|
+
.post('/api/chat/send-message')
|
|
209
|
+
.reply(200, mockResponse);
|
|
210
|
+
// Call the method
|
|
211
|
+
const result = await onyxApiService.sendChatMessage('test-session-id', 'test query');
|
|
212
|
+
// Verify the result
|
|
213
|
+
expect(result.answer).toEqual('Test answer');
|
|
214
|
+
expect(result.documents).toHaveLength(2);
|
|
215
|
+
expect(result.documents[0].document_id).toEqual('doc1');
|
|
216
|
+
});
|
|
217
|
+
it('should handle JSON lines format response', async () => {
|
|
218
|
+
// Mock the send-message API response (JSON lines)
|
|
219
|
+
const mockResponse = '{"answer_piece":"Part 1 of answer"}\n' +
|
|
220
|
+
'{"answer_piece":"Part 2 of answer"}\n' +
|
|
221
|
+
'{"top_documents":[{"document_id":"doc1","semantic_identifier":"Test Document 1"}]}';
|
|
222
|
+
// Set up the mock
|
|
223
|
+
nock('http://test-api.com')
|
|
224
|
+
.post('/api/chat/send-message')
|
|
225
|
+
.reply(200, mockResponse);
|
|
226
|
+
// Call the method
|
|
227
|
+
const result = await onyxApiService.sendChatMessage('test-session-id', 'test query');
|
|
228
|
+
// Verify the result
|
|
229
|
+
expect(result.answer).toEqual('Part 1 of answerPart 2 of answer');
|
|
230
|
+
expect(result.documents).toHaveLength(1);
|
|
231
|
+
expect(result.documents[0].document_id).toEqual('doc1');
|
|
232
|
+
});
|
|
233
|
+
it('should throw an error when the API request fails', async () => {
|
|
234
|
+
// Set up the mock to return an error
|
|
235
|
+
nock('http://test-api.com')
|
|
236
|
+
.post('/api/chat/send-message')
|
|
237
|
+
.reply(500, { error: 'Internal server error' });
|
|
238
|
+
// Call the method and expect it to throw
|
|
239
|
+
await expect(onyxApiService.sendChatMessage('test-session-id', 'test query')).rejects.toThrow('Error chatting with Onyx');
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
describe('buildContext', () => {
|
|
243
|
+
it('should build a formatted context string from search results and contents', () => {
|
|
244
|
+
// Test data
|
|
245
|
+
const results = [
|
|
246
|
+
{
|
|
247
|
+
document_id: 'doc1',
|
|
248
|
+
chunk_ind: 1,
|
|
249
|
+
semantic_identifier: 'Test Document 1',
|
|
250
|
+
score: 0.95,
|
|
251
|
+
link: 'http://example.com/doc1',
|
|
252
|
+
blurb: 'Test blurb 1',
|
|
253
|
+
source_type: 'test'
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
document_id: 'doc2',
|
|
257
|
+
chunk_ind: 2,
|
|
258
|
+
semantic_identifier: 'Test Document 2',
|
|
259
|
+
score: 0.85,
|
|
260
|
+
link: undefined,
|
|
261
|
+
blurb: 'Test blurb 2',
|
|
262
|
+
source_type: 'test'
|
|
263
|
+
}
|
|
264
|
+
];
|
|
265
|
+
const contents = [
|
|
266
|
+
'Content for document 1',
|
|
267
|
+
'Content for document 2'
|
|
268
|
+
];
|
|
269
|
+
// Call the method
|
|
270
|
+
const result = onyxApiService.buildContext(results, contents);
|
|
271
|
+
// Verify the result
|
|
272
|
+
expect(result).toContain('# Test Document 1');
|
|
273
|
+
expect(result).toContain('Source: http://example.com/doc1');
|
|
274
|
+
expect(result).toContain('Relevance Score: 0.95');
|
|
275
|
+
expect(result).toContain('Content for document 1');
|
|
276
|
+
expect(result).toContain('# Test Document 2');
|
|
277
|
+
expect(result).toContain('Source: doc2');
|
|
278
|
+
expect(result).toContain('Relevance Score: 0.85');
|
|
279
|
+
expect(result).toContain('Content for document 2');
|
|
280
|
+
});
|
|
281
|
+
it('should skip results with no content', () => {
|
|
282
|
+
// Test data
|
|
283
|
+
const results = [
|
|
284
|
+
{
|
|
285
|
+
document_id: 'doc1',
|
|
286
|
+
chunk_ind: 1,
|
|
287
|
+
semantic_identifier: 'Test Document 1',
|
|
288
|
+
score: 0.95,
|
|
289
|
+
link: 'http://example.com/doc1',
|
|
290
|
+
blurb: 'Test blurb 1',
|
|
291
|
+
source_type: 'test'
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
document_id: 'doc2',
|
|
295
|
+
chunk_ind: 2,
|
|
296
|
+
semantic_identifier: 'Test Document 2',
|
|
297
|
+
score: 0.85,
|
|
298
|
+
link: 'http://example.com/doc2',
|
|
299
|
+
blurb: 'Test blurb 2',
|
|
300
|
+
source_type: 'test'
|
|
301
|
+
}
|
|
302
|
+
];
|
|
303
|
+
const contents = [
|
|
304
|
+
'Content for document 1',
|
|
305
|
+
'' // Empty content for second document
|
|
306
|
+
];
|
|
307
|
+
// Call the method
|
|
308
|
+
const result = onyxApiService.buildContext(results, contents);
|
|
309
|
+
// Verify the result
|
|
310
|
+
expect(result).toContain('# Test Document 1');
|
|
311
|
+
expect(result).toContain('Content for document 1');
|
|
312
|
+
// Should not contain document 2 info
|
|
313
|
+
expect(result).not.toContain('# Test Document 2');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
//# sourceMappingURL=onyxApi.test.js.map
|