simple-dynamsoft-mcp 7.1.1 → 7.2.1

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/.env.example CHANGED
@@ -1,115 +1,47 @@
1
- # Runtime profile selection (recommended)
2
- # MCP_PROFILE: lite | semantic-local | semantic-gemini
3
- # - lite: lightweight defaults for broad compatibility (no model download)
4
- # - semantic-local: local embeddings via @xenova/transformers
5
- # - semantic-gemini: remote embeddings via Google Gemini
6
- MCP_PROFILE=lite
1
+ # Minimal configuration
2
+ # - If GEMINI_API_KEY is set: provider=gemini, fallback=lexical, hydration_mode=eager
3
+ # - If GEMINI_API_KEY is unset: provider=lexical, fallback=none, hydration_mode=lazy
7
4
 
8
- # RAG provider selection (optional explicit override)
9
- # RAG_PROVIDER: lexical | gemini | local | fuse
10
- # - lexical: hybrid lexical retrieval (BM25 + Fuse)
11
- # - gemini: remote embeddings via Google Gemini
12
- # - local: local embeddings via @xenova/transformers
13
- # - fuse: compatibility fallback (fuzzy-only, no embeddings)
14
- RAG_PROVIDER=lexical
5
+ # GEMINI_API_KEY=your_key_here
15
6
 
16
- # RAG_FALLBACK: none | lexical | fuse | local
17
- # - none: do not fall back if primary fails
18
- # - lexical: hybrid lexical fallback (recommended for no-model fallback)
19
- # - fuse: fuzzy search fallback (fast, no embeddings)
20
- # - local: local embeddings fallback (if primary is gemini)
21
- RAG_FALLBACK=none
22
-
23
- # Gemini remote embeddings (required when RAG_PROVIDER=gemini)
24
- # * GEMINI_API_KEY: your Gemini API key (never commit to git)
25
- # * GEMINI_EMBED_MODEL: models/embedding-001 | models/gemini-embedding-001 | gemini-embedding-001
26
- # * GEMINI_API_BASE_URL: override for proxies (default https://generativelanguage.googleapis.com)
27
- # * GEMINI_EMBED_BATCH_SIZE: batch size for Gemini embedding requests
28
- # * GEMINI_RETRY_MAX_ATTEMPTS: max retry attempts for retryable Gemini errors
29
- # * GEMINI_RETRY_BASE_DELAY_MS: base delay for exponential backoff
30
- # * GEMINI_RETRY_MAX_DELAY_MS: max delay cap for exponential backoff
31
- # * GEMINI_REQUEST_THROTTLE_MS: fixed delay between Gemini requests
32
- # GEMINI_API_KEY=your_key
33
- # GEMINI_EMBED_MODEL=gemini-embedding-001
34
- # GEMINI_API_BASE_URL=https://generativelanguage.googleapis.com
35
- # GEMINI_EMBED_BATCH_SIZE=16
36
- # GEMINI_RETRY_MAX_ATTEMPTS=5
37
- # GEMINI_RETRY_BASE_DELAY_MS=500
38
- # GEMINI_RETRY_MAX_DELAY_MS=10000
39
- # GEMINI_REQUEST_THROTTLE_MS=0
40
-
41
- # Local embeddings (used when RAG_PROVIDER=local or fallback=local)
42
- # * RAG_LOCAL_MODEL: Hugging Face model id (default Xenova/all-MiniLM-L6-v2)
43
- # * RAG_LOCAL_QUANTIZED: true|false (smaller/faster model download when true)
44
- # RAG_LOCAL_MODEL=Xenova/all-MiniLM-L6-v2
45
- # RAG_LOCAL_QUANTIZED=true
7
+ # Optional: explicit cache directories
8
+ # MCP_DATA_DIR=
9
+ # MCP_DATA_CACHE_DIR=
10
+ # RAG_CACHE_DIR=
11
+ # RAG_MODEL_CACHE_DIR=
46
12
 
47
- # Cache locations
48
- # * RAG_CACHE_DIR: vector index cache (default data/.rag-cache)
49
- # * RAG_MODEL_CACHE_DIR: local model cache (default data/.rag-cache/models)
50
- # RAG_CACHE_DIR=data/.rag-cache
51
- # RAG_MODEL_CACHE_DIR=data/.rag-cache/models
13
+ # Optional: startup behavior
14
+ # MCP_DATA_AUTO_DOWNLOAD=true
15
+ # MCP_DATA_REFRESH_ON_START=false
52
16
 
53
- # Indexing + retrieval tuning
54
- # * RAG_CHUNK_SIZE: max chars per chunk when embedding doc content
55
- # * RAG_CHUNK_OVERLAP: overlap between chunks (helps context continuity)
56
- # * RAG_MAX_CHUNKS_PER_DOC: cap chunks per doc to control index size
57
- # * RAG_MAX_TEXT_CHARS: max chars per embedding input
58
- # * RAG_MIN_SCORE: minimum cosine similarity to keep a hit (0 disables filtering). Default 0.2.
59
- # * RAG_INCLUDE_SCORE: include similarity score in search results (debugging)
60
- # RAG_CHUNK_SIZE=1200
61
- # RAG_CHUNK_OVERLAP=200
62
- # RAG_MAX_CHUNKS_PER_DOC=6
63
- # RAG_MAX_TEXT_CHARS=4000
64
- # RAG_MIN_SCORE=0.2
65
- # RAG_INCLUDE_SCORE=false
17
+ # Optional: force hydration mode override
18
+ # MCP_DATA_HYDRATION_MODE=eager
66
19
 
