n2-qln 3.4.2 → 4.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.
Files changed (57) hide show
  1. package/README.ko.md +459 -470
  2. package/README.md +459 -490
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +87 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/config.d.ts +9 -0
  7. package/{lib → dist/lib}/config.js +23 -27
  8. package/dist/lib/config.js.map +1 -0
  9. package/dist/lib/embedding.d.ts +27 -0
  10. package/{lib → dist/lib}/embedding.js +39 -47
  11. package/dist/lib/embedding.js.map +1 -0
  12. package/dist/lib/executor.d.ts +57 -0
  13. package/dist/lib/executor.js +175 -0
  14. package/dist/lib/executor.js.map +1 -0
  15. package/dist/lib/mcp-discovery.d.ts +83 -0
  16. package/dist/lib/mcp-discovery.js +203 -0
  17. package/dist/lib/mcp-discovery.js.map +1 -0
  18. package/dist/lib/provider-loader.d.ts +13 -0
  19. package/dist/lib/provider-loader.js +146 -0
  20. package/dist/lib/provider-loader.js.map +1 -0
  21. package/dist/lib/registry.d.ts +38 -0
  22. package/{lib → dist/lib}/registry.js +82 -92
  23. package/dist/lib/registry.js.map +1 -0
  24. package/dist/lib/router.d.ts +63 -0
  25. package/{lib → dist/lib}/router.js +75 -117
  26. package/dist/lib/router.js.map +1 -0
  27. package/dist/lib/schema.d.ts +20 -0
  28. package/{lib → dist/lib}/schema.js +38 -30
  29. package/dist/lib/schema.js.map +1 -0
  30. package/dist/lib/store.d.ts +37 -0
  31. package/dist/lib/store.js +207 -0
  32. package/dist/lib/store.js.map +1 -0
  33. package/dist/lib/validator.d.ts +37 -0
  34. package/dist/lib/validator.js +114 -0
  35. package/dist/lib/validator.js.map +1 -0
  36. package/dist/lib/vector-index.d.ts +37 -0
  37. package/{lib → dist/lib}/vector-index.js +19 -36
  38. package/dist/lib/vector-index.js.map +1 -0
  39. package/dist/tools/qln-call.d.ts +41 -0
  40. package/dist/tools/qln-call.js +353 -0
  41. package/dist/tools/qln-call.js.map +1 -0
  42. package/dist/tools/qln-helpers.d.ts +55 -0
  43. package/dist/tools/qln-helpers.js +88 -0
  44. package/dist/tools/qln-helpers.js.map +1 -0
  45. package/dist/types.d.ts +243 -0
  46. package/dist/types.js +4 -0
  47. package/dist/types.js.map +1 -0
  48. package/index.js +3 -79
  49. package/package.json +11 -4
  50. package/.github/FUNDING.yml +0 -3
  51. package/docs/README.md +0 -2
  52. package/docs/architecture.png +0 -0
  53. package/lib/executor.js +0 -104
  54. package/lib/provider-loader.js +0 -126
  55. package/lib/store.js +0 -217
  56. package/lib/validator.js +0 -171
  57. package/tools/qln-call.js +0 -257
