contextqmd-mcp 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/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +951 -0
- package/dist/lib/config.d.ts +12 -0
- package/dist/lib/config.js +22 -0
- package/dist/lib/doc-indexer.d.ts +116 -0
- package/dist/lib/doc-indexer.js +392 -0
- package/dist/lib/local-cache.d.ts +72 -0
- package/dist/lib/local-cache.js +210 -0
- package/dist/lib/registry-client.d.ts +37 -0
- package/dist/lib/registry-client.js +110 -0
- package/dist/lib/types.d.ts +113 -0
- package/dist/lib/types.js +2 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ContextQMD
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# contextqmd-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for local-first, version-aware library documentation. Install, search, and retrieve docs for any library directly from your AI coding assistant.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
ContextQMD is an alternative to context7 that keeps documentation local. It downloads doc packages from the [ContextQMD registry](https://github.com/darkamenosa/contextqmd-registry), indexes them with QMD (BM25 + vector + LLM reranking), and serves results through the Model Context Protocol.
|
|
8
|
+
|
|
9
|
+
Install flow is bundle-first:
|
|
10
|
+
|
|
11
|
+
1. search the library catalog and choose a library/version
|
|
12
|
+
2. fetch manifest
|
|
13
|
+
3. download a `tar.gz` docs bundle when available
|
|
14
|
+
4. verify SHA256 checksum and unpack into the local cache
|
|
15
|
+
5. index with QMD
|
|
16
|
+
6. search locally
|
|
17
|
+
|
|
18
|
+
If a compatible bundle is missing, the server falls back to `page-index` plus per-page fetches. Installs are atomic with rollback on failure.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g contextqmd-mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or run directly with npx:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx contextqmd-mcp
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## MCP Configuration
|
|
33
|
+
|
|
34
|
+
Add to your MCP client configuration (e.g., Claude Desktop, Cursor, etc.):
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"contextqmd": {
|
|
40
|
+
"command": "npx",
|
|
41
|
+
"args": ["-y", "contextqmd-mcp"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or if installed globally:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"contextqmd": {
|
|
53
|
+
"command": "contextqmd-mcp"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### CLI Options
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
--transport <type> Transport type: stdio (default) or http
|
|
63
|
+
--port <number> HTTP port (default: 3001)
|
|
64
|
+
--registry <url> Registry URL override
|
|
65
|
+
--token <token> API token
|
|
66
|
+
--cache-dir <path> Cache directory override
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Available Tools
|
|
70
|
+
|
|
71
|
+
| Tool | Description |
|
|
72
|
+
|------|-------------|
|
|
73
|
+
| `search_libraries` | Search the remote library catalog. Returns candidates with versions, aliases, source metadata, and local install status. |
|
|
74
|
+
| `install_docs` | Install a documentation package. Bundle-first with SHA256 verification; falls back to page API. Atomic with rollback. Idempotent — skips if already installed with same manifest checksum. |
|
|
75
|
+
| `update_docs` | Update installed docs to latest version or refresh when manifest checksum changes. Rolls back on failure. |
|
|
76
|
+
| `search_docs` | Search installed documentation locally. Returns page-level results with snippets, line anchors, and scores. Supports auto/fts/vector/hybrid modes. |
|
|
77
|
+
| `get_doc` | Read a bounded slice from a locally installed page by `doc_path` or `page_uid`. Supports sequential reads (`from_line`/`max_lines`) and context windows (`around_line`/`before`/`after`). |
|
|
78
|
+
| `list_installed_docs` | List all locally installed documentation packages with metadata. |
|
|
79
|
+
| `remove_docs` | Remove an installed documentation version or all versions for a library. Cleans up both cache and search index. |
|
|
80
|
+
|
|
81
|
+
## Search Modes
|
|
82
|
+
|
|
83
|
+
`search_docs` supports four modes via the `mode` parameter:
|
|
84
|
+
|
|
85
|
+
- **auto** (default) — Smart routing based on query classification: short keyword queries use FTS, conceptual/how-to questions use vector, complex multi-aspect queries use hybrid.
|
|
86
|
+
- **fts** — BM25 full-text search (fast, keyword-based). Best for API names, function lookups, and code patterns.
|
|
87
|
+
- **vector** — Semantic vector search. Best for conceptual questions. Falls back to FTS on timeout.
|
|
88
|
+
- **hybrid** — Combined BM25 + vector with LLM reranking (best quality, slower). Falls back to FTS on timeout.
|
|
89
|
+
|
|
90
|
+
Cross-library searches always use FTS regardless of mode.
|
|
91
|
+
|
|
92
|
+
## Progressive Retrieval
|
|
93
|
+
|
|
94
|
+
The server uses a progressive retrieval model — search returns small snippets with line anchors, then `get_doc` allows bounded expansion:
|
|
95
|
+
|
|
96
|
+
1. Discover candidate libraries:
|
|
97
|
+
`search_libraries({ query: "react refs" })`
|
|
98
|
+
2. Install the exact docs package:
|
|
99
|
+
`install_docs({ library: "facebook/react", version: "19.2.0" })`
|
|
100
|
+
3. Search the local index:
|
|
101
|
+
`search_docs({ query: "how can i optimize refs", library: "facebook/react", version: "19.2.0" })`
|
|
102
|
+
4. Read a bounded excerpt from the best result:
|
|
103
|
+
`get_doc({ library: "facebook/react", version: "19.2.0", doc_path: "reference/react/useRef.md", from_line: 40, max_lines: 30 })`
|
|
104
|
+
|
|
105
|
+
### search_docs results
|
|
106
|
+
|
|
107
|
+
Each result includes: `doc_path`, `page_uid`, `title`, `content_md`, `score`, `snippet`, `line_start`, `line_end`, `search_mode`, and `url`.
|
|
108
|
+
|
|
109
|
+
`search_docs` is local-only. If the library is not installed, it returns a `NOT_INSTALLED` error instead of silently fetching from the network.
|
|
110
|
+
|
|
111
|
+
### get_doc reading modes
|
|
112
|
+
|
|
113
|
+
- **Sequential**: `from_line` + `max_lines` (default: line 1, 60 lines)
|
|
114
|
+
- **Context window**: `around_line` + `before`/`after` (default: 30 before, 60 after)
|
|
115
|
+
- **Line numbers**: set `line_numbers: true` to get line-number-prefixed output
|
|
116
|
+
|
|
117
|
+
## Upgrade Note
|
|
118
|
+
|
|
119
|
+
Older installed libraries may still have legacy `page_uid.md` paths in the local QMD index. The server lazily rebuilds those indexes on first search. If the rebuild is interrupted, rerun `update_docs` or reinstall the affected library version.
|
|
120
|
+
|
|
121
|
+
## Configuration
|
|
122
|
+
|
|
123
|
+
Config file location: `~/.config/contextqmd/config.json`
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"registry_url": "https://contextqmd.com",
|
|
128
|
+
"local_cache_dir": "~/.cache/contextqmd",
|
|
129
|
+
"default_install_mode": "slim",
|
|
130
|
+
"preferred_search_mode": "auto"
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Environment variables:
|
|
135
|
+
- `CONTEXTQMD_API_TOKEN` — API token for authenticated endpoints
|
|
136
|
+
|
|
137
|
+
## Architecture
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
src/
|
|
141
|
+
index.ts # CLI entry point, MCP server, 7 tool handlers
|
|
142
|
+
lib/
|
|
143
|
+
types.ts # TypeScript interfaces for the API contract
|
|
144
|
+
config.ts # Config loader (~/.config/contextqmd/config.json)
|
|
145
|
+
registry-client.ts # HTTP client for the ContextQMD registry API
|
|
146
|
+
local-cache.ts # Local filesystem cache manager (atomic installs, page layout)
|
|
147
|
+
doc-indexer.ts # QMD-backed search indexer (FTS, vector, hybrid, query classifier)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Key design patterns:
|
|
151
|
+
- **Local-first**: All search is local-only. `search_docs` never touches the network.
|
|
152
|
+
- **Bundle-first installs**: Prefers `tar.gz` bundles; falls back to page-by-page API fetches.
|
|
153
|
+
- **Atomic installs**: Staged temp directories with backup/restore for safe upgrades.
|
|
154
|
+
- **Idempotent operations**: `install_docs` is a no-op when the same version/checksum is already installed.
|
|
155
|
+
- **Security**: Bundle extraction validates against path traversal, symlinks, and unsupported entry types.
|
|
156
|
+
|
|
157
|
+
## Development
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm install # install dependencies
|
|
161
|
+
npm run build # compile TypeScript to dist/
|
|
162
|
+
npm run dev # watch mode
|
|
163
|
+
npm run check # type-check without emitting
|
|
164
|
+
npm test # run tests (vitest)
|
|
165
|
+
npm run test:watch # watch mode tests
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Set `SKIP_INTEGRATION=1` to skip integration tests that require a running registry at localhost:3000.
|
|
169
|
+
|
|
170
|
+
## Requirements
|
|
171
|
+
|
|
172
|
+
- Node.js >= 22.0.0
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { RegistryClient } from "./lib/registry-client.js";
|
|
4
|
+
import { LocalCache } from "./lib/local-cache.js";
|
|
5
|
+
import { DocIndexer, type SearchMode } from "./lib/doc-indexer.js";
|
|
6
|
+
export interface ServerDeps {
|
|
7
|
+
registryClient: RegistryClient;
|
|
8
|
+
cache: LocalCache;
|
|
9
|
+
indexer: DocIndexer;
|
|
10
|
+
}
|
|
11
|
+
type ToolResult = {
|
|
12
|
+
content: Array<{
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}>;
|
|
16
|
+
structuredContent?: Record<string, unknown>;
|
|
17
|
+
isError?: boolean;
|
|
18
|
+
};
|
|
19
|
+
type SearchDocsInput = {
|
|
20
|
+
query: string;
|
|
21
|
+
library?: string;
|
|
22
|
+
version?: string;
|
|
23
|
+
max_results?: number;
|
|
24
|
+
mode?: SearchMode;
|
|
25
|
+
};
|
|
26
|
+
type SearchLibrariesInput = {
|
|
27
|
+
query: string;
|
|
28
|
+
limit?: number;
|
|
29
|
+
};
|
|
30
|
+
type InstallDocsInput = {
|
|
31
|
+
library: string;
|
|
32
|
+
version?: string;
|
|
33
|
+
};
|
|
34
|
+
type GetDocInput = {
|
|
35
|
+
library: string;
|
|
36
|
+
version: string;
|
|
37
|
+
doc_path?: string;
|
|
38
|
+
page_uid?: string;
|
|
39
|
+
from_line?: number;
|
|
40
|
+
max_lines?: number;
|
|
41
|
+
around_line?: number;
|
|
42
|
+
before?: number;
|
|
43
|
+
after?: number;
|
|
44
|
+
line_numbers?: boolean;
|
|
45
|
+
};
|
|
46
|
+
export declare function handleSearchLibraries(deps: ServerDeps, input: SearchLibrariesInput): Promise<ToolResult>;
|
|
47
|
+
export declare function handleInstallDocs(deps: ServerDeps, input: InstallDocsInput): Promise<ToolResult>;
|
|
48
|
+
export declare function handleSearchDocs(deps: ServerDeps, input: SearchDocsInput): Promise<ToolResult>;
|
|
49
|
+
export declare function handleGetDoc(deps: ServerDeps, input: GetDocInput): Promise<ToolResult>;
|
|
50
|
+
export declare function handleListInstalledDocs(deps: ServerDeps): ToolResult;
|
|
51
|
+
export declare function handleUpdateDocs(deps: ServerDeps, input: {
|
|
52
|
+
library?: string;
|
|
53
|
+
}): Promise<ToolResult>;
|
|
54
|
+
export declare function handleRemoveDocs(deps: ServerDeps, input: {
|
|
55
|
+
library: string;
|
|
56
|
+
version?: string;
|
|
57
|
+
}): Promise<ToolResult>;
|
|
58
|
+
declare function createServer(deps: ServerDeps): McpServer;
|
|
59
|
+
export { createServer };
|
|
60
|
+
export declare function isCliEntrypoint(argvPath?: string, moduleUrl?: string): boolean;
|