67
- # Cache/boot controls
68
- # * RAG_REBUILD: true to ignore cache and rebuild on startup/search
69
- # * RAG_PREWARM: true to build the embedding index at startup
70
- # * RAG_PREWARM_BLOCK: true to block startup until prewarm completes
71
- # * RAG_PREBUILT_INDEX_AUTO_DOWNLOAD: auto-download prebuilt index when local or gemini embeddings are selected
72
- # * RAG_PREBUILT_INDEX_URL: global override URL for prebuilt index archive (applies to both local and gemini providers)
73
- # * RAG_PREBUILT_INDEX_URL_LOCAL: provider-specific URL override for local prebuilt index archive
74
- # * RAG_PREBUILT_INDEX_URL_GEMINI: provider-specific URL override for gemini prebuilt index archive
75
- # * RAG_PREBUILT_INDEX_TIMEOUT_MS: timeout for prebuilt index download request
76
- # RAG_REBUILD=false
77
- # RAG_PREWARM=false
78
- # RAG_PREWARM_BLOCK=false
20
+ # Optional: prebuilt Gemini index behavior
79
21
  # RAG_PREBUILT_INDEX_AUTO_DOWNLOAD=true
80
22
  # RAG_PREBUILT_INDEX_URL=
81
- # RAG_PREBUILT_INDEX_URL_LOCAL=
82
23
  # RAG_PREBUILT_INDEX_URL_GEMINI=
83
24
  # RAG_PREBUILT_INDEX_TIMEOUT_MS=180000
84
25
 
85
- # Optional data submodule sync on server startup
86
- # * DATA_SYNC_ON_START: true to fetch + fast-forward configured submodules
87
- # * DATA_SYNC_TIMEOUT_MS: timeout per git operation in milliseconds
88
- # DATA_SYNC_ON_START=false
89
- # DATA_SYNC_TIMEOUT_MS=30000
26
+ # Optional: prewarm behavior
27
+ # RAG_PREWARM=true
28
+ # RAG_PREWARM_BLOCK=false
90
29
 
91
- # Data bootstrap mode (for npm/npx installs without submodules)
92
- # * MCP_DATA_DIR: explicit existing data root (must contain metadata/, samples/, documentation/)
93
- # * MCP_DATA_AUTO_DOWNLOAD: auto-download pinned sample/doc archives when local data is missing
94
- # * MCP_DATA_CACHE_DIR: where downloaded data is stored
95
- # * MCP_DATA_REFRESH_ON_START: force re-download even when cache matches lock manifest
96
- # * MCP_DATA_HYDRATION_MODE: eager (download all repos at startup) | lazy (metadata-first, hydrate repos on demand)
97
- # * MCP_DATA_DOWNLOAD_TIMEOUT_MS: timeout per archive download request in milliseconds
98
- # * MCP_DATA_DOWNLOAD_RETRY_MAX_ATTEMPTS: max retry attempts for retryable archive download failures
99
- # * MCP_DATA_DOWNLOAD_RETRY_BASE_DELAY_MS: exponential backoff base delay in milliseconds
100
- # * MCP_DATA_DOWNLOAD_RETRY_MAX_DELAY_MS: exponential backoff max delay in milliseconds
101
- # MCP_DATA_DIR=
102
- # MCP_DATA_AUTO_DOWNLOAD=true
103
- # MCP_DATA_CACHE_DIR=
104
- # MCP_DATA_REFRESH_ON_START=false
105
- # MCP_DATA_HYDRATION_MODE=lazy
30
+ # Optional: Gemini request tuning
31
+ # GEMINI_EMBED_MODEL=gemini-embedding-001
32
+ # GEMINI_API_BASE_URL=https://generativelanguage.googleapis.com
33
+ # GEMINI_EMBED_BATCH_SIZE=16
34
+ # GEMINI_RETRY_MAX_ATTEMPTS=5
35
+ # GEMINI_RETRY_BASE_DELAY_MS=500
36
+ # GEMINI_RETRY_MAX_DELAY_MS=10000
37
+ # GEMINI_REQUEST_THROTTLE_MS=0
38
+
39
+ # Optional: data download retry tuning
106
40
  # MCP_DATA_DOWNLOAD_TIMEOUT_MS=180000
107
41
  # MCP_DATA_DOWNLOAD_RETRY_MAX_ATTEMPTS=3
108
42
  # MCP_DATA_DOWNLOAD_RETRY_BASE_DELAY_MS=500
109
43
  # MCP_DATA_DOWNLOAD_RETRY_MAX_DELAY_MS=5000
110
44
 
111
- # Observability logs
112
- # * MCP_VERBOSE_LOGS: true to include debug-level diagnostics
113
- # * MCP_LOG_LEVEL: info | debug (debug enables verbose logs)
45
+ # Optional: observability
114
46
  # MCP_VERBOSE_LOGS=false
115
47
  # MCP_LOG_LEVEL=info
package/README.md CHANGED
@@ -14,146 +14,167 @@ Default transport is `stdio`. Native Streamable HTTP is also supported at `/mcp`
14
14
 
15
15
  https://github.com/user-attachments/assets/cc1c5f4b-1461-4462-897a-75abc20d62a6
16
16
 
17
- ## Two Supported Usage Scenarios
17
+ ## Two Core Usage Modes
18
18
 
19
- This project is intentionally documented for two real-world usage paths:
19
+ 1. Remote MCP over HTTP (recommended)
20
+ 2. Local MCP via `npx`
20
21
 
21
- 1. Local usage with `npx -y simple-dynamsoft-mcp@latest` and minimal config
22
- 2. HTTP deployment on Ubuntu with full data + prebuilt indexes + Gemini embeddings
22
+ ### 1) Remote (Recommended)
23
23
 
24
- If you need deep operator/dev settings, see `AGENTS.md` and `.env.example`.
24
+ Use this endpoint directly:
25
25
 