@@ -0,0 +1,243 @@
1
+ /** Normalized tool entry stored in registry + SQLite */
2
+ export interface ToolEntry {
3
+ name: string;
4
+ description: string;
5
+ source: string;
6
+ category: string;
7
+ provider: string;
8
+ inputSchema: Record<string, unknown> | null;
9
+ triggers: string[];
10
+ tags: string[];
11
+ examples: string[];
12
+ endpoint: string;
13
+ searchText: string;
14
+ /** 3-10 word capability phrase for BM25 search boosting */
15
+ boostKeywords: string;
16
+ usageCount: number;
17
+ successRate: number;
18
+ /** Consecutive failure count for circuit breaker */
19
+ consecutiveFailures: number;
20
+ lastUsedAt: string | null;
21
+ embedding: number[] | null;
22
+ registeredAt: string;
23
+ updatedAt: string;
24
+ }
25
+ /** Raw tool data before normalization (partial fields accepted) */
26
+ export interface RawToolEntry {
27
+ name: string;
28
+ description?: string;
29
+ source?: string;
30
+ category?: string;
31
+ provider?: string;
32
+ pluginName?: string;
33
+ inputSchema?: Record<string, unknown> | null;
34
+ triggers?: string[];
35
+ tags?: string[];
36
+ examples?: string[];
37
+ endpoint?: string;
38
+ /** 3-10 word capability phrase for BM25 search boosting */
39
+ boostKeywords?: string;
40
+ usageCount?: number;
41
+ successRate?: number;
42
+ lastUsedAt?: string | null;
43
+ embedding?: number[] | null;
44
+ registeredAt?: string;
45
+ }
46
+ /** QLN configuration (config.js + config.local.js merged) */
47
+ export interface QLNConfig {
48
+ dataDir: string;
49
+ embedding: {
50
+ enabled: boolean;
51
+ model: string;
52
+ endpoint: string;
53
+ };
54
+ executor: {
55
+ httpEndpoint: string | null;
56
+ timeout: number;
57
+ };
58
+ providers: {
59
+ enabled: boolean;
60
+ dir: string;
61
+ };
62
+ search: {
63
+ defaultTopK: number;
64
+ threshold: number;
65
+ /** Per-source score multiplier (default: 1.0 for all) */
66
+ sourceWeights: Record<string, number>;
67
+ };
68
+ }
69
+ /** Search result returned by Router */
70
+ export interface SearchResult {
71
+ name: string;
72
+ score: number;
73
+ stages: {
74
+ trigger: number;
75
+ keyword: number;
76
+ semantic: number;
77
+ usage: number;
78
+ success: number;
79
+ recencyFactor: number;
80
+ };
81
+ description: string;
82
+ source: string;
83
+ category: string;
84
+ inputSchema: Record<string, unknown> | null;
85
+ explorer?: boolean;
86
+ }
87
+ /** Timing info from Router search */
88
+ export interface SearchTiming {
89
+ stage1: number;
90
+ stage2: number;
91
+ stage3: number;
92
+ merge: number;
93
+ total: number;
94
+ }
95
+ /** Validation error from validator */
96
+ export interface ValidationError {
97
+ field: string;
98
+ message: string;
99
+ severity: 'error' | 'warning';
100
+ }
101
+ /** Provider manifest JSON structure */
102
+ export interface ProviderManifest {
103
+ provider: string;
104
+ version?: string;
105
+ description?: string;
106
+ endpoint?: string;
107
+ tools: Array<{
108
+ name: string;
109
+ description: string;
110
+ category?: string;
111
+ inputSchema?: Record<string, unknown>;
112
+ triggers?: string[];
113
+ tags?: string[];
114
+ examples?: string[];
115
+ endpoint?: string;
116
+ boostKeywords?: string;
117
+ }>;
118
+ }
119
+ /** Tool execution result */
120
+ export interface ExecResult {
121
+ result: unknown;
122
+ source: 'local' | 'http';
123
+ elapsed: number;
124
+ /** Whether this result came from a fallback tool */
125
+ fallback?: boolean;
126
+ /** Original tool name if fallback was used */
127
+ originalTool?: string;
128
+ }
129
+ /** Circuit breaker configuration */
130
+ export interface CircuitBreakerConfig {
131
+ /** Consecutive failures before tripping (default: 3) */
132
+ failureThreshold: number;
133
+ /** Milliseconds before attempting recovery (default: 60000) */
134
+ recoveryTimeout: number;
135
+ }
136
+ /** Circuit breaker states */
137
+ export type CircuitState = 'closed' | 'open' | 'half-open';
138
+ /** MCP tool response content */
139
+ export interface McpToolResponse {
140
+ content: Array<{
141
+ type: string;
142
+ text: string;
143
+ }>;
144
+ isError?: boolean;
145
+ }
146
+ /** SQLite row from tools table (snake_case columns) */
147
+ export interface ToolRow {
148
+ name: string;
149
+ description: string;
150
+ source: string;
151
+ category: string;
152
+ provider: string;
153
+ plugin_name?: string;
154
+ input_schema: string;
155
+ triggers: string;
156
+ tags: string;
157
+ examples: string;
158
+ endpoint: string;
159
+ search_text: string;
160
+ embedding: string;
161
+ usage_count: number;
162
+ success_rate: number;
163
+ last_used_at: string | null;
164
+ registered_at: string;
165
+ updated_at: string;
166
+ }
167
+ /** sql.js Database interface (minimal) */
168
+ export interface SqlJsDatabase {
169
+ run(sql: string, params?: unknown[]): void;
170
+ exec(sql: string): Array<{
171
+ columns: string[];
172
+ values: unknown[][];
173
+ }>;
174
+ export(): Uint8Array;
175
+ close(): void;
176
+ }
177
+ /** sql.js static module interface */
178
+ export interface SqlJsStatic {
179
+ Database: new (data?: ArrayLike<number>) => SqlJsDatabase;
180
+ }
181
+ /** Stage score accumulator used internally by Router */
182
+ export interface StageScores {
183
+ stage1: number;
184
+ stage2: number;
185
+ stage3: number;
186
+ }
187
+ /** Registry stats */
188
+ export interface RegistryStats {
189
+ total: number;
190
+ bySource: Record<string, number>;
191
+ byCategory: Record<string, number>;
192
+ withEmbedding: number;
193
+ embeddingCoverage: string;
194
+ }
195
+ /** VectorIndex build result */
196
+ export interface VectorBuildResult {
197
+ indexed: number;
198
+ categories: number;
199
+ dimension: number;
200
+ }
201
+ /** VectorIndex stats */
202
+ export interface VectorStats {
203
+ built: boolean;
204
+ tools: number;
205
+ dimension: number;
206
+ categories: number;
207
+ categoryList: string[];
208
+ memoryKB: number;
209
+ }
210
+ /** Router stats */
211
+ export interface RouterStats {
212
+ registrySize: number;
213
+ vectorIndex: VectorStats;
214
+ embeddingAvailable: boolean;
215
+ bm25: {
216
+ idfTerms: number;
217
+ avgDocLen: number;
218
+ k1: number;
219
+ b: number;
220
+ };
221
+ }
222
+ /** Provider load result */
223
+ export interface ProviderLoadResult {
224
+ loaded: number;
225
+ skipped: number;
226
+ failed: number;
227
+ details: Array<{
228
+ file: string;
229
+ status: 'loaded' | 'skipped' | 'failed';
230
+ reason?: string;
231
+ provider?: string;
232
+ toolCount?: number;
233
+ }>;
234
+ }
235
+ /** Embedding precompute result */
236
+ export interface EmbeddingPrecomputeResult {
237
+ embedded: number;
238
+ skipped: number;
239
+ failed: number;
240
+ }
241
+ /** Tool handler function signature */
242
+ export type ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;
243
+ //# sourceMappingURL=types.d.ts.map
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // QLN — Shared type definitions for all modules
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,gDAAgD"}
package/index.js CHANGED
@@ -1,80 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // QLN — Query Layer Network MCP server entry point
3
- // Semantic tool dispatcher: route 1000 tools through 1 router
4
- const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
5
- const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
6
- const { z } = require('zod');
7
-
8
- // Core
9
- const path = require('path');
10
- const { loadConfig } = require('./lib/config');
11
- const { Store } = require('./lib/store');
12
- const { Embedding } = require('./lib/embedding');
13
- const { Registry } = require('./lib/registry');
14
- const { VectorIndex } = require('./lib/vector-index');
15
- const { Router } = require('./lib/router');
16
- const { Executor } = require('./lib/executor');
17
- const { loadProviders } = require('./lib/provider-loader');
18
-
19
- // MCP Tool (unified)
20
- const { registerQlnCall } = require('./tools/qln-call');
21
-
22
- async function main() {
23
- const config = loadConfig();
24
-
25
- // 1. Core engine initialization
26
- const store = new Store(config.dataDir);
27
- await store.init();
28
-
29
- const embedding = config.embedding?.enabled
30
- ? new Embedding(config.embedding)
31
- : null;
32
-
33
- const registry = new Registry(store, embedding);
34
- registry.load();
35
-
36
- // 1.5. Provider auto-indexing (providers/*.json → registry)
37
- if (config.providers?.enabled !== false) {
38
- const provDir = config.providers?.dir || path.join(__dirname, 'providers');
39
- const provResult = loadProviders(provDir, registry);
40
- if (provResult.loaded > 0) {
41
- console.error(`[QLN] Providers: ${provResult.loaded} tools from ${provResult.details.filter(d => d.status === 'loaded').length} files`);
42
- }
43
- if (provResult.failed > 0) {
44
- console.error(`[QLN] Provider warnings: ${provResult.failed} files failed to load`);
45
- }
46
- }
47
-
48
- const vectorIndex = new VectorIndex();
49
- const router = new Router(registry, vectorIndex, embedding);
50
- const executor = new Executor(config.executor || {});
51
-
52
- // 2. Precompute embeddings + build vector index (async, non-blocking)
53
- if (embedding) {
54
- setImmediate(async () => {
55
- try {
56
- await registry.precomputeEmbeddings();
57
- router.buildIndex();
58
- } catch { /* Ollama not available — Stage 1+2 still work */ }
59
- });
60
- }
61
-
62
- // 3. Create MCP server
63
- const pkg = require('./package.json');
64
- const server = new McpServer({
65
- name: 'n2-qln',
66
- version: pkg.version,
67
- });
68
-
69
- // 4. Register unified MCP tool (1 tool, 5 actions)
70
- registerQlnCall(server, z, router, executor, registry);
71
-
72
- // 5. Connect stdio transport
73
- const transport = new StdioServerTransport();
74
- await server.connect(transport);
75
- }
76
-
77
- main().catch(err => {
78
- console.error(`[QLN] Fatal: ${err.message}`);
79
- process.exit(1);
80
- });
2
+ // QLN v4.0.0 CJS forwarder (for backward compatibility)
3
+ // Actual entry point: dist/index.js (compiled from src/index.ts)
4
+ require('./dist/index.js');
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "n2-qln",
3
- "version": "3.4.2",
3
+ "version": "4.1.0",
4
4
  "description": "Query Layer Network — Semantic tool dispatcher for MCP. Route 1000 tools through 1 router.",
