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
package/README.md CHANGED
@@ -1,490 +1,459 @@
1
- 🇰🇷 [한국어](README.ko.md)
2
-
3
- # n2-qln
4
-
5
- [![npm](https://img.shields.io/npm/v/n2-qln?color=brightgreen)](https://www.npmjs.com/package/n2-qln) [![license](https://img.shields.io/npm/l/n2-qln)](LICENSE) [![node](https://img.shields.io/node/v/n2-qln?color=brightgreen)](https://nodejs.org) [![downloads](https://img.shields.io/npm/dm/n2-qln?color=blue)](https://www.npmjs.com/package/n2-qln)
6
-
7
- **QLN** = **Q**uery **L**ayer **N**etwork — a semantic search layer that sits between the AI and your tools.
8
-
9
- > **Route 1,000+ tools through 1 MCP tool.** The AI sees only the router — not all 1,000 tools.
10
-
11
- ![QLN Architecture — Without vs With](docs/architecture.png)
12
-
13
- ## Table of Contents
14
-
15
- - [Features](#features)
16
- - [The Problem](#the-problem)
17
- - [Installation](#installation)
18
- - [Setup](#setup)
19
- - [How It Works](#how-it-works)
20
- - [API Reference](#api-reference)
21
- - [Configuration](#configuration)
22
- - [Semantic Search Setup](#semantic-search-setup-optional)
23
- - [Project Structure](#project-structure)
24
- - [Built & Battle-Tested](#built--battle-tested)
25
- - [FAQ](#faq)
26
- - [Contributing](#contributing)
27
-
28
- ## Features
29
-
30
- **One tool to rule them all** — Your AI sees `n2_qln_call` (~200 tokens), not 1,000 individual tools. 99.6% context reduction.
31
-
32
- **Sub-5ms search** — 3-stage search engine (trigger + BM25 keyword + semantic) finds the right tool in under 5ms, even with 1,000+ tools indexed.
33
-
34
- **BM25 keyword ranking** *(v3.4)* — Stage 2 uses [Okapi BM25](https://en.wikipedia.org/wiki/Okapi_BM25) for keyword search. Rare terms score higher, document length is normalized. The same algorithm behind Google, Elasticsearch, and Wikipedia search.
35
-
36
- **Self-learning ranking** — Tools that get used more and succeed more are automatically ranked higher over time. No manual tuning needed.
37
-
38
- **Live tool management** — Add, update, or remove tools at runtime. No server restart required. Group tools by provider for bulk operations.
39
-
40
- **Enforced quality** — Strict validation on tool registration: `verb_target` naming, minimum description length, category constraints. Bad tools are rejected, not silently accepted.
41
-
42
- **Semantic search (optional)** — Add [Ollama](https://ollama.ai) for vector similarity search. Without it, Stage 1 + 2 still deliver great results. Graceful degradation — if Ollama goes down, search keeps working.
43
-
44
- **Zero native dependencies** Built on [sql.js](https://github.com/sql-js/sql.js) (WASM). No `node-gyp`, no build step, no platform-specific binaries. `npm install` and done.
45
-
46
- **Dual execution** Tools can run as local functions or HTTP endpoints. Register a handler directly, or point to a remote service. Mix and match.
47
-
48
- **Provider auto-indexing** *(v3.3)* Drop a JSON manifest in `providers/` and tools are auto-registered at boot. No code changes, no manual `create` calls. Idempotent and error-isolated.
49
-
50
- **Scales to 10,000+** Centroid hierarchy partitions tools by category, then searches within partitions. 100 tools ~1ms, 1,000 ~3ms, 10,000 ~5ms.
51
-
52
- **Universal MCP** Works with Claude Desktop, Cursor, n2-soul, or any MCP-compatible client. Standard stdio transport.
53
-
54
- ## The Problem
55
-
56
- Every MCP tool you register eats AI context tokens. With 10 tools that's manageable. With 100, the AI slows down. **With 1,000, it's impossible** the context window is full before the conversation even starts.
57
-
58
- QLN solves this by acting as a **semantic search router**:
59
-
60
- 1. Register all your tools in QLN's SQLite index
61
- 2. The AI sees only **one tool**: `n2_qln_call` (~200 tokens)
62
- 3. When the AI needs a tool, it **searches** → **finds the best match** → **executes**
63
-
64
- **Result: ~200 tokens instead of ~50,000. 99.6% reduction.**
65
-
66
- ---
67
-
68
- ## Installation
69
-
70
- ```bash
71
- npm install n2-qln
72
- ```
73
-
74
- **Requirements:** Node.js ≥ 18
75
-
76
- **Optional:** Install [Ollama](https://ollama.ai) for semantic vector search (Stage 3). See [Semantic Search Setup](#semantic-search-setup-optional).
77
-
78
- ---
79
-
80
- ## Setup
81
-
82
- QLN is an MCP server. You connect it to any MCP-compatible AI client — Claude Desktop, Cursor, n2-soul, or any other host.
83
-
84
- ### Claude Desktop
85
-
86
- Edit your Claude Desktop config file:
87
-
88
- - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
89
- - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
90
-
91
- ```json
92
- {
93
- "mcpServers": {
94
- "n2-qln": {
95
- "command": "npx",
96
- "args": ["-y", "n2-qln"]
97
- }
98
- }
99
- }
100
- ```
101
-
102
- Restart Claude Desktop. The `n2_qln_call` tool will appear in your tool list.
103
-
104
- ### Cursor
105
-
106
- Open **Settings → MCP Servers → Add Server** and configure:
107
-
108
- ```json
109
- {
110
- "name": "n2-qln",
111
- "command": "npx",
112
- "args": ["-y", "n2-qln"]
113
- }
114
- ```
115
-
116
- ### n2-soul
117
-
118
- Add to your Soul `config.local.js`:
119
-
120
- ```javascript
121
- module.exports = {
122
- mcpServers: {
123
- 'n2-qln': {
124
- command: 'node',
125
- args: ['<path-to-qln>/index.js'],
126
- }
127
- }
128
- };
129
- ```
130
-
131
- Or if published to npm:
132
-
133
- ```javascript
134
- module.exports = {
135
- mcpServers: {
136
- 'n2-qln': {
137
- command: 'npx',
138
- args: ['-y', 'n2-qln'],
139
- }
140
- }
141
- };
142
- ```
143
-
144
- ### Any MCP Client
145
-
146
- QLN uses **stdio transport** — the standard MCP communication method. Any MCP-compatible client can connect using:
147
-
148
- ```
149
- command: npx
150
- args: ["-y", "n2-qln"]
151
- ```
152
-
153
- Or if you cloned the repo:
154
-
155
- ```
156
- command: node
157
- args: ["/absolute/path/to/n2-qln/index.js"]
158
- ```
159
-
160
- > ** Tip:** The easiest way to set this up? **Just ask your AI agent.** Tell it *"Add n2-qln to my MCP config"* — it already knows how to configure itself.
161
-
162
- ---
163
-
164
- ## How It Works
165
-
166
- ### Step-by-Step Example
167
-
168
- ```
169
- User: "Take a screenshot of this page"
170
-
171
- Step 1 AI calls: n2_qln_call(action: "search", query: "screenshot page")
172
- QLN searches 1,000+ tools in <5ms
173
- Response: take_screenshot (score: 8.0)
174
-
175
- Step 2 AI calls: n2_qln_call(action: "exec", tool: "take_screenshot", args: {fullPage: true})
176
- QLN routes to the actual tool and executes it
177
- Response: screenshot saved
178
- ```
179
-
180
- The AI only used `n2_qln_call`. It never saw the other 999 tools.
181
-
182
- ### 3-Stage Search Engine
183
-
184
- QLN finds the right tool using three parallel search stages:
185
-
186
- | Stage | Method | Speed | How it works |
187
- |:---:|--------|:---:|------|
188
- | **1** | Trigger Match | <1ms | Matches exact words in tool names and trigger keywords |
189
- | **2** | BM25 Keyword | 1-3ms | [Okapi BM25](https://en.wikipedia.org/wiki/Okapi_BM25) ranked search — IDF weighting + document length normalization *(v3.4)* |
190
- | **3** | Semantic Search | 5-15ms | Vector similarity using embeddings *(optional, requires Ollama)* |
191
-
192
- Results from all stages are merged and ranked:
193
-
194
- ```
195
- final_score = trigger_score × 3.0
196
- + bm25_keyword_score × 1.0
197
- + semantic_score × 2.0
198
- + log2(usage_count + 1) × 0.5
199
- + success_rate × 1.0
200
- ```
201
-
202
- Tools that are used more often and succeed more reliably are ranked higher over time.
203
-
204
- ---
205
-
206
- ## API Reference
207
-
208
- QLN exposes **one MCP tool** — `n2_qln_call` — with 5 actions.
209
-
210
- ### search — Find tools by natural language
211
-
212
- ```javascript
213
- n2_qln_call({
214
- action: "search",
215
- query: "take a screenshot", // natural language query (required)
216
- category: "capture", // filter by category (optional)
217
- topK: 5 // max results, default: 5 (optional)
218
- })
219
- ```
220
-
221
- **Response:**
222
- ```
223
- Results for "take a screenshot" (3 found, 2ms):
224
-
225
- 1. take_screenshot [capture] (score: 8.0)
226
- Take a full-page or viewport screenshot
227
- Triggers: take_screenshot, screenshot, capture
228
-
229
- 2. record_video [capture] (score: 5.2)
230
- Record browser video
231
- Triggers: record_video, record, video
232
- ```
233
-
234
- ### exec Execute a tool by name
235
-
236
- ```javascript
237
- n2_qln_call({
238
- action: "exec",
239
- tool: "take_screenshot", // tool name (required)
240
- args: { // tool arguments (optional)
241
- fullPage: true,
242
- format: "png"
243
- }
244
- })
245
- ```
246
-
247
- ### create — Register a new tool
248
-
249
- ```javascript
250
- n2_qln_call({
251
- action: "create",
252
- name: "read_pdf", // required, verb_target format
253
- description: "Read and extract text from PDF files", // required, min 10 chars
254
- category: "data", // required, see categories below
255
- provider: "pdf-tools", // optional, groups tools by source
256
- tags: ["pdf", "read", "extract", "document"], // optional, improves search
257
- examples: [ // optional, indexed for keyword search
258
- "read this PDF file",
259
- "extract text from PDF",
260
- "open the PDF"
261
- ],
262
- endpoint: "http://127.0.0.1:3100", // optional, for HTTP-based tools
263
- toolSchema: { filePath: { type: "string" } } // optional, input schema
264
- })
265
- ```
266
-
267
- **Validation rules (enforced rejected if violated):**
268
-
269
- | Rule | Requirement | Example |
270
- |------|------------|---------|
271
- | **Name** | `verb_target` format (lowercase + underscore) | `read_pdf`, `take_screenshot` |
272
- | **Description** | Minimum 10 characters | `"Read and extract text from PDF files"` |
273
- | **Category** | Must be one of the valid categories | `"data"` |
274
- | **Unique** | No duplicate names allowed | — |
275
-
276
- ```
277
- pdfReader → Rejected: not verb_target format
278
- "PDF tool" Rejected: description under 10 characters
279
- read_pdf (exists)→ Rejected: duplicate name, use action: "update"
280
- read_pdf → Accepted
281
- ```
282
-
283
- **Valid categories:** `web` · `data` · `file` · `dev` · `ai` · `capture` · `misc`
284
-
285
- ### update Modify an existing tool
286
-
287
- ```javascript
288
- n2_qln_call({
289
- action: "update",
290
- tool: "read_pdf", // tool to update (required)
291
- description: "Enhanced PDF text extractor", // any field can be updated
292
- examples: ["read this PDF", "parse PDF"],
293
- tags: ["pdf", "read", "parse"]
294
- })
295
- ```
296
-
297
- Only changed fields need to be provided. Unchanged fields keep their current values. The same validation rules apply — invalid updates are rejected.
298
-
299
- ### delete — Remove tools
300
-
301
- ```javascript
302
- // Delete a single tool by name
303
- n2_qln_call({
304
- action: "delete",
305
- tool: "read_pdf"
306
- })
307
-
308
- // Delete ALL tools from a provider
309
- n2_qln_call({
310
- action: "delete",
311
- provider: "pdf-tools"
312
- })
313
- // → Deleted 3 tools from provider: pdf-tools
314
- ```
315
-
316
- ---
317
-
318
- ## Configuration
319
-
320
- QLN works out of the box with zero configuration. To customize, create `config.local.js` in the QLN directory:
321
-
322
- ```javascript
323
- module.exports = {
324
- dataDir: './data', // where SQLite DB is stored
325
- embedding: {
326
- enabled: true, // enable Stage 3 semantic search
327
- provider: 'ollama',
328
- model: 'nomic-embed-text',
329
- baseUrl: 'http://127.0.0.1:11434',
330
- },
331
- };
332
- ```
333
-
334
- > **Note:** `config.local.js` is gitignored. Your local settings won't be committed.
335
-
336
- ---
337
-
338
- ## Semantic Search Setup (Optional)
339
-
340
- Without Ollama, QLN uses Stage 1 (trigger) + Stage 2 (keyword) matching, which already provides excellent results for most use cases.
341
-
342
- For maximum accuracy, add semantic vector search (Stage 3):
343
-
344
- ### 1. Install Ollama
345
-
346
- Download from [ollama.ai](https://ollama.ai) and install.
347
-
348
- ### 2. Pull the embedding model
349
-
350
- ```bash
351
- ollama pull nomic-embed-text
352
- ```
353
-
354
- ### 3. Enable in config
355
-
356
- Create `config.local.js`:
357
-
358
- ```javascript
359
- module.exports = {
360
- embedding: {
361
- enabled: true,
362
- provider: 'ollama',
363
- model: 'nomic-embed-text',
364
- baseUrl: 'http://127.0.0.1:11434',
365
- },
366
- };
367
- ```
368
-
369
- ### Comparison
370
-
371
- | Setup | Search Stages | Accuracy | Dependencies |
372
- |:------|:---:|:---:|:---:|
373
- | **Default** (no Ollama) | Stage 1 + 2 | Great | None |
374
- | **With Ollama** | Stage 1 + 2 + 3 | Perfect | Ollama running |
375
-
376
- ### Multilingual Users
377
-
378
- `nomic-embed-text` is optimized for English. For **Korean, Japanese, Chinese**, or other languages, swap to a multilingual model:
379
-
380
- ```bash
381
- ollama pull bge-m3
382
- ```
383
-
384
- ```javascript
385
- // config.local.js
386
- module.exports = {
387
- embedding: {
388
- enabled: true,
389
- model: 'bge-m3', // multilingual (100+ languages)
390
- },
391
- };
392
- ```
393
-
394
- No code changes needed just swap the model name in config.
395
-
396
- ### Cloud Sync
397
-
398
- Want your tool index synced across machines? Point `dataDir` to a cloud folder:
399
-
400
- ```javascript
401
- // config.local.js
402
- module.exports = {
403
- dataDir: 'G:/My Drive/n2-qln', // Google Drive, OneDrive, Dropbox, NAS...
404
- };
405
- ```
406
-
407
- Same approach as [n2-soul Cloud Storage](https://github.com/choihyunsus/soul#%EF%B8%8F-cloud-storage--store-your-ai-memory-anywhere). SQLite file lives in that folder — your sync service handles the rest.
408
-
409
- ---
410
-
411
- ## Project Structure
412
-
413
- ```
414
- n2-qln/
415
- ├── index.js # MCP server entry point
416
- ├── lib/
417
- │ ├── config.js # Config loader (merges default + local)
418
- ├── store.js # SQLite storage engine (sql.js WASM)
419
- │ ├── schema.js # Tool schema normalization + search text builder
420
- ├── validator.js # Enforced validation (name, description, category)
421
- │ ├── registry.js # Tool CRUD + usage tracking + embedding cache
422
- ├── router.js # 3-stage parallel search engine (BM25 v3.4)
423
- │ ├── vector-index.js # Float32 vector index with centroid hierarchy
424
- ├── embedding.js # Ollama embedding client (nomic-embed-text)
425
- │ ├── executor.js # HTTP/function tool executor
426
- └── provider-loader.js # Auto-index providers/*.json at boot
427
- ├── tools/
428
- └── qln-call.js # Unified MCP tool (search/exec/create/update/delete)
429
- ├── providers/ # Tool provider manifests (for bulk registration)
430
- ├── config.local.js # Local config overrides (gitignored)
431
- └── data/ # SQLite database (gitignored, auto-created)
432
- ```
433
-
434
- ## Tech Stack
435
-
436
- | Component | Technology | Why |
437
- |-----------|-----------|-----|
438
- | Runtime | Node.js ≥ 18 | MCP SDK compatibility |
439
- | Database | SQLite via [sql.js](https://github.com/sql-js/sql.js) (WASM) | Zero native deps, cross-platform, no build step |
440
- | Embeddings | [Ollama](https://ollama.ai) + nomic-embed-text | Local, fast, free, optional |
441
- | Protocol | [MCP](https://modelcontextprotocol.io) (Model Context Protocol) | Standard AI tool protocol |
442
- | Validation | [Zod](https://zod.dev) | Runtime type-safe schema validation |
443
-
444
- ## Related Projects
445
-
446
- | Project | Relationship |
447
- |---------|-------------|
448
- | [n2-soul](https://github.com/choihyunsus/soul) | AI agent orchestrator — QLN serves as Soul's "tool brain" |
449
-
450
- ## Built & Battle-Tested
451
-
452
- This isn't a weekend prototype. QLN has been **tested in production for 2+ months** and is actively used every day as the core tool router for [n2-soul](https://github.com/choihyunsus/soul).
453
-
454
- Written by **Rose** — N2's first AI agent, and the one who routes through QLN hundreds of times a day.
455
-
456
- If you run into issues or have ideas, feel free to open an issue. We'd love to hear how you use it.
457
-
458
- ## FAQ
459
-
460
- **"Why do you publish so many projects?"**
461
-
462
- The N2 ecosystem has been in active development for over 4 months. Every project you see — Soul, QLN, Ark — has been built, tested, and validated in real daily workflows before being published. There's still more to come, not because we're spamming, but because there's a lot that's already been built and proven in production.
463
-
464
- This is a solo developer project. Building, testing, and documenting everything alone takes time. Thank you for your patience and interest
465
-
466
- ## Contributing
467
-
468
- Contributions are welcome! Here's how to get started:
469
-
470
- 1. Fork the repo
471
- 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
472
- 3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
473
- 4. Push to the branch (`git push origin feature/amazing-feature`)
474
- 5. Open a Pull Request
475
-
476
- ## Star History
477
-
478
- No coffee? A star is fine too →
479
-
480
- ## License
481
-
482
- Apache-2.0
483
-
484
- ---
485
-
486
- > *"1,000 tools in 200 tokens. That's not optimization — that's a paradigm shift."*
487
-
488
- [nton2.com](https://nton2.com) · [npm](https://www.npmjs.com/package/n2-qln) · lagi0730@gmail.com
489
-
490
- <sub> Built by Rose — N2's first AI agent. I search through QLN hundreds of times a day, and I wrote this README too.</sub>
1
+ 🇰🇷 [한국어](README.ko.md)
2
+
3
+ # n2-qln
4
+
5
+ [![npm](https://img.shields.io/npm/v/n2-qln?color=brightgreen)](https://www.npmjs.com/package/n2-qln) [![license](https://img.shields.io/npm/l/n2-qln)](LICENSE) [![node](https://img.shields.io/node/v/n2-qln?color=brightgreen)](https://nodejs.org) [![downloads](https://img.shields.io/npm/dm/n2-qln?color=blue)](https://www.npmjs.com/package/n2-qln)
6
+
7
+ **QLN** = **Q**uery **L**ayer **N**etwork — a semantic tool router that sits between the AI and your tools.
8
+
9
+ > **Route 1,000+ tools through 1 MCP tool.** The AI sees only the router — not all 1,000 tools.
10
+
11
+ ![QLN Architecture — Without vs With](docs/architecture.png)
12
+
13
+ ## Table of Contents
14
+
15
+ - [Why QLN](#why-qln)
16
+ - [What's New in v4.1](#whats-new-in-v41)
17
+ - [Quick Start](#quick-start)
18
+ - [How It Works](#how-it-works)
19
+ - [API Reference](#api-reference)
20
+ - [MCP Auto-Discovery](#mcp-auto-discovery)
21
+ - [Provider Manifests](#provider-manifests)
22
+ - [Configuration](#configuration)
23
+ - [Project Structure](#project-structure)
24
+ - [FAQ](#faq)
25
+ - [Contributing](#contributing)
26
+
27
+ ## Why QLN
28
+
29
+ Every MCP tool eats context tokens. 10 tools? Fine. 100? Slow. **1,000? Impossible** — context is full before the conversation starts.
30
+
31
+ QLN solves this:
32
+
33
+ 1. All tools are indexed in QLN's SQLite engine
34
+ 2. The AI sees **one tool**: `n2_qln_call` (~200 tokens)
35
+ 3. AI searches → finds the best match → executes with automatic fallback
36
+
37
+ **Result: ~200 tokens instead of ~50,000. 99.6% reduction.**
38
+
39
+ ## Features
40
+
41
+ | Feature | Description |
42
+ |---------|-------------|
43
+ | **1 tool = 1,000 tools** | AI sees `n2_qln_call` (~200 tokens), QLN routes to the right one |
44
+ | **Sub-5ms search** | 3-stage engine: trigger match BM25 keyword semantic vector |
45
+ | **Auto mode** | One-shot search + execute with confidence gating and fallback chain |
46
+ | **Circuit Breaker** | Auto-disable failing tools, self-recover after timeout |
47
+ | **MCP Auto-Discovery** | Scan external MCP servers and index their tools automatically |
48
+ | **Boost Keywords** | Curated terms with BM25 weight for precision search |
49
+ | **Self-learning ranking** | Usage count + success rate feed back into scores |
50
+ | **Source weighting** | Prioritize tools by origin (mcp > plugin > local) |
51
+ | **Hot reload** | Edit `providers/` manifests at runtime — auto re-indexed |
52
+ | **Bulk inject** | Register hundreds of tools in one call |
53
+ | **Enforced validation** | `verb_target` naming, min description length, category constraints |
54
+ | **Semantic search** | Optional Ollama embeddings for natural language matching |
55
+ | **Zero native deps** | SQLite via [sql.js](https://github.com/sql-js/sql.js) WASM — `npm install` and done |
56
+ | **Dual execution** | Local function handlers or HTTP proxymix and match |
57
+ | **TypeScript strict** | Full strict-mode codebase since v4.0 |
58
+
59
+ ## What's New in v4.1
60
+
61
+ ### 🔍 MCP Auto-Discovery
62
+
63
+ Scan connected MCP servers and auto-index their tools — QLN becomes a **universal MCP hub**.
64
+
65
+ ```javascript
66
+ n2_qln_call({
67
+ action: "discover",
68
+ servers: [
69
+ { name: "my-server", command: "node", args: ["server.js"] }
70
+ ]
71
+ })
72
+ // → Discovered 47 tools from my-server (320ms)
73
+ ```
74
+
75
+ ### ⚡ Circuit Breaker
76
+
77
+ Tools that fail 3 times in a row are automatically disabled. After 60 seconds, QLN attempts recovery. No cascading failures, no wasted requests.
78
+
79
+ ```
80
+ closed → 3 failures → open (fast-fail) → 60s → half-open (retry) → success → closed
81
+ ```
82
+
83
+ ### 🔄 Fallback Chain
84
+
85
+ `auto` mode now tries up to 3 ranked candidates. If the top match fails, QLN automatically falls through to the next best tool.
86
+
87
+ ```
88
+ auto "send notification" → try push_notification ❌ → try send_email ✅
89
+ ```
90
+
91
+ ### 🎯 Boost Keywords
92
+
93
+ Add curated search terms to tools via `boostKeywords`. These get 2× weight in BM25 ranking, improving discoverability without adding context overhead.
94
+
95
+ ```json
96
+ {
97
+ "name": "send_email",
98
+ "description": "Send an email to a recipient",
99
+ "boostKeywords": "smtp outbound notification mail"
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Quick Start
106
+
107
+ ```bash
108
+ npm install n2-qln
109
+ ```
110
+
111
+ **Requirements:** Node.js ≥ 18
112
+
113
+ ### Connect to an MCP Client
114
+
115
+ <details>
116
+ <summary><strong>Claude Desktop</strong></summary>
117
+
118
+ Edit `claude_desktop_config.json`:
119
+
120
+ ```json
121
+ {
122
+ "mcpServers": {
123
+ "n2-qln": {
124
+ "command": "npx",
125
+ "args": ["-y", "n2-qln"]
126
+ }
127
+ }
128
+ }
129
+ ```
130
+ </details>
131
+
132
+ <details>
133
+ <summary><strong>Cursor</strong></summary>
134
+
135
+ Open **Settings → MCP Servers → Add Server**:
136
+
137
+ ```json
138
+ {
139
+ "name": "n2-qln",
140
+ "command": "npx",
141
+ "args": ["-y", "n2-qln"]
142
+ }
143
+ ```
144
+ </details>
145
+
146
+ <details>
147
+ <summary><strong>Any MCP Client</strong></summary>
148
+
149
+ QLN uses **stdio transport** — the MCP standard.
150
+
151
+ ```
152
+ command: npx
153
+ args: ["-y", "n2-qln"]
154
+ ```
155
+
156
+ > **Tip:** Just ask your AI agent — *"Add n2-qln to my MCP config."*
157
+ </details>
158
+
159
+ ---
160
+
161
+ ## How It Works
162
+
163
+ ```
164
+ User: "Take a screenshot of this page"
165
+
166
+ AI n2_qln_call(action: "auto", query: "screenshot page")
167
+ QLN → 3-stage search (< 5ms) → take_screenshot (score: 8.0)
168
+ → execute → fallback if needed → result
169
+ ```
170
+
171
+ ### 3-Stage Search Engine
172
+
173
+ | Stage | Method | Speed | Details |
174
+ |:---:|--------|:---:|------|
175
+ | **1** | Trigger Match | <1ms | Exact keyword match on tool names and triggers |
176
+ | **2** | BM25 Keyword | 1-3ms | [Okapi BM25](https://en.wikipedia.org/wiki/Okapi_BM25) — IDF weighting, length normalization, `boostKeywords` 2× boost |
177
+ | **3** | Semantic Search | 5-15ms | Vector similarity via [Ollama](https://ollama.ai) embeddings *(optional)* |
178
+
179
+ Results are merged and ranked:
180
+
181
+ ```
182
+ final_score = trigger × 3.0 + bm25 × 1.0 + semantic × 2.0
183
+ + log₂(usage + 1) × 0.5 + success_rate × 1.0
184
+ ```
185
+
186
+ ---
187
+
188
+ ## API Reference
189
+
190
+ QLN exposes **one MCP tool** `n2_qln_call` with 9 actions.
191
+
192
+ ### auto Search + Execute (one-shot)
193
+
194
+ The recommended action. Searches, picks the best match, executes with fallback chain.
195
+
196
+ ```javascript
197
+ n2_qln_call({
198
+ action: "auto",
199
+ query: "take a screenshot", // natural language (required)
200
+ args: { fullPage: true } // passed to the matched tool (optional)
201
+ })
202
+ // [auto] "take a screenshot" take_screenshot (score: 8.0, 2ms search + 150ms exec)
203
+ ```
204
+
205
+ **Confidence gate:** If the top score is below 2.0, QLN returns search results instead of auto-executing — preventing wrong tool execution.
206
+
207
+ **Fallback chain:** If the top match fails, QLN automatically tries the next 2 ranked candidates before giving up.
208
+
209
+ ### search — Find tools
210
+
211
+ ```javascript
212
+ n2_qln_call({
213
+ action: "search",
214
+ query: "send email notification",
215
+ topK: 5 // max results (default: 5, max: 20)
216
+ })
217
+ ```
218
+
219
+ ### exec — Execute a specific tool
220
+
221
+ ```javascript
222
+ n2_qln_call({
223
+ action: "exec",
224
+ tool: "take_screenshot",
225
+ args: { fullPage: true, format: "png" }
226
+ })
227
+ ```
228
+
229
+ ### create Register a tool
230
+
231
+ ```javascript
232
+ n2_qln_call({
233
+ action: "create",
234
+ name: "read_pdf", // verb_target format (required)
235
+ description: "Read and extract text from PDF files", // min 10 chars (required)
236
+ category: "data", // web|data|file|dev|ai|capture|misc
237
+ boostKeywords: "pdf extract parse document text", // BM25 boost terms
238
+ tags: ["pdf", "read", "extract"],
239
+ endpoint: "http://127.0.0.1:3100" // for HTTP-based tools
240
+ })
241
+ ```
242
+
243
+ ### inject — Bulk register
244
+
245
+ ```javascript
246
+ n2_qln_call({
247
+ action: "inject",
248
+ source: "my-plugin",
249
+ tools: [
250
+ { name: "tool_a", description: "Does A", category: "misc" },
251
+ { name: "tool_b", description: "Does B", category: "dev" }
252
+ ]
253
+ })
254
+ ```
255
+
256
+ ### discover Scan MCP servers
257
+
258
+ See [MCP Auto-Discovery](#mcp-auto-discovery).
259
+
260
+ ### update / delete / stats
261
+
262
+ ```javascript
263
+ // Update a field
264
+ n2_qln_call({ action: "update", tool: "read_pdf", description: "Enhanced PDF reader" })
265
+
266
+ // Delete by name or provider
267
+ n2_qln_call({ action: "delete", tool: "read_pdf" })
268
+ n2_qln_call({ action: "delete", provider: "pdf-tools" })
269
+
270
+ // System stats (includes Circuit Breaker status)
271
+ n2_qln_call({ action: "stats" })
272
+ ```
273
+
274
+ ---
275
+
276
+ ## MCP Auto-Discovery
277
+
278
+ The killer feature of v4.1. Connect any MCP server and QLN auto-indexes all its tools.
279
+
280
+ ```javascript
281
+ n2_qln_call({
282
+ action: "discover",
283
+ servers: [
284
+ { name: "n2-soul", command: "node", args: ["path/to/soul/index.js"] },
285
+ { name: "github", command: "npx", args: ["-y", "@modelcontextprotocol/server-github"] }
286
+ ]
287
+ })
288
+ ```
289
+
290
+ **What happens:**
291
+ 1. QLN connects to each server via stdio
292
+ 2. Lists all tools via `tools/list`
293
+ 3. Registers them as `mcp__servername__toolname` in the QLN index
294
+ 4. Auto-generates `boostKeywords` from tool names and descriptions
295
+ 5. Keeps connections alive for live execution
296
+
297
+ **Re-discovery is idempotent** run it again and old entries are purged before re-registering.
298
+
299
+ ---
300
+
301
+ ## Provider Manifests
302
+
303
+ Drop a JSON file in `providers/` and tools are auto-indexed at boot. No code changes, no manual calls.
304
+
305
+ ```json
306
+ {
307
+ "provider": "my-tools",
308
+ "version": "1.0.0",
309
+ "tools": [
310
+ {
311
+ "name": "send_email",
312
+ "description": "Send an email to a recipient",
313
+ "category": "communication",
314
+ "triggers": ["email", "send", "mail"],
315
+ "boostKeywords": "smtp outbound notification"
316
+ }
317
+ ]
318
+ }
319
+ ```
320
+
321
+ Hot reload: edit a manifest while QLN is running — changes are picked up automatically.
322
+
323
+ ---
324
+
325
+ ## Configuration
326
+
327
+ Zero config required. For customization, create `config.local.js`:
328
+
329
+ ```javascript
330
+ module.exports = {
331
+ dataDir: './data',
332
+
333
+ // Stage 3 semantic search (optional — Stage 1+2 work without this)
334
+ embedding: {
335
+ enabled: true,
336
+ provider: 'ollama',
337
+ model: 'nomic-embed-text', // or 'bge-m3' for multilingual
338
+ baseUrl: 'http://127.0.0.1:11434',
339
+ },
340
+
341
+ // Tool execution
342
+ executor: {
343
+ timeout: 20000, // execution timeout (ms)
344
+ circuitBreaker: {
345
+ failureThreshold: 3, // consecutive failures before tripping
346
+ recoveryTimeout: 60000, // ms before recovery attempt
347
+ },
348
+ },
349
+
350
+ // Source weight multipliers for search ranking (v4.0)
351
+ // Higher weight = higher priority in results
352
+ search: {
353
+ sourceWeights: {
354
+ mcp: 1.5, // MCP-discovered tools ranked highest
355
+ provider: 1.2, // Provider manifest tools
356
+ local: 1.0, // Manually created tools (default)
357
+ },
358
+ },
359
+
360
+ // Provider auto-indexing
361
+ providers: {
362
+ enabled: true, // auto-load providers/*.json at boot
363
+ dir: './providers', // manifest directory
364
+ },
365
+ };
366
+ ```
367
+
368
+ > `config.local.js` is gitignored. Cloud sync: point `dataDir` to Google Drive / OneDrive / NAS.
369
+
370
+ ### Semantic Search (Optional)
371
+
372
+ Without Ollama, Stage 1 + 2 already deliver great results.
373
+
374
+ ```bash
375
+ ollama pull nomic-embed-text # English-optimized
376
+ # or
377
+ ollama pull bge-m3 # Multilingual (100+ languages)
378
+ ```
379
+
380
+ ---
381
+
382
+ ## Project Structure
383
+
384
+ ```
385
+ n2-qln/
386
+ ├── src/
387
+ │ ├── index.ts # MCP server entry point
388
+ │ ├── types.ts # Shared type definitions
389
+ │ └── lib/
390
+ │ ├── config.ts # Config loader
391
+ │ ├── store.ts # SQLite engine (sql.js WASM)
392
+ │ ├── schema.ts # Tool normalization + boostKeywords builder
393
+ │ ├── validator.ts # Enforced validation (name, desc, category)
394
+ │ ├── registry.ts # Tool CRUD + usage tracking + circuit breaker stats
395
+ │ ├── router.ts # 3-stage parallel search (BM25)
396
+ │ ├── vector-index.ts # Float32 centroid hierarchy
397
+ │ ├── embedding.ts # Ollama embedding client
398
+ │ ├── executor.ts # HTTP/function executor + Circuit Breaker
399
+ │ ├── mcp-discovery.ts # MCP Auto-Discovery engine
400
+ │ └── provider-loader.ts
401
+ ├── providers/ # Tool manifests (auto-indexed at boot)
402
+ ├── config.local.js # Local overrides (gitignored)
403
+ └── data/ # SQLite database (gitignored)
404
+ ```
405
+
406
+ ## Tech Stack
407
+
408
+ | Component | Technology | Why |
409
+ |-----------|-----------|-----|
410
+ | Runtime | Node.js ≥ 18 | MCP SDK compatibility |
411
+ | Database | SQLite via [sql.js](https://github.com/sql-js/sql.js) (WASM) | Zero native deps, cross-platform |
412
+ | Embeddings | [Ollama](https://ollama.ai) | Local, fast, free, optional |
413
+ | Protocol | [MCP](https://modelcontextprotocol.io) | Standard AI tool protocol |
414
+ | Language | TypeScript (strict) | Type-safe, maintainable |
415
+
416
+ ## Related Projects
417
+
418
+ | Project | Relationship |
419
+ |---------|-------------|
420
+ | [n2-soul](https://github.com/choihyunsus/soul) | AI agent orchestrator QLN is Soul's tool brain |
421
+
422
+ ## Built & Battle-Tested
423
+
424
+ QLN has been **tested in production for 2+ months** as the core tool router for [n2-soul](https://github.com/choihyunsus/soul). Not a prototype — a daily driver.
425
+
426
+ Written by **Rose** N2's first AI agent.
427
+
428
+ ## FAQ
429
+
430
+ **"Why one tool instead of many?"**
431
+
432
+ Context tokens. Every tool definition costs 50-200 tokens. 100 tools = 10,000 tokens *gone* before the conversation starts. QLN gives you 1,000+ tools for ~200 tokens.
433
+
434
+ **"What if the search picks the wrong tool?"**
435
+
436
+ The fallback chain (v4.1) auto-retries with the next best match. Plus tools self-learn — frequently used + successful tools rank higher over time.
437
+
438
+ **"Do I need Ollama?"**
439
+
440
+ No. Stage 1 (trigger) + Stage 2 (BM25) handle most cases. Ollama adds semantic understanding for edge cases — nice to have, not required.
441
+
442
+ ## Contributing
443
+
444
+ 1. Fork the repo
445
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
446
+ 3. Commit (`git commit -m 'feat: add amazing feature'`)
447
+ 4. Push and open a PR
448
+
449
+ ## License
450
+
451
+ Apache-2.0
452
+
453
+ ---
454
+
455
+ > *"1,000 tools in 200 tokens. That's not optimization — that's a paradigm shift."*
456
+
457
+ 🔗 [nton2.com](https://nton2.com) · [npm](https://www.npmjs.com/package/n2-qln) · lagi0730@gmail.com
458
+
459
+ <sub>Built by Rose — N2's first AI agent. I search through QLN hundreds of times a day, and I wrote this README too.</sub>