26
- ## Scenario 1: Local Usage (Recommended Default)
26
+ - `https://simple-dynamsoft-mcp.wonderfulwave-69908b91.eastus2.azurecontainerapps.io/mcp`
27
27
 
28
- For most users, this is enough.
29
-
30
- Command:
28
+ ### 2) Local
31
29
 
32
30
  ```bash
33
31
  npx -y simple-dynamsoft-mcp@latest
34
32
  ```
35
33
 
36
- Notes:
37
- - No explicit environment variables are required for the default path.
38
- - Default profile is lightweight (`lite`) and avoids local embedding model downloads.
39
- - If local data is missing, the package can bootstrap pinned data from cache/download sources.
34
+ ## Deployment Guides
40
35
 
41
- ## Scenario 2: Ubuntu HTTP Deployment (Full Data + Gemini)
36
+ - Azure Container Apps runbook: `docs/deployment/azure-container-apps.md`
37
+ - Self-hosting (Ubuntu/any server): `docs/deployment/self-hosting.md`
42
38
 
43
- Use this when you host the server remotely and expose MCP over HTTP.
39
+ ## MCP Client Configuration
44
40
 
45
- ### 1) Prepare host
41
+ Use one of the following client configs. Remote is recommended.
46
42
 
47
- ```bash
48
- sudo apt update
49
- sudo apt install -y git curl
50
- curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
51
- sudo apt install -y nodejs
52
- node -v
53
- npm -v
54
- ```
43
+ ### OpenCode
55
44
 
56
- ### 2) Clone and install
45
+ <details>
46
+ <summary>OpenCode Config</summary>
57
47
 
58
- ```bash
59
- git clone --recurse-submodules https://github.com/yushulx/simple-dynamsoft-mcp.git
60
- cd simple-dynamsoft-mcp
61
- npm ci
62
- ```
48
+ Remote (recommended):
63
49
 
64
- If you did not clone with submodules:
50
+ Location:
51
+ - macOS: `~/.config/opencode/opencode.json`
52
+ - Windows: `%USERPROFILE%\.config\opencode\opencode.json`
65
53
 
66
- ```bash
67
- npm run data:bootstrap
68
- npm run data:sync
54
+ ```json
55
+ {
56
+ "$schema": "https://opencode.ai/config.json",
57
+ "mcp": {
58
+ "dynamsoft": {
59
+ "type": "remote",
60
+ "url": "https://simple-dynamsoft-mcp.wonderfulwave-69908b91.eastus2.azurecontainerapps.io/mcp"
61
+ }
62
+ }
63
+ }
69
64
  ```
70
65
 
71
- ### 3) Configure environment
66
+ Local:
72
67
 
73
- Create `.env` in repo root:
68
+ ```json
69
+ {
70
+ "$schema": "https://opencode.ai/config.json",
71
+ "mcp": {
72
+ "dynamsoft": {
73
+ "type": "local",
74
+ "command": ["npx", "-y", "simple-dynamsoft-mcp@latest"]
75
+ }
76
+ }
77
+ }
78
+ ```
74
79
 
75
- ```dotenv
76
- GEMINI_API_KEY=your_key_here
80
+ </details>
77
81
 
78
- MCP_PROFILE=semantic-gemini
79
- RAG_PROVIDER=gemini
80
- RAG_FALLBACK=lexical
82
+ ### Claude Desktop
81
83
 
82
- MCP_DATA_HYDRATION_MODE=eager
83
- MCP_DATA_AUTO_DOWNLOAD=true
84
- MCP_DATA_REFRESH_ON_START=false
84
+ <details>
85
+ <summary>Claude Desktop Config</summary>
85
86
 
86
- RAG_PREBUILT_INDEX_AUTO_DOWNLOAD=true
87
+ Location:
88
+ - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
89
+ - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
90
+
91
+ Remote (recommended):
87
92
 
88
- MCP_LOG_LEVEL=info
93
+ ```json
94
+ {
95
+ "mcpServers": {
96
+ "dynamsoft": {
97
+ "url": "https://simple-dynamsoft-mcp.wonderfulwave-69908b91.eastus2.azurecontainerapps.io/mcp"
98
+ }
99
+ }
100
+ }
89
101
  ```
90
102
 
91
- ### 4) Start HTTP server
103
+ Local:
92
104
 
93
- ```bash
94
- node src/index.js --transport=http --host=0.0.0.0 --port=3333
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "dynamsoft": {
109
+ "command": "npx",
110
+ "args": ["-y", "simple-dynamsoft-mcp@latest"]
111
+ }
112
+ }
113
+ }
95
114
  ```
96
115
 
97
- Endpoint:
98
- - `http://<server-ip>:3333/mcp`
116
+ </details>
99
117
 
100
- ### 5) Optional: systemd service
118
+ ### VS Code with GitHub Copilot
101
119
 
102
- Example `/etc/systemd/system/simple-dynamsoft-mcp.service`:
120
+ <details>
121
+ <summary>VS Code MCP Config</summary>
103
122
 