5
- "main": "index.js",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
6
7
  "bin": {
7
- "n2-qln": "./index.js"
8
+ "n2-qln": "./dist/index.js"
8
9
  },
9
10
  "scripts": {
10
- "start": "node index.js",
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsc --watch",
11
14
  "test": "node test/test-provider-loader.js"
12
15
  },
13
16
  "dependencies": {
@@ -30,5 +33,9 @@
30
33
  },
31
34
  "engines": {
32
35
  "node": ">=18.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^25.5.0",
39
+ "typescript": "^6.0.2"
33
40
  }
34
41
  }
@@ -1,3 +0,0 @@
1
- # QLN MCP — Funding configuration
2
- # This enables the 💖 Sponsor button on the GitHub repository
3
- github: [choihyunsus]
package/docs/README.md DELETED
@@ -1,2 +0,0 @@
1
- # QLN docs directory
2
- Architecture diagrams and documentation assets.
Binary file
package/lib/executor.js DELETED
@@ -1,104 +0,0 @@
1
- // QLN — L3 tool executor
2
- // Execute tools via HTTP (localhost) or registered local handlers
3
- const http = require('http');
4
-
5
- /**
6
- * Tool executor — HTTP proxy + local function calls.
7
- *
8
- * Execution priority:
9
- * 1. Local registered handler (via addHandler)
10
- * 2. HTTP proxy (when endpoint configured)
11
- */
12
- class Executor {
13
- /**
14
- * @param {object} config
15
- * @param {string} [config.httpEndpoint] - Tool execution HTTP endpoint (e.g. "http://127.0.0.1:PORT")
16
- * @param {number} [config.timeout=20000] - HTTP timeout (ms)
17
- */
18
- constructor(config = {}) {
19
- this._httpEndpoint = config.httpEndpoint || null;
20
- this._timeout = config.timeout || 20000;
21
- /** @type {Map<string, Function>} Local handlers */
22
- this._handlers = new Map();
23
- }
24
-
25
- /**
26
- * Register a local tool handler.
27
- * @param {string} name - Tool name
28
- * @param {Function} handler - (args) => Promise<unknown>
29
- */
30
- addHandler(name, handler) {
31
- this._handlers.set(name, handler);
32
- }
33
-
34
- /**
35
- * Execute a tool.
36
- * @param {string} name - Tool name
37
- * @param {object} args - Tool arguments
38
- * @returns {Promise<{result: unknown, source: string, elapsed: number}>}
39
- */
40
- async exec(name, args = {}) {
41
- const t0 = Date.now();
42
-
43
- // 1. Local handler first
44
- if (this._handlers.has(name)) {
45
- const handler = this._handlers.get(name);
46
- const result = await handler(args);
47
- return { result, source: 'local', elapsed: Date.now() - t0 };
48
- }
49
-
50
- // 2. HTTP proxy
51
- if (this._httpEndpoint) {
52
- const result = await this._execHttp(name, args);
53
- return { result, source: 'http', elapsed: Date.now() - t0 };
54
- }
55
-
56
- throw new Error(`No handler found for tool: ${name}. Register with addHandler() or set httpEndpoint.`);
57
- }
58
-
59
- /**
60
- * Dynamically set HTTP endpoint.
61
- * @param {string} endpoint - "http://127.0.0.1:PORT" format
62
- */
63
- setHttpEndpoint(endpoint) {
64
- this._httpEndpoint = endpoint;
65
- }
66
-
67
- /** @private HTTP POST /call → tool execution */
68
- _execHttp(name, args) {
69
- return new Promise((resolve, reject) => {
70
- const url = new URL(this._httpEndpoint);
71
- const bodyStr = JSON.stringify({ tool: name, args });
72
- const timer = setTimeout(() => reject(new Error(`timeout (${this._timeout}ms)`)), this._timeout);
73
-
74
- const req = http.request({
75
- hostname: url.hostname,
76
- port: url.port,
77
- path: '/call',
78
- method: 'POST',
79
- headers: {
80
- 'Content-Type': 'application/json',
81
- 'Content-Length': Buffer.byteLength(bodyStr),
82
- },
83
- }, (res) => {
84
- let body = '';
85
- res.on('data', c => body += c);
86
- res.on('end', () => {
87
- clearTimeout(timer);
88
- try {
89
- const parsed = JSON.parse(body);
90
- if (parsed.error) reject(new Error(parsed.error));
91
- else resolve(parsed.result);
92
- } catch {
93
- reject(new Error(`Invalid response: ${body.slice(0, 200)}`));
94
- }
95
- });
96
- });
97
- req.on('error', e => { clearTimeout(timer); reject(e); });
98
- req.write(bodyStr);
99
- req.end();
100
- });
101
- }
102
- }
103
-
104
- module.exports = { Executor };
@@ -1,126 +0,0 @@
1
- // QLN — Provider manifest loader (providers/*.json → registry auto-registration)
2
- const fs = require('fs');
3
- const path = require('path');
4
- const { inferCategory } = require('./schema');
5
-
6
- /**
7
- * Required fields for a valid provider manifest.
8
- * @type {string[]}
9
- */
10
- const REQUIRED_MANIFEST_FIELDS = ['provider', 'tools'];
11
-
12
- /**
13
- * Required fields for each tool entry within a manifest.
14
- * @type {string[]}
15
- */
16
- const REQUIRED_TOOL_FIELDS = ['name', 'description'];
17
-
18
- /**
19
- * Load all provider manifests from a directory and register their tools.
20
- *
21
- * @param {string} providersDir - Absolute path to providers/ directory
22
- * @param {import('./registry').Registry} registry - QLN registry instance
23
- * @returns {{ loaded: number, skipped: number, failed: number, details: object[] }}
24
- */
25
- function loadProviders(providersDir, registry) {
26
- const result = { loaded: 0, skipped: 0, failed: 0, details: [] };
27
-
28
- if (!fs.existsSync(providersDir)) return result;
29
-
30
- /** @type {string[]} */
31
- const files = fs.readdirSync(providersDir)
32
- .filter(f => f.endsWith('.json'));
33
-
34
- if (files.length === 0) return result;
35
-
36
- for (const file of files) {
37
- const filePath = path.join(providersDir, file);
38
- try {
39
- const manifest = _parseManifest(filePath);
40
- if (!manifest) {
41
- result.skipped++;
42
- result.details.push({ file, status: 'skipped', reason: 'invalid manifest' });
43
- continue;
44
- }
45
-
46
- const tools = _normalizeTools(manifest);
47
- if (tools.length === 0) {
48
- result.skipped++;
49
- result.details.push({ file, status: 'skipped', reason: 'no valid tools' });
50
- continue;
51
- }
52
-
53
- // Idempotent: purge old entries from this provider before re-registering
54
- registry.purgeBySource(`provider:${manifest.provider}`);
55
-
56
- const count = registry.registerBatch(tools);
57
- result.loaded += count;
58
- result.details.push({
59
- file,
60
- status: 'loaded',
61
- provider: manifest.provider,
62
- toolCount: count,
63
- });
64
- } catch (err) {
65
- result.failed++;
66
- result.details.push({ file, status: 'failed', reason: err.message });
67
- }
68
- }
69
-
70
- return result;
71
- }
72
-
73
- /**
74
- * Parse and validate a manifest JSON file.
75
- *
76
- * @param {string} filePath - Absolute path to JSON file
77
- * @returns {object|null} Parsed manifest or null if invalid
78
- */
79
- function _parseManifest(filePath) {
80
- const raw = fs.readFileSync(filePath, 'utf-8');
81
- const manifest = JSON.parse(raw);
82
-
83
- // Validate required fields
84
- for (const field of REQUIRED_MANIFEST_FIELDS) {
85
- if (!manifest[field]) return null;
86
- }
87
-
88
- // tools must be a non-empty array
89
- if (!Array.isArray(manifest.tools)) return null;
90
-
91
- return manifest;
92
- }
93
-
94
- /**
95
- * Normalize tool entries from a manifest for registry registration.
96
- * Injects provider metadata and assigns source = "provider:{name}".
97
- *
98
- * @param {object} manifest - Validated manifest object
99
- * @returns {object[]} Array of normalized tool entries ready for registerBatch()
100
- */
101
- function _normalizeTools(manifest) {
102
- const providerName = manifest.provider;
103
- const tools = [];
104
-
105
- for (const raw of manifest.tools) {
106
- // Skip tools missing required fields
107
- if (!raw.name || !raw.description) continue;
108
-
109
- tools.push({
110
- name: raw.name,
111
- description: raw.description,
112
- source: `provider:${providerName}`,
113
- category: raw.category || inferCategory(raw.name, 'provider'),
114
- provider: providerName,
115
- inputSchema: raw.inputSchema || null,
116
- triggers: raw.triggers || undefined, // let schema.js extract
117
- tags: raw.tags || [],
118
- examples: raw.examples || [],
119
- endpoint: raw.endpoint || '',
120
- });
121
- }
122
-
123
- return tools;
124
- }
125
-
126
- module.exports = { loadProviders };