@iflow-mcp/jeanibarz-knowledge-base-mcp-server 0.1.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/.gitattributes +1 -0
- package/CHANGELOG.md +23 -0
- package/Dockerfile +22 -0
- package/README.md +224 -0
- package/UNLICENSE +24 -0
- package/build/FaissIndexManager.js +341 -0
- package/build/FaissIndexManager.js.map +1 -0
- package/build/FaissIndexManager.test.js +121 -0
- package/build/FaissIndexManager.test.js.map +1 -0
- package/build/KnowledgeBaseServer.js +184 -0
- package/build/KnowledgeBaseServer.js.map +1 -0
- package/build/config.js +17 -0
- package/build/config.js.map +1 -0
- package/build/index.js +11 -0
- package/build/index.js.map +1 -0
- package/build/logger.js +72 -0
- package/build/logger.js.map +1 -0
- package/build/logger.test.js +63 -0
- package/build/logger.test.js.map +1 -0
- package/build/utils.js +42 -0
- package/build/utils.js.map +1 -0
- package/build/utils.test.js +86 -0
- package/build/utils.test.js.map +1 -0
- package/jeanibarz-knowledge-base-mcp-server-20251229_process.log +20 -0
- package/jest.config.js +18 -0
- package/package.json +38 -0
- package/smithery.yaml +78 -0
- package/src/FaissIndexManager.test.ts +139 -0
- package/src/FaissIndexManager.ts +366 -0
- package/src/KnowledgeBaseServer.ts +201 -0
- package/src/config.ts +22 -0
- package/src/index.ts +11 -0
- package/src/knowledge-base-server-flow.md +44 -0
- package/src/logger.test.ts +68 -0
- package/src/logger.ts +74 -0
- package/src/utils.test.ts +98 -0
- package/src/utils.ts +46 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as fsp from 'fs/promises';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
const saveMock = jest.fn();
|
|
5
|
+
const addDocumentsMock = jest.fn();
|
|
6
|
+
const fromTextsMock = jest.fn();
|
|
7
|
+
const loadMock = jest.fn();
|
|
8
|
+
const similaritySearchMock = jest.fn();
|
|
9
|
+
class MockFaissStore {
|
|
10
|
+
async addDocuments(...args) {
|
|
11
|
+
return addDocumentsMock(...args);
|
|
12
|
+
}
|
|
13
|
+
async save(...args) {
|
|
14
|
+
return saveMock(...args);
|
|
15
|
+
}
|
|
16
|
+
async similaritySearchWithScore(...args) {
|
|
17
|
+
return similaritySearchMock(...args);
|
|
18
|
+
}
|
|
19
|
+
static async fromTexts(...args) {
|
|
20
|
+
fromTextsMock(...args);
|
|
21
|
+
return new MockFaissStore();
|
|
22
|
+
}
|
|
23
|
+
static async load(...args) {
|
|
24
|
+
loadMock(...args);
|
|
25
|
+
return new MockFaissStore();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
jest.mock('@langchain/community/embeddings/hf', () => ({
|
|
29
|
+
__esModule: true,
|
|
30
|
+
HuggingFaceInferenceEmbeddings: class MockEmbedding {
|
|
31
|
+
_config;
|
|
32
|
+
constructor(_config) {
|
|
33
|
+
this._config = _config;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
jest.mock('@langchain/community/vectorstores/faiss', () => ({
|
|
38
|
+
__esModule: true,
|
|
39
|
+
FaissStore: MockFaissStore,
|
|
40
|
+
}));
|
|
41
|
+
function createPermissionError(message) {
|
|
42
|
+
const error = new Error(message);
|
|
43
|
+
error.code = 'EACCES';
|
|
44
|
+
return error;
|
|
45
|
+
}
|
|
46
|
+
describe('FaissIndexManager permission handling', () => {
|
|
47
|
+
const originalEnv = {
|
|
48
|
+
KNOWLEDGE_BASES_ROOT_DIR: process.env.KNOWLEDGE_BASES_ROOT_DIR,
|
|
49
|
+
FAISS_INDEX_PATH: process.env.FAISS_INDEX_PATH,
|
|
50
|
+
EMBEDDING_PROVIDER: process.env.EMBEDDING_PROVIDER,
|
|
51
|
+
HUGGINGFACE_API_KEY: process.env.HUGGINGFACE_API_KEY,
|
|
52
|
+
LOG_FILE: process.env.LOG_FILE,
|
|
53
|
+
};
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
saveMock.mockReset();
|
|
56
|
+
addDocumentsMock.mockReset();
|
|
57
|
+
fromTextsMock.mockReset();
|
|
58
|
+
loadMock.mockReset();
|
|
59
|
+
similaritySearchMock.mockReset();
|
|
60
|
+
});
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
const keys = Object.keys(originalEnv);
|
|
63
|
+
for (const key of keys) {
|
|
64
|
+
const value = originalEnv[key];
|
|
65
|
+
if (value === undefined) {
|
|
66
|
+
delete process.env[key];
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
process.env[key] = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
jest.restoreAllMocks();
|
|
73
|
+
});
|
|
74
|
+
it('throws explicit error when FAISS directory cannot be created', async () => {
|
|
75
|
+
const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'kb-faiss-init-'));
|
|
76
|
+
const lockedDir = path.join(tempDir, 'locked');
|
|
77
|
+
await fsp.mkdir(lockedDir, { recursive: true });
|
|
78
|
+
await fsp.chmod(lockedDir, 0o500);
|
|
79
|
+
process.env.KNOWLEDGE_BASES_ROOT_DIR = tempDir;
|
|
80
|
+
process.env.FAISS_INDEX_PATH = path.join(lockedDir, '.faiss');
|
|
81
|
+
process.env.EMBEDDING_PROVIDER = 'huggingface';
|
|
82
|
+
process.env.HUGGINGFACE_API_KEY = 'test-key';
|
|
83
|
+
try {
|
|
84
|
+
jest.resetModules();
|
|
85
|
+
const loggerModule = await import('./logger.js');
|
|
86
|
+
const loggerErrorSpy = jest.spyOn(loggerModule.logger, 'error');
|
|
87
|
+
const { FaissIndexManager } = await import('./FaissIndexManager.js');
|
|
88
|
+
const manager = new FaissIndexManager();
|
|
89
|
+
await expect(manager.initialize()).rejects.toThrow(/Permission denied/);
|
|
90
|
+
expect(loggerErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Permission denied'));
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
await fsp.chmod(lockedDir, 0o700);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
it('logs permission errors to file when saving FAISS index fails', async () => {
|
|
97
|
+
const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'kb-faiss-update-'));
|
|
98
|
+
const kbDir = path.join(tempDir, 'kb');
|
|
99
|
+
const defaultKb = path.join(kbDir, 'default');
|
|
100
|
+
await fsp.mkdir(defaultKb, { recursive: true });
|
|
101
|
+
const docPath = path.join(defaultKb, 'doc.md');
|
|
102
|
+
await fsp.writeFile(docPath, '# Title\n\nSome content for embeddings.');
|
|
103
|
+
const logFile = path.join(tempDir, 'logs', 'kb.log');
|
|
104
|
+
process.env.KNOWLEDGE_BASES_ROOT_DIR = kbDir;
|
|
105
|
+
process.env.FAISS_INDEX_PATH = path.join(tempDir, '.faiss');
|
|
106
|
+
process.env.LOG_FILE = logFile;
|
|
107
|
+
process.env.EMBEDDING_PROVIDER = 'huggingface';
|
|
108
|
+
process.env.HUGGINGFACE_API_KEY = 'test-key';
|
|
109
|
+
saveMock.mockRejectedValue(createPermissionError('cannot write index'));
|
|
110
|
+
jest.resetModules();
|
|
111
|
+
const { FaissIndexManager } = await import('./FaissIndexManager.js');
|
|
112
|
+
const manager = new FaissIndexManager();
|
|
113
|
+
await manager.initialize();
|
|
114
|
+
await expect(manager.updateIndex()).rejects.toThrow(/Permission denied/);
|
|
115
|
+
expect(saveMock).toHaveBeenCalledWith(path.join(process.env.FAISS_INDEX_PATH, 'faiss.index'));
|
|
116
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
117
|
+
const logContents = await fsp.readFile(logFile, 'utf-8');
|
|
118
|
+
expect(logContents).toContain('Permission denied while attempting to save FAISS index at');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=FaissIndexManager.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaissIndexManager.test.js","sourceRoot":"","sources":["../src/FaissIndexManager.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// KnowledgeBaseServer.ts
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import * as fsp from 'fs/promises';
|
|
6
|
+
import { FaissIndexManager } from './FaissIndexManager.js';
|
|
7
|
+
import { KNOWLEDGE_BASES_ROOT_DIR } from './config.js';
|
|
8
|
+
import { logger } from './logger.js';
|
|
9
|
+
export class KnowledgeBaseServer {
|
|
10
|
+
server;
|
|
11
|
+
faissManager;
|
|
12
|
+
constructor() {
|
|
13
|
+
this.faissManager = new FaissIndexManager();
|
|
14
|
+
logger.info('Initializing KnowledgeBaseServer');
|
|
15
|
+
this.server = new Server({
|
|
16
|
+
name: 'knowledge-base-server',
|
|
17
|
+
version: '0.1.0',
|
|
18
|
+
}, {
|
|
19
|
+
capabilities: {
|
|
20
|
+
resources: {},
|
|
21
|
+
tools: {},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
this.setupToolHandlers();
|
|
25
|
+
this.server.onerror = (error) => logger.error('[MCP Error]', error);
|
|
26
|
+
process.on('SIGINT', async () => {
|
|
27
|
+
await this.server.close();
|
|
28
|
+
process.exit(0);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
setupToolHandlers() {
|
|
32
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
33
|
+
tools: [
|
|
34
|
+
{
|
|
35
|
+
name: 'list_knowledge_bases',
|
|
36
|
+
description: 'Lists the available knowledge bases.',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {},
|
|
40
|
+
required: [],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'retrieve_knowledge',
|
|
45
|
+
description: 'Retrieves similar chunks from the knowledge base based on a query. Optionally, if a knowledge base is specified, only that one is searched; otherwise, all available knowledge bases are considered. By default, at most 10 documents are returned with a score below a threshold of 2. A different threshold can optionally be provided.',
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
query: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: 'The query text to use for semantic search.',
|
|
52
|
+
},
|
|
53
|
+
knowledge_base_name: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: "Optional. Name of the knowledge base to query (e.g., 'company', 'it_support', 'onboarding'). If omitted, the search is performed across all available knowledge bases.",
|
|
56
|
+
},
|
|
57
|
+
threshold: {
|
|
58
|
+
type: 'number',
|
|
59
|
+
description: 'Optional. The maximum similarity score to return.',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ['query'],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
}));
|
|
67
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
68
|
+
if (request.params.name === 'list_knowledge_bases') {
|
|
69
|
+
return this.handleListKnowledgeBases();
|
|
70
|
+
}
|
|
71
|
+
else if (request.params.name === 'retrieve_knowledge') {
|
|
72
|
+
return this.handleRetrieveKnowledge(request.params.arguments);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async handleListKnowledgeBases() {
|
|
80
|
+
try {
|
|
81
|
+
const entries = await fsp.readdir(KNOWLEDGE_BASES_ROOT_DIR);
|
|
82
|
+
const knowledgeBases = entries.filter((entry) => !entry.startsWith('.'));
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: 'text',
|
|
87
|
+
text: JSON.stringify(knowledgeBases, null, 2),
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
logger.error('Error listing knowledge bases:', error);
|
|
94
|
+
if (error?.stack) {
|
|
95
|
+
logger.error(error.stack);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
content: [
|
|
99
|
+
{
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: `Error listing knowledge bases: ${error.message}`,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
isError: true,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async handleRetrieveKnowledge(args) {
|
|
109
|
+
if (!args || typeof args.query !== 'string') {
|
|
110
|
+
throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments for retrieve_knowledge: missing query');
|
|
111
|
+
}
|
|
112
|
+
const query = args.query;
|
|
113
|
+
const knowledgeBaseName = typeof args.knowledge_base_name === 'string' ? args.knowledge_base_name : undefined;
|
|
114
|
+
const threshold = typeof args.threshold === 'number' ? args.threshold : undefined;
|
|
115
|
+
try {
|
|
116
|
+
const startTime = Date.now();
|
|
117
|
+
logger.debug(`[${startTime}] handleRetrieveKnowledge started`);
|
|
118
|
+
// Update FAISS index: if a specific knowledge base is provided, update only that one; otherwise update all.
|
|
119
|
+
await this.faissManager.updateIndex(knowledgeBaseName);
|
|
120
|
+
logger.debug(`[${Date.now()}] FAISS index update completed`);
|
|
121
|
+
// Perform similarity search using the provided query.
|
|
122
|
+
const similaritySearchResults = await this.faissManager.similaritySearch(query, 10, threshold);
|
|
123
|
+
logger.debug(`[${Date.now()}] Similarity search completed`);
|
|
124
|
+
// Build a nicely formatted markdown response including the similarity score.
|
|
125
|
+
let formattedResults = '';
|
|
126
|
+
if (similaritySearchResults && similaritySearchResults.length > 0) {
|
|
127
|
+
formattedResults = similaritySearchResults
|
|
128
|
+
.map((doc, idx) => {
|
|
129
|
+
const resultHeader = `**Result ${idx + 1}:**`;
|
|
130
|
+
const content = doc.pageContent.trim();
|
|
131
|
+
const metadata = JSON.stringify(doc.metadata, null, 2);
|
|
132
|
+
const scoreText = doc.score !== undefined ? `**Score:** ${doc.score.toFixed(2)}\n\n` : '';
|
|
133
|
+
return `${resultHeader}\n\n${scoreText}${content}\n\n**Source:**\n\`\`\`json\n${metadata}\n\`\`\``;
|
|
134
|
+
})
|
|
135
|
+
.join('\n\n---\n\n');
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
formattedResults = '_No similar results found._';
|
|
139
|
+
}
|
|
140
|
+
const disclaimer = '\n\n> **Disclaimer:** The provided results might not all be relevant. Please cross-check the relevance of the information.';
|
|
141
|
+
const responseText = `## Semantic Search Results\n\n${formattedResults}${disclaimer}`;
|
|
142
|
+
const endTime = Date.now();
|
|
143
|
+
logger.debug(`[${endTime}] handleRetrieveKnowledge completed in ${endTime - startTime}ms`);
|
|
144
|
+
return {
|
|
145
|
+
content: [
|
|
146
|
+
{
|
|
147
|
+
type: 'text',
|
|
148
|
+
text: responseText,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
logger.error('Error retrieving knowledge:', error);
|
|
155
|
+
if (error?.stack) {
|
|
156
|
+
logger.error(error.stack);
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: `Error retrieving knowledge: ${error.message}`,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
isError: true,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async run() {
|
|
170
|
+
try {
|
|
171
|
+
const transport = new StdioServerTransport();
|
|
172
|
+
await this.server.connect(transport);
|
|
173
|
+
logger.info('Knowledge Base MCP server running on stdio');
|
|
174
|
+
await this.faissManager.initialize();
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
logger.error('Error during server startup:', error);
|
|
178
|
+
if (error?.stack) {
|
|
179
|
+
logger.error(error.stack);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=KnowledgeBaseServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KnowledgeBaseServer.js","sourceRoot":"","sources":["../src/KnowledgeBaseServer.ts"],"names":[],"mappings":"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"}
|
package/build/config.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
export const KNOWLEDGE_BASES_ROOT_DIR = process.env.KNOWLEDGE_BASES_ROOT_DIR ||
|
|
4
|
+
path.join(os.homedir(), 'knowledge_bases');
|
|
5
|
+
export const DEFAULT_FAISS_INDEX_PATH = path.join(KNOWLEDGE_BASES_ROOT_DIR, '.faiss');
|
|
6
|
+
export const FAISS_INDEX_PATH = process.env.FAISS_INDEX_PATH || DEFAULT_FAISS_INDEX_PATH;
|
|
7
|
+
// Embedding provider configuration
|
|
8
|
+
export const EMBEDDING_PROVIDER = process.env.EMBEDDING_PROVIDER || 'huggingface';
|
|
9
|
+
// HuggingFace configuration
|
|
10
|
+
export const DEFAULT_HUGGINGFACE_MODEL_NAME = 'sentence-transformers/all-MiniLM-L6-v2';
|
|
11
|
+
export const HUGGINGFACE_MODEL_NAME = process.env.HUGGINGFACE_MODEL_NAME || DEFAULT_HUGGINGFACE_MODEL_NAME;
|
|
12
|
+
// Ollama configuration
|
|
13
|
+
export const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
|
|
14
|
+
export const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'dengcao/Qwen3-Embedding-0.6B:Q8_0';
|
|
15
|
+
// OpenAI configuration
|
|
16
|
+
export const OPENAI_MODEL_NAME = process.env.OPENAI_MODEL_NAME || 'text-embedding-ada-002';
|
|
17
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAEzB,MAAM,CAAC,MAAM,wBAAwB,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB;IAC1E,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,iBAAiB,CAAC,CAAC;AAE7C,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC,IAAI,CAAC,wBAAwB,EAAE,QAAQ,CAAC,CAAC;AACtF,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,wBAAwB,CAAC;AAEzF,mCAAmC;AACnC,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,aAAa,CAAC;AAElF,4BAA4B;AAC5B,MAAM,CAAC,MAAM,8BAA8B,GAAG,wCAAwC,CAAC;AACvF,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,8BAA8B,CAAC;AAE3G,uBAAuB;AACvB,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,wBAAwB,CAAC;AACvF,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,mCAAmC,CAAC;AAE5F,uBAAuB;AACvB,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,wBAAwB,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { logger } from './logger.js';
|
|
3
|
+
import { KnowledgeBaseServer } from './KnowledgeBaseServer.js';
|
|
4
|
+
const server = new KnowledgeBaseServer();
|
|
5
|
+
server.run().catch((error) => {
|
|
6
|
+
logger.error('Unhandled server error:', error);
|
|
7
|
+
if (error?.stack) {
|
|
8
|
+
logger.error(error.stack);
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,MAAM,MAAM,GAAG,IAAI,mBAAmB,EAAE,CAAC;AACzC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IAC3B,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;IAC/C,IAAI,KAAK,EAAE,KAAK,EAAE,CAAC;QACjB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC,CAAC,CAAC"}
|
package/build/logger.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { appendFileSync, createWriteStream, existsSync, mkdirSync } from 'fs';
|
|
2
|
+
import { dirname } from 'path';
|
|
3
|
+
import { format } from 'util';
|
|
4
|
+
const LEVEL_PRIORITY = {
|
|
5
|
+
debug: 10,
|
|
6
|
+
info: 20,
|
|
7
|
+
warn: 30,
|
|
8
|
+
error: 40,
|
|
9
|
+
};
|
|
10
|
+
const envLevel = (process.env.LOG_LEVEL || 'info').toLowerCase();
|
|
11
|
+
const activeLevel = LEVEL_PRIORITY[envLevel] ? envLevel : 'info';
|
|
12
|
+
const destinations = [process.stderr];
|
|
13
|
+
const logFilePath = process.env.LOG_FILE;
|
|
14
|
+
if (logFilePath) {
|
|
15
|
+
try {
|
|
16
|
+
const parentDir = dirname(logFilePath);
|
|
17
|
+
if (!existsSync(parentDir)) {
|
|
18
|
+
mkdirSync(parentDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
appendFileSync(logFilePath, '');
|
|
21
|
+
const stream = createWriteStream(logFilePath, { flags: 'a' });
|
|
22
|
+
stream.on('error', (error) => {
|
|
23
|
+
const message = `${new Date().toISOString()} [ERROR] Log file stream error for ${logFilePath}: ${format(error)}`;
|
|
24
|
+
process.stderr.write(`${message}\n`);
|
|
25
|
+
});
|
|
26
|
+
destinations.push(stream);
|
|
27
|
+
try {
|
|
28
|
+
stream.write('');
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const message = `${new Date().toISOString()} [ERROR] Failed to prime log file ${logFilePath}: ${format(error)}`;
|
|
32
|
+
process.stderr.write(`${message}\n`);
|
|
33
|
+
}
|
|
34
|
+
process.on('exit', () => {
|
|
35
|
+
try {
|
|
36
|
+
stream.end();
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
// noop – logging best effort on shutdown
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
const message = `${new Date().toISOString()} [ERROR] Failed to initialize log file ${logFilePath}: ${format(error)}`;
|
|
45
|
+
process.stderr.write(`${message}\n`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function write(level, args) {
|
|
49
|
+
if (LEVEL_PRIORITY[level] < LEVEL_PRIORITY[activeLevel]) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const message = format(...args);
|
|
53
|
+
const line = `${new Date().toISOString()} [${level.toUpperCase()}] ${message}\n`;
|
|
54
|
+
for (const destination of destinations) {
|
|
55
|
+
try {
|
|
56
|
+
destination.write(line);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// If writing to a destination fails, fall back to stderr without recursion.
|
|
60
|
+
if (destination !== process.stderr) {
|
|
61
|
+
process.stderr.write(`${new Date().toISOString()} [ERROR] Failed to write log: ${format(error)}\n`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export const logger = {
|
|
67
|
+
debug: (...args) => write('debug', args),
|
|
68
|
+
info: (...args) => write('info', args),
|
|
69
|
+
warn: (...args) => write('warn', args),
|
|
70
|
+
error: (...args) => write('error', args),
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as fsp from 'fs/promises';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
describe('logger', () => {
|
|
5
|
+
const originalEnv = {
|
|
6
|
+
LOG_FILE: process.env.LOG_FILE,
|
|
7
|
+
LOG_LEVEL: process.env.LOG_LEVEL,
|
|
8
|
+
};
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
if (originalEnv.LOG_FILE === undefined) {
|
|
11
|
+
delete process.env.LOG_FILE;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
process.env.LOG_FILE = originalEnv.LOG_FILE;
|
|
15
|
+
}
|
|
16
|
+
if (originalEnv.LOG_LEVEL === undefined) {
|
|
17
|
+
delete process.env.LOG_LEVEL;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
process.env.LOG_LEVEL = originalEnv.LOG_LEVEL;
|
|
21
|
+
}
|
|
22
|
+
jest.restoreAllMocks();
|
|
23
|
+
jest.resetModules();
|
|
24
|
+
});
|
|
25
|
+
it('writes log messages to the configured file', async () => {
|
|
26
|
+
const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'kb-logger-file-'));
|
|
27
|
+
const logFile = path.join(tempDir, 'logs', 'app.log');
|
|
28
|
+
process.env.LOG_FILE = logFile;
|
|
29
|
+
process.env.LOG_LEVEL = 'debug';
|
|
30
|
+
await jest.isolateModulesAsync(async () => {
|
|
31
|
+
const { logger } = await import('./logger.js');
|
|
32
|
+
logger.info('File target message');
|
|
33
|
+
logger.debug('debug content');
|
|
34
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
35
|
+
});
|
|
36
|
+
await expect(fsp.stat(path.dirname(logFile))).resolves.toBeTruthy();
|
|
37
|
+
const fileContents = await fsp.readFile(logFile, 'utf-8');
|
|
38
|
+
expect(fileContents).toContain('File target message');
|
|
39
|
+
expect(fileContents).toContain('[DEBUG] debug content');
|
|
40
|
+
});
|
|
41
|
+
it('falls back to stderr when log file cannot be initialized', async () => {
|
|
42
|
+
const stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
43
|
+
const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'kb-logger-stderr-'));
|
|
44
|
+
const lockedDir = path.join(tempDir, 'locked');
|
|
45
|
+
await fsp.mkdir(lockedDir, { recursive: true });
|
|
46
|
+
await fsp.chmod(lockedDir, 0o500);
|
|
47
|
+
const logFile = path.join(lockedDir, 'app.log');
|
|
48
|
+
process.env.LOG_FILE = logFile;
|
|
49
|
+
try {
|
|
50
|
+
await jest.isolateModulesAsync(async () => {
|
|
51
|
+
const { logger } = await import('./logger.js');
|
|
52
|
+
logger.info('Fallback message');
|
|
53
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await fsp.chmod(lockedDir, 0o700);
|
|
58
|
+
}
|
|
59
|
+
const stderrOutput = stderrSpy.mock.calls.flat().join('');
|
|
60
|
+
expect(stderrOutput).toMatch(/Failed to (initialize|write log)|Log file stream error/);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=logger.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.test.js","sourceRoot":"","sources":["../src/logger.test.ts"],"names":[],"mappings":"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"}
|
package/build/utils.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import * as fsp from 'fs/promises';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { logger } from './logger.js';
|
|
5
|
+
export async function calculateSHA256(filePath) {
|
|
6
|
+
const fileBuffer = await fsp.readFile(filePath);
|
|
7
|
+
const hashSum = crypto.createHash('sha256');
|
|
8
|
+
hashSum.update(fileBuffer);
|
|
9
|
+
return hashSum.digest('hex');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Recursively gets all files in a directory, excluding hidden files and directories.
|
|
13
|
+
* @param dirPath The directory path to search
|
|
14
|
+
* @returns Array of file paths
|
|
15
|
+
*/
|
|
16
|
+
export async function getFilesRecursively(dirPath) {
|
|
17
|
+
const files = [];
|
|
18
|
+
async function traverse(currentPath) {
|
|
19
|
+
try {
|
|
20
|
+
const entries = await fsp.readdir(currentPath, { withFileTypes: true });
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
// Skip hidden files and directories
|
|
23
|
+
if (entry.name.startsWith('.')) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
await traverse(fullPath);
|
|
29
|
+
}
|
|
30
|
+
else if (entry.isFile()) {
|
|
31
|
+
files.push(fullPath);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger.error(`Error traversing directory ${currentPath}:`, error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
await traverse(dirPath);
|
|
40
|
+
return files;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAAe;IACvD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,UAAU,QAAQ,CAAC,WAAmB;QACzC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YAExE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,oCAAoC;gBACpC,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC/B,SAAS;gBACX,CAAC;gBAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBAEpD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;oBACxB,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAC3B,CAAC;qBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;oBAC1B,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACvB,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,8BAA8B,WAAW,GAAG,EAAE,KAAK,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;IACxB,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { getFilesRecursively } from './utils.js';
|
|
2
|
+
import * as fsp from 'fs/promises';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
// Mock fs/promises
|
|
5
|
+
jest.mock('fs/promises', () => ({
|
|
6
|
+
...jest.requireActual('fs/promises'), // Import and retain default behavior
|
|
7
|
+
readdir: jest.fn(), // Mock readdir specifically
|
|
8
|
+
}));
|
|
9
|
+
describe('getFilesRecursively', () => {
|
|
10
|
+
const mockReaddir = fsp.readdir;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
it('should return files recursively from nested directories', async () => {
|
|
15
|
+
// Mock directory structure:
|
|
16
|
+
// test_dir/
|
|
17
|
+
// file1.txt
|
|
18
|
+
// sub_dir/
|
|
19
|
+
// file2.txt
|
|
20
|
+
// nested_dir/
|
|
21
|
+
// file3.txt
|
|
22
|
+
mockReaddir.mockImplementation(async (dirPath, options) => {
|
|
23
|
+
const dir = dirPath.toString();
|
|
24
|
+
if (dir === 'test_dir') {
|
|
25
|
+
return [
|
|
26
|
+
{ name: 'file1.txt', isDirectory: () => false, isFile: () => true },
|
|
27
|
+
{ name: 'sub_dir', isDirectory: () => true, isFile: () => false }
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
else if (dir === path.join('test_dir', 'sub_dir')) {
|
|
31
|
+
return [
|
|
32
|
+
{ name: 'file2.txt', isDirectory: () => false, isFile: () => true },
|
|
33
|
+
{ name: 'nested_dir', isDirectory: () => true, isFile: () => false }
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
else if (dir === path.join('test_dir', 'sub_dir', 'nested_dir')) {
|
|
37
|
+
return [
|
|
38
|
+
{ name: 'file3.txt', isDirectory: () => false, isFile: () => true }
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
});
|
|
43
|
+
const files = await getFilesRecursively('test_dir');
|
|
44
|
+
expect(files).toEqual([
|
|
45
|
+
path.join('test_dir', 'file1.txt'),
|
|
46
|
+
path.join('test_dir', 'sub_dir', 'file2.txt'),
|
|
47
|
+
path.join('test_dir', 'sub_dir', 'nested_dir', 'file3.txt')
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
it('should skip hidden files and directories', async () => {
|
|
51
|
+
mockReaddir.mockImplementation(async (dirPath, options) => {
|
|
52
|
+
const dir = dirPath.toString();
|
|
53
|
+
if (dir === 'test_dir') {
|
|
54
|
+
return [
|
|
55
|
+
{ name: 'file1.txt', isDirectory: () => false, isFile: () => true },
|
|
56
|
+
{ name: '.hidden_file', isDirectory: () => false, isFile: () => true },
|
|
57
|
+
{ name: '.hidden_dir', isDirectory: () => true, isFile: () => false },
|
|
58
|
+
{ name: 'visible_dir', isDirectory: () => true, isFile: () => false }
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
else if (dir === path.join('test_dir', 'visible_dir')) {
|
|
62
|
+
return [
|
|
63
|
+
{ name: 'file2.txt', isDirectory: () => false, isFile: () => true },
|
|
64
|
+
{ name: '.hidden_file2', isDirectory: () => false, isFile: () => true }
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
});
|
|
69
|
+
const files = await getFilesRecursively('test_dir');
|
|
70
|
+
expect(files).toEqual([
|
|
71
|
+
path.join('test_dir', 'file1.txt'),
|
|
72
|
+
path.join('test_dir', 'visible_dir', 'file2.txt')
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
it('should handle empty directories', async () => {
|
|
76
|
+
mockReaddir.mockResolvedValue([]);
|
|
77
|
+
const files = await getFilesRecursively('empty_dir');
|
|
78
|
+
expect(files).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
it('should handle errors gracefully', async () => {
|
|
81
|
+
mockReaddir.mockRejectedValue(new Error('Permission denied'));
|
|
82
|
+
const files = await getFilesRecursively('error_dir');
|
|
83
|
+
expect(files).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=utils.test.js.map
|