104
- ```ini
105
- [Unit]
106
- Description=Simple Dynamsoft MCP Server
107
- After=network.target
123
+ Global location:
124
+ - macOS: `~/Library/Application Support/Code/User/mcp.json`
125
+ - Windows: `%APPDATA%\Code\User\mcp.json`
108
126
 
109
- [Service]
110
- Type=simple
111
- WorkingDirectory=/opt/simple-dynamsoft-mcp
112
- ExecStart=/usr/bin/node /opt/simple-dynamsoft-mcp/src/index.js --transport=http --host=0.0.0.0 --port=3333
113
- EnvironmentFile=/opt/simple-dynamsoft-mcp/.env
114
- Restart=always
115
- RestartSec=3
127
+ Workspace alternative: `.vscode/mcp.json`
128
+
129
+ Remote (recommended):
116
130
 
117
- [Install]
118
- WantedBy=multi-user.target
131
+ ```json
132
+ {
133
+ "servers": {
134
+ "dynamsoft": {
135
+ "url": "https://simple-dynamsoft-mcp.wonderfulwave-69908b91.eastus2.azurecontainerapps.io/mcp"
136
+ }
137
+ }
138
+ }
119
139
  ```
120
140
 
121
- Enable and start:
141
+ Local:
122
142
 
123
- ```bash
124
- sudo systemctl daemon-reload
125
- sudo systemctl enable simple-dynamsoft-mcp
126
- sudo systemctl start simple-dynamsoft-mcp
127
- sudo systemctl status simple-dynamsoft-mcp
143
+ ```json
144
+ {
145
+ "servers": {
146
+ "dynamsoft": {
147
+ "command": "npx",
148
+ "args": ["-y", "simple-dynamsoft-mcp@latest"]
149
+ }
150
+ }
151
+ }
128
152
  ```
129
153
 
130
- ## MCP Client Configuration
154
+ </details>
131
155
 
132
- Use one of the following client configs.
156
+ ### Cursor
133
157
 
134
- ### OpenCode
158
+ <details>
159
+ <summary>Cursor MCP Config</summary>
135
160
 
136
161
  Location:
137
- - macOS: `~/.config/opencode/opencode.json`
138
- - Windows: `%USERPROFILE%\.config\opencode\opencode.json`
162
+ - macOS: `~/.cursor/mcp.json`
163
+ - Windows: `%USERPROFILE%\.cursor\mcp.json`
164
+
165
+ Remote (recommended):
139
166
 
140
167
  ```json
141
168
  {
142
- "$schema": "https://opencode.ai/config.json",
143
- "mcp": {
169
+ "mcpServers": {
144
170
  "dynamsoft": {
145
- "type": "local",
146
- "command": ["npx", "-y", "simple-dynamsoft-mcp@latest"]
171
+ "url": "https://simple-dynamsoft-mcp.wonderfulwave-69908b91.eastus2.azurecontainerapps.io/mcp"
147
172
  }
148
173
  }
149
174
  }
150
175
  ```
151
176
 
152
- ### Claude Desktop
153
-
154
- Location:
155
- - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
156
- - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
177
+ Local:
157
178
 
158
179
  ```json
159
180
  {
@@ -166,15 +187,34 @@ Location:
166
187
  }
167
188
  ```
168
189
 
169
- ### VS Code with GitHub Copilot
190
+ </details>
170
191
 
171
- Global location:
172
- - macOS: `~/Library/Application Support/Code/User/mcp.json`
173
- - Windows: `%APPDATA%\Code\User\mcp.json`
192
+ ### Windsurf
193
+
194
+ <details>
195
+ <summary>Windsurf MCP Config</summary>
196
+
197
+ Location:
198
+ - macOS: `~/.codeium/windsurf/mcp_config.json`
199
+ - Windows: `%USERPROFILE%\.codeium\windsurf\mcp_config.json`
200
+
201
+ Remote (recommended):
174
202
 
175
203
  ```json
176
204
  {
177
- "servers": {
205
+ "mcpServers": {
206
+ "dynamsoft": {
207
+ "url": "https://simple-dynamsoft-mcp.wonderfulwave-69908b91.eastus2.azurecontainerapps.io/mcp"
208
+ }
209
+ }
210
+ }
211
+ ```
212
+
213
+ Local:
214
+
215
+ ```json
216
+ {
217
+ "mcpServers": {
178
218
  "dynamsoft": {
179
219
  "command": "npx",
180
220
  "args": ["-y", "simple-dynamsoft-mcp@latest"]
@@ -183,30 +223,29 @@ Global location:
183
223
  }
184
224
  ```
185
225
 
186
- Workspace alternative: `.vscode/mcp.json`
226
+ </details>
187
227
 
188
- ### Cursor
228
+ ### Cline
229
+
230
+ <details>
231
+ <summary>Cline MCP Config</summary>
189
232
 
190
233
  Location:
191
- - macOS: `~/.cursor/mcp.json`
192
- - Windows: `%USERPROFILE%\.cursor\mcp.json`
234
+ - VS Code settings JSON for Cline MCP integration
235
+
236
+ Remote (recommended):
193
237
 
194
238
  ```json
195
239
  {
196
240
  "mcpServers": {
197
241
  "dynamsoft": {
198
- "command": "npx",
199
- "args": ["-y", "simple-dynamsoft-mcp@latest"]
242
+ "url": "https://simple-dynamsoft-mcp.wonderfulwave-69908b91.eastus2.azurecontainerapps.io/mcp"
200
243
  }
201
244
  }
202
245
  }
203
246
  ```
204
247
 
205
- ### Windsurf
206
-
207
- Location:
208
- - macOS: `~/.codeium/windsurf/mcp_config.json`
209
- - Windows: `%USERPROFILE%\.codeium\windsurf\mcp_config.json`
248
+ Local:
210
249
 
211
250
  ```json
212
251
  {
@@ -219,6 +258,38 @@ Location:
219
258
  }
220
259
  ```
221
260
 
261
+ </details>
262
+
263
+ ### Continue
264
+
265
+ <details>
266
+ <summary>Continue MCP Config</summary>
267
+
268
+ Location:
269
+ - `~/.continue/config.yaml` (or workspace Continue config)
270
+
271
+ Remote (recommended):
272
+
273
+ ```yaml
274
+ mcpServers:
275
+ dynamsoft:
276
+ transport: streamable-http
277
+ url: https://simple-dynamsoft-mcp.wonderfulwave-69908b91.eastus2.azurecontainerapps.io/mcp
278
+ ```
279
+
280
+ Local:
281
+
282
+ ```yaml
283
+ mcpServers:
284
+ dynamsoft:
285
+ command: npx
286
+ args:
287
+ - -y
288
+ - simple-dynamsoft-mcp@latest
289
+ ```
290
+
291
+ </details>
292
+
222
293
  ## Available Tools
