mcp-vision-web-bridge 0.2.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/.env.example ADDED
@@ -0,0 +1,18 @@
1
+ # OpenAI-compatible model endpoint
2
+ MODEL_BASE_URL=https://api.example.com/v1
3
+ MODEL_API_KEY=replace-with-your-own-key
4
+ MODEL_NAME=replace-with-your-vision-model
5
+
6
+ # Optional upload directories.
7
+ # macOS/Linux default delimiter: :
8
+ # Windows default delimiter: ;
9
+ # Defaults are best-effort Claude Desktop / Cowork local pending upload directories for macOS, Windows, and Linux.
10
+ # CLAUDE_UPLOAD_DIRS=~/Library/Application Support/Claude-3p/pending-uploads:~/Library/Application Support/Claude/pending-uploads
11
+ # CLAUDE_UPLOAD_DIRS_DELIMITER=:
12
+
13
+ # Safer defaults.
14
+ ALLOW_LOCAL_IMAGE_PATHS=false
15
+ ALLOW_CLIPBOARD_IMAGES=false
16
+ ALLOW_PRIVATE_NETWORK_URLS=false
17
+ USE_JINA_READER=false
18
+ MAX_IMAGE_BYTES=10485760
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ - Add MCP prompts for latest uploaded images and clipboard images.
6
+ - Return a non-sensitive image source label with image recognition results.
7
+ - Block private-network image URLs by default.
8
+ - Replace local endpoint defaults with a placeholder API endpoint.
9
+ - Improve provider error messages and redact bearer tokens.
10
+ - Add release-time secret scanning and `prepack` checks.
11
+
12
+ ## 0.1.0
13
+
14
+ - Initial MCP server with image reading and web link reading tools.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
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,201 @@
1
+ # mcp-vision-web-bridge
2
+
3
+ Local MCP server that gives Claude Desktop, Claude Code, and other MCP clients two practical bridge tools:
4
+
5
+ - read an image from a recent Claude upload, clipboard, local path, URL, or base64 input, then send it to an OpenAI-compatible vision model;
6
+ - read web links locally, extract readable text, then send the result to an OpenAI-compatible model.
7
+
8
+ It does not include a model or any model credits. You bring your own OpenAI-compatible API endpoint and API key.
9
+
10
+ ## Use Cases
11
+
12
+ - Your Claude client can accept images, but the third-party model behind it does not reliably receive image content.
13
+ - Your model provider supports vision, but your MCP client needs a local tool to collect image inputs.
14
+ - You want a safer local web reader with private-network blocking enabled by default.
15
+
16
+ ## Capabilities
17
+
18
+ | Capability | macOS | Windows | Linux |
19
+ | --- | --- | --- | --- |
20
+ | MCP server | Supported | Supported | Supported |
21
+ | OpenAI-compatible chat completions | Supported | Supported | Supported |
22
+ | Web page reading | Supported | Supported | Supported |
23
+ | Recent Claude upload image | Supported | Best effort | Best effort |
24
+ | Clipboard image | Supported | Supported via PowerShell / Windows Forms | Supported via `wl-paste` or `xclip` |
25
+
26
+ Recent Claude upload paths are client implementation details, not a stable public API. If auto-detection does not work in your environment, set `CLAUDE_UPLOAD_DIRS` manually.
27
+
28
+ ## Security Defaults
29
+
30
+ The default configuration is intentionally conservative:
31
+
32
+ - `.env` is ignored and should never be committed.
33
+ - API keys are only read from environment variables.
34
+ - Explicit local image paths are disabled unless `ALLOW_LOCAL_IMAGE_PATHS=true`.
35
+ - Clipboard image reading is disabled unless `ALLOW_CLIPBOARD_IMAGES=true`.
36
+ - Private-network web and image URLs are disabled unless `ALLOW_PRIVATE_NETWORK_URLS=true`.
37
+ - Jina Reader fallback is disabled unless `USE_JINA_READER=true`.
38
+ - The server does not log prompts, image data, API keys, or fetched page bodies.
39
+
40
+ ## Requirements
41
+
42
+ - Node.js 20 or newer
43
+ - npm
44
+
45
+ This is a Node.js project. It does not require Python or a virtual environment.
46
+
47
+ ## Install
48
+
49
+ ```bash
50
+ npm install
51
+ cp .env.example .env
52
+ ```
53
+
54
+ Edit `.env`:
55
+
56
+ ```bash
57
+ MODEL_BASE_URL=https://api.example.com/v1
58
+ MODEL_API_KEY=replace-with-your-own-key
59
+ MODEL_NAME=replace-with-your-vision-model
60
+
61
+ ALLOW_LOCAL_IMAGE_PATHS=false
62
+ ALLOW_CLIPBOARD_IMAGES=false
63
+ ALLOW_PRIVATE_NETWORK_URLS=false
64
+ USE_JINA_READER=false
65
+ MAX_IMAGE_BYTES=10485760
66
+ ```
67
+
68
+ `MODEL_BASE_URL` must be an OpenAI-compatible `/v1` endpoint.
69
+
70
+ ### SiliconFlow Example
71
+
72
+ ```bash
73
+ MODEL_BASE_URL=https://api.siliconflow.cn/v1
74
+ MODEL_API_KEY=replace-with-your-own-key
75
+ MODEL_NAME=Qwen/Qwen3-VL-8B-Instruct
76
+ ```
77
+
78
+ Use a model that supports vision input.
79
+
80
+ ## Claude Desktop Config
81
+
82
+ Use absolute paths for your local checkout:
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "vision-web-bridge": {
88
+ "command": "node",
89
+ "args": [
90
+ "--env-file-if-exists=/absolute/path/to/mcp-vision-web-bridge/.env",
91
+ "/absolute/path/to/mcp-vision-web-bridge/src/server.mjs"
92
+ ]
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ Restart Claude Desktop after changing the config.
99
+
100
+ ## Claude Code Config
101
+
102
+ Add the same server to your Claude Code MCP config. After restart, check `/mcp` and confirm that `vision-web-bridge` is connected.
103
+
104
+ The server exposes two tools:
105
+
106
+ - `read_image_with_model`
107
+ - `read_links_with_model`
108
+
109
+ It also exposes two MCP prompts:
110
+
111
+ - `/mcp__vision-web-bridge__img`
112
+ - `/mcp__vision-web-bridge__clipboard-image`
113
+
114
+ ## Usage
115
+
116
+ Read the latest image uploaded to the Claude client:
117
+
118
+ ```text
119
+ Use read_image_with_model with use_latest_upload=true.
120
+ ```
121
+
122
+ Read the current clipboard image:
123
+
124
+ ```text
125
+ Use read_image_with_model with use_clipboard=true and use_latest_upload=false.
126
+ ```
127
+
128
+ Read a local image path after enabling `ALLOW_LOCAL_IMAGE_PATHS=true`:
129
+
130
+ ```text
131
+ Use read_image_with_model with image_path="/absolute/path/to/image.png".
132
+ ```
133
+
134
+ Read web links:
135
+
136
+ ```text
137
+ Use read_links_with_model to summarize https://example.com/article
138
+ ```
139
+
140
+ ## Tool Details
141
+
142
+ ### `read_image_with_model`
143
+
144
+ Supported image sources:
145
+
146
+ - latest Claude upload;
147
+ - public image URL;
148
+ - base64 image;
149
+ - data URL;
150
+ - local image path, opt-in only;
151
+ - clipboard image, opt-in only.
152
+
153
+ The tool returns the model response and a non-sensitive source label such as `latest uploaded image`, `clipboard image`, or `local image path`.
154
+
155
+ ### `read_links_with_model`
156
+
157
+ The tool extracts URLs from the user input, fetches readable page content locally, and asks the configured model to summarize or answer questions.
158
+
159
+ Private-network URLs are blocked by default. Optional Jina Reader fallback can be enabled with `USE_JINA_READER=true`, which sends the URL to Jina Reader.
160
+
161
+ ## Environment Variables
162
+
163
+ | Variable | Default | Description |
164
+ | --- | --- | --- |
165
+ | `MODEL_BASE_URL` | `https://api.example.com/v1` | OpenAI-compatible `/v1` endpoint |
166
+ | `OPENAI_BASE_URL` | unset | Fallback base URL if `MODEL_BASE_URL` is not set |
167
+ | `MODEL_API_KEY` | unset | API key for the model provider |
168
+ | `MODEL_NAME` | `replace-with-your-vision-model` | Chat or vision model name |
169
+ | `CLAUDE_UPLOAD_DIRS` | client-specific defaults | Override upload directories |
170
+ | `CLAUDE_UPLOAD_DIRS_DELIMITER` | platform default | Directory list delimiter |
171
+ | `ALLOW_LOCAL_IMAGE_PATHS` | `false` | Allow explicit local image paths |
172
+ | `ALLOW_CLIPBOARD_IMAGES` | `false` | Allow reading image data from clipboard |
173
+ | `ALLOW_PRIVATE_NETWORK_URLS` | `false` | Allow private-network web and image URLs |
174
+ | `USE_JINA_READER` | `false` | Allow Jina Reader fallback |
175
+ | `MAX_IMAGE_BYTES` | `10485760` | Maximum image size in bytes |
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ npm test
181
+ npm run check:secrets
182
+ ```
183
+
184
+ Before publishing, run:
185
+
186
+ ```bash
187
+ npm pack --dry-run
188
+ ```
189
+
190
+ Check the file list carefully. `.env`, logs, images, local screenshots, and personal paths must not be included.
191
+
192
+ ## Windows Notes
193
+
194
+ - Use full absolute paths in `claude_desktop_config.json`.
195
+ - Save JSON config as UTF-8 without BOM.
196
+ - Restart Claude from the system tray after editing config.
197
+ - Clipboard image reading uses PowerShell / Windows Forms.
198
+
199
+ ## License
200
+
201
+ MIT
@@ -0,0 +1,37 @@
1
+ # Security Review Checklist
2
+
3
+ Use this checklist before publishing or tagging a release.
4
+
5
+ ## Required Checks
6
+
7
+ - `.env` is present only locally and is not tracked.
8
+ - No real API keys, cookies, tokens, credentials, account IDs, or session data are present.
9
+ - No local screenshots, image caches, logs, downloads, transcripts, or personal drafts are present.
10
+ - No personal filesystem paths are present.
11
+ - No machine-specific network addresses are present in docs, examples, package metadata, or release notes.
12
+ - Local image paths remain disabled by default.
13
+ - Clipboard image reading remains disabled by default.
14
+ - Private-network web and image URLs remain disabled by default.
15
+ - Third-party reader fallback remains disabled by default.
16
+ - Tool errors do not echo bearer tokens or configured API keys.
17
+
18
+ ## Current Security Boundaries
19
+
20
+ - `read_image_with_model` requires exactly one image source.
21
+ - `image_path` requires `ALLOW_LOCAL_IMAGE_PATHS=true`.
22
+ - `use_clipboard` requires `ALLOW_CLIPBOARD_IMAGES=true`.
23
+ - `image_url` blocks private-network hosts unless explicitly enabled.
24
+ - `read_links_with_model` blocks private-network hosts unless explicitly enabled.
25
+ - Jina Reader fallback requires `USE_JINA_READER=true`.
26
+ - The tool returns a non-sensitive image source label, not local file paths.
27
+ - The server does not intentionally log prompts, image content, API keys, or fetched web content.
28
+
29
+ ## Release Commands
30
+
31
+ ```bash
32
+ npm test
33
+ npm run check:secrets
34
+ npm pack --dry-run
35
+ ```
36
+
37
+ Manually inspect the `npm pack --dry-run` file list before publishing.
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "mcp-vision-web-bridge",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "description": "A local MCP server that adds image understanding and web-page reading through an OpenAI-compatible model API.",
6
+ "files": [
7
+ "src",
8
+ "README.md",
9
+ "CHANGELOG.md",
10
+ "SECURITY_REVIEW.md",
11
+ "LICENSE",
12
+ ".env.example",
13
+ "scripts"
14
+ ],
15
+ "bin": {
16
+ "mcp-vision-web-bridge": "src/server.mjs"
17
+ },
18
+ "scripts": {
19
+ "start": "node --env-file-if-exists=.env src/server.mjs",
20
+ "test": "node --test",
21
+ "check:secrets": "node scripts/check-secrets.mjs",
22
+ "prepack": "npm test && npm run check:secrets"
23
+ },
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "dependencies": {
28
+ "@modelcontextprotocol/sdk": "^1.29.0",
29
+ "zod": "^4.4.1"
30
+ },
31
+ "devDependencies": {}
32
+ }
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ import { readdir, readFile, stat } from 'node:fs/promises';
3
+ import { join, relative } from 'node:path';
4
+
5
+ const ROOT = process.cwd();
6
+ const EXCLUDED_DIRS = new Set(['.git', '.claude', 'node_modules', 'coverage', 'dist']);
7
+ const EXCLUDED_FILES = new Set(['.env', 'package-lock.json', 'scripts/check-secrets.mjs']);
8
+ const SECRET_PATTERNS = [
9
+ ['OpenAI-style API key', /sk-[A-Za-z0-9_-]{16,}/],
10
+ ['Slack token', /xox[baprs]-/],
11
+ ['GitHub token', /gh[pousr]_[A-Za-z0-9_]{20,}/],
12
+ ['AWS access key', /AKIA[0-9A-Z]{16}/],
13
+ ['Bearer token', /Bearer\s+[A-Za-z0-9._~+/=-]{16,}/],
14
+ ['personal absolute path', /\/Users\/[^\s"'`]+/]
15
+ ];
16
+
17
+ const findings = [];
18
+
19
+ await scanDir(ROOT);
20
+
21
+ if (findings.length) {
22
+ for (const finding of findings) {
23
+ console.error(`${finding.file}:${finding.line}: ${finding.kind}`);
24
+ }
25
+ process.exitCode = 1;
26
+ }
27
+
28
+ async function scanDir(dir) {
29
+ const entries = await readdir(dir, {
30
+ withFileTypes: true
31
+ });
32
+
33
+ for (const entry of entries) {
34
+ const fullPath = join(dir, entry.name);
35
+ const relPath = relative(ROOT, fullPath) || entry.name;
36
+
37
+ if (entry.isDirectory()) {
38
+ if (EXCLUDED_DIRS.has(entry.name)) continue;
39
+ await scanDir(fullPath);
40
+ continue;
41
+ }
42
+
43
+ if (!entry.isFile()) continue;
44
+ if (EXCLUDED_FILES.has(relPath) || relPath.endsWith('.tgz')) continue;
45
+
46
+ await scanFile(fullPath, relPath);
47
+ }
48
+ }
49
+
50
+ async function scanFile(fullPath, relPath) {
51
+ const info = await stat(fullPath);
52
+ if (info.size > 2 * 1024 * 1024) return;
53
+
54
+ let text;
55
+ try {
56
+ text = await readFile(fullPath, 'utf8');
57
+ } catch {
58
+ return;
59
+ }
60
+
61
+ const lines = text.split(/\r?\n/);
62
+ for (const [index, line] of lines.entries()) {
63
+ for (const [kind, pattern] of SECRET_PATTERNS) {
64
+ if (pattern.test(line)) {
65
+ findings.push({
66
+ file: relPath,
67
+ line: index + 1,
68
+ kind
69
+ });
70
+ }
71
+ }
72
+ }
73
+ }