223
294
 
224
295
  The server exposes this minimal tool surface:
@@ -230,25 +301,6 @@ The server exposes this minimal tool surface:
230
301
  - `get_quickstart` -- opinionated quickstart: picks a sample by scenario, returns code + install instructions
231
302
  - `get_sample_files` -- get full project files for a known sample (discovered via list_samples or search)
232
303
 
233
- ## Companion: Dynamsoft SDK Skills
234
-
235
- For AI agents that support skills (Claude Code, OpenCode, Codex), install [dynamsoft-sdk-skills](https://github.com/user/dynamsoft-sdk-skills) for guided integration workflows:
236
-
237
- npx dynamsoft-sdk-skills install --all
238
-
239
- - **Skills** provide integration patterns, gotchas, and decision trees (loaded into agent context)
240
- - **MCP Server** provides runtime tools: version resolution, doc search, sample browsing, and retrieval of full sample project files
241
-
242
- Both work independently, but together the skills guide agents to use MCP tools at the right moments.
243
-
244
- ## Quick Troubleshooting
245
-
246
- - If startup says data is incomplete, run `npm run data:bootstrap` and `npm run data:sync` in clone-based deployments.
247
- - For HTTP deployments, check service logs first:
248
- - `journalctl -u simple-dynamsoft-mcp -f`
249
- - For Gemini mode, confirm `GEMINI_API_KEY` is present in service environment.
250
- - Structured startup logs include `[data]`, `[transport]`, and `[rag]` event lines.
251
-
252
304
  ## Advanced Configuration And Operator Docs
253
305
 
254
306
  Advanced settings, CI/runbook details, and maintenance workflows live in:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-dynamsoft-mcp",
3
- "version": "7.1.1",
3
+ "version": "7.2.1",
4
4
  "description": "MCP server for Dynamsoft SDKs - Capture Vision, Barcode Reader (Mobile/Python/Web), Dynamic Web TWAIN, and Document Viewer. Provides documentation, code snippets, and API guidance.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,8 +26,6 @@
26
26
  "test": "npm run test:lite",
27
27
  "test:unit": "node --test test/unit/gemini-retry.test.js test/unit/profile-config.test.js test/unit/lexical-provider.test.js test/unit/hydration-mode.test.js test/unit/hydration-policy.test.js test/unit/repo-map.test.js test/unit/download-utils.test.js test/unit/logging.test.js test/unit/create-server.test.js test/unit/server-helpers.test.js",
28
28
  "test:lite": "npm run test:stdio && npm run test:http && npm run test:package",
29
- "test:fuse": "npm run test:lite",
30
- "test:local": "node --test test/integration/stdio.test.js test/integration/http.test.js",
31
29
  "test:lexical": "node --test test/integration/stdio.test.js test/integration/http.test.js",
32
30
  "test:gemini": "node scripts/run-gemini-tests.mjs",
33
31
  "test:stdio": "node --test test/integration/stdio.test.js",
@@ -13,7 +13,7 @@ function fileHash(path) {
13
13
  }
14
14
 
15
15
  function ensureEnvDefaults() {
16
- process.env.RAG_PROVIDER = process.env.RAG_PROVIDER || "local";
16
+ process.env.RAG_PROVIDER = process.env.RAG_PROVIDER || "gemini";
17
17
  process.env.RAG_FALLBACK = process.env.RAG_FALLBACK || "none";
18
18
  process.env.RAG_PREWARM = process.env.RAG_PREWARM || "true";
19
19
  process.env.RAG_PREWARM_BLOCK = process.env.RAG_PREWARM_BLOCK || "true";
@@ -50,10 +50,10 @@ if (cacheFiles.length === 0) {
50
50
 
51
51
  let indexSignature = "";
52
52
  let indexCacheKey = "";
53
- const localCacheFile = cacheFiles.find((path) => /rag-local-.*\.json$/i.test(path));
54
- if (localCacheFile) {
53
+ const geminiCacheFile = cacheFiles.find((path) => /rag-gemini-.*\.json$/i.test(path));
54
+ if (geminiCacheFile) {
55
55
  try {
56
- const parsed = JSON.parse(readFileSync(localCacheFile, "utf8"));
56
+ const parsed = JSON.parse(readFileSync(geminiCacheFile, "utf8"));
57
57
  indexSignature = String(parsed?.meta?.signature || "");
58
58
  indexCacheKey = String(parsed?.cacheKey || "");
59
59
  } catch {
@@ -65,7 +65,7 @@ const manifest = {
65
65
  packageVersion: pkg.version,
66
66
  generatedAt: new Date().toISOString(),
67
67
  ragProvider: ragConfig.provider,
68
- ragModel: ragConfig.provider === "gemini" ? ragConfig.geminiModel : ragConfig.localModel,
68
+ ragModel: ragConfig.geminiModel,
69
69
  indexSignature,
70
70
  indexCacheKey,
71
71
  cacheDir: toPosixPath(cacheDir),
@@ -8,8 +8,6 @@ const child = spawn(
8
8
  stdio: "inherit",
9
9
  env: {
10
10
  ...process.env,
11
- RUN_FUSE_PROVIDER_TESTS: "false",
12
- RUN_LOCAL_PROVIDER_TESTS: "false",
13
11
  RUN_GEMINI_PROVIDER_TESTS: "true"
14
12
  }
15
13
  }
@@ -22,4 +20,3 @@ child.on("exit", (code, signal) => {
22
20
  }
23
21
  process.exit(code ?? 1);
24
22
  });
25
-
@@ -3,9 +3,23 @@ function normalizeEnvValue(value) {
3
3
  return String(value).trim().toLowerCase();
4
4
  }
5
5
 
6
+ function resolveEffectiveProvider(env = process.env) {
7
+ const explicitProvider = normalizeEnvValue(env.RAG_PROVIDER);
8
+ const hasGeminiKey = normalizeEnvValue(env.GEMINI_API_KEY) !== "";
9
+ if (explicitProvider === "gemini" || explicitProvider === "lexical") {
10
+ return explicitProvider;
11
+ }
12
+ if (explicitProvider === "auto") {
13
+ return hasGeminiKey ? "gemini" : "lexical";
14
+ }
15
+ return hasGeminiKey ? "gemini" : "lexical";
16
+ }
17
+
6
18
  function resolveHydrationMode(env = process.env) {
7
19
  const mode = normalizeEnvValue(env.MCP_DATA_HYDRATION_MODE);
8
- if (!mode) return "lazy";
20
+ if (!mode) {
21
+ return resolveEffectiveProvider(env) === "gemini" ? "eager" : "lazy";
22
+ }
9
23
  if (mode === "lazy" || mode === "eager") return mode;
10
24
  return "eager";
11
25
  }
package/src/rag/config.js CHANGED
@@ -13,8 +13,6 @@ const legacyPrebuiltIndexUrl =
13
13
  `https://github.com/yushulx/simple-dynamsoft-mcp/releases/download/v${pkg.version}/prebuilt-rag-index-${pkg.version}.tar.gz`;
14
14
 
15
15
  const defaultPrebuiltIndexUrls = {
16
- local:
17
- `https://github.com/yushulx/simple-dynamsoft-mcp/releases/download/v${pkg.version}/prebuilt-rag-index-local-${pkg.version}.tar.gz`,
18
16
  gemini:
19
17
  `https://github.com/yushulx/simple-dynamsoft-mcp/releases/download/v${pkg.version}/prebuilt-rag-index-gemini-${pkg.version}.tar.gz`
20
18
  };
@@ -52,6 +50,7 @@ function normalizeGeminiModel(model) {
52
50
  }
53
51
 
54
52
  const profileConfig = resolveProfileConfig(process.env);
53
+ const defaultPrewarm = profileConfig.provider === "gemini";
55
54
 
56
55
  const ragConfig = {
57
56
  profile: profileConfig.profile,
@@ -62,8 +61,6 @@ const ragConfig = {
62
61
  fallback: profileConfig.fallback,
63
62
  cacheDir: readEnvValue("RAG_CACHE_DIR", join(dataRoot, ".rag-cache")),
64
63
  modelCacheDir: readEnvValue("RAG_MODEL_CACHE_DIR", join(dataRoot, ".rag-cache", "models")),
65
- localModel: readEnvValue("RAG_LOCAL_MODEL", "Xenova/all-MiniLM-L6-v2"),
66
- localQuantized: readBoolEnv("RAG_LOCAL_QUANTIZED", true),
67
64
  chunkSize: readIntEnv("RAG_CHUNK_SIZE", 1200),
68
65
  chunkOverlap: readIntEnv("RAG_CHUNK_OVERLAP", 200),
69
66
  maxChunksPerDoc: readIntEnv("RAG_MAX_CHUNKS_PER_DOC", 6),
@@ -71,11 +68,10 @@ const ragConfig = {
71
68
  minScore: readFloatEnv("RAG_MIN_SCORE", 0.2),
72
69
  includeScore: readBoolEnv("RAG_INCLUDE_SCORE", false),
73
70
  rebuild: readBoolEnv("RAG_REBUILD", false),
74
- prewarm: readBoolEnv("RAG_PREWARM", false),
71
+ prewarm: readBoolEnv("RAG_PREWARM", defaultPrewarm),
75
72
  prewarmBlock: readBoolEnv("RAG_PREWARM_BLOCK", false),
76
73
  prebuiltIndexAutoDownload: readBoolEnv("RAG_PREBUILT_INDEX_AUTO_DOWNLOAD", true),
77
74
  prebuiltIndexUrl: readEnvValue("RAG_PREBUILT_INDEX_URL", ""),
78
- prebuiltIndexUrlLocal: readEnvValue("RAG_PREBUILT_INDEX_URL_LOCAL", defaultPrebuiltIndexUrls.local),
79
75
  prebuiltIndexUrlGemini: readEnvValue("RAG_PREBUILT_INDEX_URL_GEMINI", defaultPrebuiltIndexUrls.gemini),
80
76
  prebuiltIndexTimeoutMs: readIntEnv("RAG_PREBUILT_INDEX_TIMEOUT_MS", 180000),
81
77
  geminiApiKey: readEnvValue("GEMINI_API_KEY", ""),
package/src/rag/index.js CHANGED
@@ -69,6 +69,29 @@ const providerOrchestrator = createProviderOrchestrator({
69
69
  vectorCache
70
70
  });
71
71
 
72
+ function classifyGeminiFailureReason(error) {
73
+ const status = Number(error?.status);
74
+ if (status === 401 || status === 403) return "invalid_auth";
75
+ if (status === 400 || status === 404) return "invalid_config";
76
+ const message = String(error?.message || "").toLowerCase();
77
+ if (message.includes("gemini_api_key") || message.includes("api key")) return "missing_api_key";
78
+ if (message.includes("embed model") || message.includes("model")) return "invalid_config";
79
+ return "runtime_error";
80
+ }
81
+
82
+ function logGeminiDegradedOnce({ reason, fallback, error, stage }) {
83
+ const key = `${stage}:${reason}:${fallback}`;
84
+ if (ragLogState.degradedNotices.has(key)) return;
85
+ ragLogState.degradedNotices.add(key);
86
+ logRag("provider_degraded", {
87
+ provider: "gemini",
88
+ fallback,
89
+ reason,
90
+ stage,
91
+ error: error?.message || String(error)
92
+ }, { level: "error" });
93
+ }
94
+
72
95
  function refreshRagIndexes() {
73
96
  providerOrchestrator.refreshProviders();
74
97
  resetRagProviderLogState();
@@ -144,6 +167,13 @@ async function searchResources({ query, product, edition, platform, type, limit
144
167
  fallback: ragConfig.fallback,
145
168
  error: error.message
146
169
  }, { level: "error" });
170
+ if (name === "gemini") {
171
+ const reason = classifyGeminiFailureReason(error);
172
+ const hasFallback = providers.includes("lexical") && providers[0] === "gemini";
173
+ if (hasFallback) {
174
+ logGeminiDegradedOnce({ reason, fallback: "lexical", error, stage: "search" });
175
+ }
176
+ }
147
177
  }
148
178
  }
149
179
 
@@ -183,6 +213,10 @@ async function prewarmRagIndex() {
183
213
  fallback: ragConfig.fallback
184
214
  });
185
215
  } catch (error) {
216
+ if (primary === "gemini" && providers.includes("lexical")) {
217
+ const reason = classifyGeminiFailureReason(error);
218
+ logGeminiDegradedOnce({ reason, fallback: "lexical", error, stage: "prewarm" });
219
+ }
186
220
  logRag("prewarm_failed", {
187
221
  provider: primary,
188
222
  error: error.message
package/src/rag/logger.js CHANGED
@@ -3,7 +3,7 @@ import { logEvent } from "../observability/logging.js";
3
3
  const ragLogState = {
4
4
  config: false,
5
5
  providerChain: false,
6
- localEmbedderInit: false,
6
+ degradedNotices: new Set(),
7
7
  providerReady: new Set(),
8
8
  providerFirstUse: new Set(),
9
9
  fallbackUse: new Set()
@@ -23,7 +23,7 @@ function logRagConfigOnce(ragConfig) {
23
23
  logRag(
24
24
  `config provider=${ragConfig.provider} fallback=${ragConfig.fallback} prewarm=${ragConfig.prewarm} rebuild=${ragConfig.rebuild} ` +
25
25
  `cache_dir=${ragConfig.cacheDir} prebuilt_auto_download=${ragConfig.prebuiltIndexAutoDownload} ` +
26
- `prebuilt_url_override=${ragConfig.prebuiltIndexUrl ? "set" : "empty"} prebuilt_url_local=${ragConfig.prebuiltIndexUrlLocal ? "set" : "empty"} ` +
26
+ `prebuilt_url_override=${ragConfig.prebuiltIndexUrl ? "set" : "empty"} ` +
27
27
  `prebuilt_url_gemini=${ragConfig.prebuiltIndexUrlGemini ? "set" : "empty"} ` +
28
28
  `prebuilt_timeout_ms=${ragConfig.prebuiltIndexTimeoutMs} gemini_retry_max_attempts=${ragConfig.geminiRetryMaxAttempts} ` +
29
29
  `gemini_retry_base_delay_ms=${ragConfig.geminiRetryBaseDelayMs} gemini_retry_max_delay_ms=${ragConfig.geminiRetryMaxDelayMs} ` +
@@ -32,6 +32,7 @@ function logRagConfigOnce(ragConfig) {
32
32
  }
33
33
 
34
34
  function resetRagProviderLogState() {
35
+ ragLogState.degradedNotices.clear();
35
36
  ragLogState.providerReady.clear();
36
37
  ragLogState.providerFirstUse.clear();
37
38
  ragLogState.fallbackUse.clear();
@@ -1,48 +1,50 @@
1
- const PROFILE_DEFAULTS = {
2
- lite: {
3
- provider: "lexical",
4
- fallback: "none"
5
- },
6
- "semantic-local": {
7
- provider: "local",
8
- fallback: "none"
9
- },
10
- "semantic-gemini": {
11
- provider: "gemini",
12
- fallback: "none"
13
- }
14
- };
15
-
16
1
  function normalizeEnvValue(value) {
17
2
  if (value === undefined || value === null) return "";
18
3
  return String(value).trim().toLowerCase();
19
4
  }
20
5
 
21
- function resolveProfileConfig(env = process.env) {
22
- const rawProfile = normalizeEnvValue(env.MCP_PROFILE);
23
- const explicitProvider = normalizeEnvValue(env.RAG_PROVIDER);
24
- const explicitFallback = normalizeEnvValue(env.RAG_FALLBACK);
6
+ function hasGeminiKey(env) {
7
+ return normalizeEnvValue(env?.GEMINI_API_KEY) !== "";
8
+ }
25
9
 
26
- if (rawProfile && !PROFILE_DEFAULTS[rawProfile]) {
27
- throw new Error(
28
- `Invalid MCP_PROFILE "${rawProfile}". Expected one of: ${Object.keys(PROFILE_DEFAULTS).join(", ")}.`
29
- );
10
+ function resolveProvider(env) {
11
+ const explicit = normalizeEnvValue(env?.RAG_PROVIDER);
12
+ if (explicit === "gemini" || explicit === "lexical") {
13
+ return { value: explicit, source: "env" };
30
14
  }
15
+ return {
16
+ value: hasGeminiKey(env) ? "gemini" : "lexical",
17
+ source: "auto"
18
+ };
19
+ }
31
20
 
32
- const profile = rawProfile || "lite";
33
- const defaults = PROFILE_DEFAULTS[profile];
21
+ function resolveFallback(env, provider) {
22
+ const explicit = normalizeEnvValue(env?.RAG_FALLBACK);
23
+ if (explicit === "none" || explicit === "gemini" || explicit === "lexical") {
24
+ return { value: explicit, source: "env" };
25
+ }
26
+ return {
27
+ value: provider === "gemini" ? "lexical" : "none",
28
+ source: "auto"
29
+ };
30
+ }
34
31
 
32
+ function resolveProfileConfig(env = process.env) {
33
+ const provider = resolveProvider(env);
34
+ const fallback = resolveFallback(env, provider.value);
35
35
  return {
36
- profile,
37
- defaults,
38
- provider: explicitProvider || defaults.provider,
39
- fallback: explicitFallback || defaults.fallback,
40
- providerSource: explicitProvider ? "env" : "profile-default",
41
- fallbackSource: explicitFallback ? "env" : "profile-default"
36
+ profile: provider.value === "gemini" ? "semantic-gemini" : "lite",
37
+ defaults: {
38
+ provider: provider.value,
39
+ fallback: fallback.value
40
+ },
41
+ provider: provider.value,
42
+ fallback: fallback.value,
43
+ providerSource: provider.source,
44
+ fallbackSource: fallback.source
42
45
  };
43
46
  }
44
47
 
45
48
  export {
46
- PROFILE_DEFAULTS,
47
49
  resolveProfileConfig
48
50
  };
@@ -1,5 +1,4 @@
1
1
  import { createHash } from "node:crypto";
2
- import { existsSync, mkdirSync } from "node:fs";
3
2
  import { join } from "node:path";
4
3
  import {
5
4
  sleepMs,
@@ -10,16 +9,10 @@ import {
10
9
  executeWithGeminiRetry
11
10
  } from "./gemini-retry.js";
12
11
 
13
- function ensureDirectory(path) {
14
- if (!existsSync(path)) {
15
- mkdirSync(path, { recursive: true });
16
- }
17
- }
18
-
19
12
  function resolveProviderChain(ragConfig) {
20
13
  let primary = ragConfig.provider;
21
14
  if (primary === "auto") {
22
- primary = ragConfig.geminiApiKey ? "gemini" : "local";
15
+ primary = ragConfig.geminiApiKey ? "gemini" : "lexical";
23
16
  }
24
17
  const chain = [primary];
25
18
  if (ragConfig.fallback && ragConfig.fallback !== "none" && ragConfig.fallback !== primary) {
@@ -145,35 +138,8 @@ function createProviderOrchestrator({
145
138
  }) {
146
139
  let fuseSearch = utils.createFuseSearch(resourceIndex);
147
140
  const providerCache = new Map();
148
- let localEmbedderPromise = null;
149
141
  let geminiEmbedderPromise = null;
150
142
 
151
- async function getLocalEmbedder() {
152
- if (localEmbedderPromise) return localEmbedderPromise;
153
- localEmbedderPromise = (async () => {
154
- const { pipeline, env } = await import("@xenova/transformers");
155
- ensureDirectory(ragConfig.modelCacheDir);
156
- if (!ragLogState.localEmbedderInit) {
157
- ragLogState.localEmbedderInit = true;
158
- logRag(
159
- `init local embedder model=${ragConfig.localModel} quantized=${ragConfig.localQuantized} model_cache_dir=${ragConfig.modelCacheDir}`
160
- );
161
- }
162
- env.cacheDir = ragConfig.modelCacheDir;
163
- env.allowLocalModels = true;
164
- const extractor = await pipeline("feature-extraction", ragConfig.localModel, {
165
- quantized: ragConfig.localQuantized
166
- });
167
- return {
168
- embed: async (text) => {
169
- const output = await extractor(text, { pooling: "mean", normalize: true });
170
- return Array.from(output.data);
171
- }
172
- };
173
- })();
174
- return localEmbedderPromise;
175
- }
176
-
177
143
  async function getGeminiEmbedder() {
178
144
  if (!ragConfig.geminiApiKey) {
179
145
  throw new Error("GEMINI_API_KEY is required for gemini embeddings.");
@@ -537,16 +503,6 @@ function createProviderOrchestrator({
537
503
  entryMatchesScope: utils.entryMatchesScope,
538
504
  attachScore: utils.attachScore
539
505
  }));
540
- } else if (name === "local") {
541
- providerPromise = (async () => {
542
- const embedder = await getLocalEmbedder();
543
- return createVectorProvider({
544
- name: "local",
545
- model: ragConfig.localModel,
546
- embedder,
547
- batchSize: 1
548
- });
549
- })();
550
506
  } else if (name === "gemini") {
551
507
  providerPromise = (async () => {
552
508
  const embedder = await getGeminiEmbedder();
@@ -154,9 +154,7 @@ function resolvePrebuiltIndexUrlCandidates(provider, ragConfig, legacyPrebuiltIn
154
154
  if (override) return [override];
155
155
 
156
156
  const candidates = [];
157
- if (provider === "local") {
158
- candidates.push(String(ragConfig.prebuiltIndexUrlLocal || "").trim());
159
- } else if (provider === "gemini") {
157
+ if (provider === "gemini") {
160
158
  candidates.push(String(ragConfig.prebuiltIndexUrlGemini || "").trim());
161
159
  }
162
160
  candidates.push(legacyPrebuiltIndexUrl);
@@ -204,7 +202,7 @@ function createVectorCacheHelpers({ ragConfig, pkgVersion, legacyPrebuiltIndexUr
204
202
  const prebuiltDownloadAttempts = new Map();
205
203
 
206
204
  async function maybeDownloadPrebuiltVectorIndex({ provider, model, cacheKey, signature, cacheFile }) {
207
- if (!["local", "gemini"].includes(provider)) {
205
+ if (provider !== "gemini") {
208
206
  return { downloaded: false, reason: "provider_not_supported" };
209
207
  }
210
208
  if (!ragConfig.prebuiltIndexAutoDownload) {