nvidia-vision-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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/package.json +25 -0
  4. package/src/server.js +181 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 M Jupri Amin
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,103 @@
1
+ # NVIDIA Vision MCP
2
+
3
+ A small MCP server for reading local images with NVIDIA vision models.
4
+
5
+ This is useful when the AI model you are using cannot see images directly. A common case is browser debugging: Chrome DevTools can capture a screenshot, but the model still cannot inspect what is inside the image. This server gives the model a simple way to read that screenshot.
6
+
7
+ ## What It Does
8
+
9
+ - Describes local images and screenshots
10
+ - Extracts visible text from images
11
+ - Answers specific questions about an image
12
+ - Deletes temporary screenshot files after use
13
+
14
+ ## Setup
15
+
16
+ Add the server to your MCP client config:
17
+
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "nvidia-vision": {
22
+ "command": "npx",
23
+ "args": ["-y", "nvidia-vision-mcp"],
24
+ "env": {
25
+ "NVIDIA_MODEL": "meta/llama-4-maverick-17b-128e-instruct",
26
+ "NVIDIA_API_KEY": "your_nvidia_api_key"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ The API key is read from the MCP server environment. No `.env` file is needed.
34
+
35
+ `NVIDIA_MODEL` is optional. If it is not set, the server uses:
36
+
37
+ ```text
38
+ meta/llama-4-maverick-17b-128e-instruct
39
+ ```
40
+
41
+ You can replace it with another NVIDIA-hosted vision-capable chat model when needed.
42
+
43
+ For local development from this folder:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "nvidia-vision": {
49
+ "command": "node",
50
+ "args": ["/path/to/nvidia-vision/src/server.js"],
51
+ "env": {
52
+ "NVIDIA_MODEL": "meta/llama-4-maverick-17b-128e-instruct",
53
+ "NVIDIA_API_KEY": "your_nvidia_api_key"
54
+ }
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## Tools
61
+
62
+ `describe_image`
63
+
64
+ Describes what is visible in a local image.
65
+
66
+ `extract_text_from_image`
67
+
68
+ Extracts text from an image or screenshot. Useful for UI errors, terminal output, form labels, dialogs, and short documents.
69
+
70
+ `analyze_image`
71
+
72
+ Answers a custom question about an image. For example, you can ask where a button is, what color an element uses, or whether an error message is visible.
73
+
74
+ `delete_file`
75
+
76
+ Deletes a local file. This is mostly for cleaning up temporary screenshots.
77
+
78
+ ## Examples
79
+
80
+ Read text from a screenshot:
81
+
82
+ ```text
83
+ extract_text_from_image(image_path="/tmp/screenshot.png")
84
+ ```
85
+
86
+ Ask about a specific part of the UI:
87
+
88
+ ```text
89
+ analyze_image(
90
+ image_path="/tmp/screenshot.png",
91
+ question="What does the primary button say, and where is it located?"
92
+ )
93
+ ```
94
+
95
+ Describe a screenshot and remove it afterwards:
96
+
97
+ ```text
98
+ describe_image(image_path="/tmp/screenshot.png", cleanup=true)
99
+ ```
100
+
101
+ ## Notes
102
+
103
+ This server intentionally stays narrow. It exists to help models inspect local screenshots when another tool can produce the image file but cannot explain what is inside it.
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "nvidia-vision-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for reading local images with NVIDIA vision models.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "nvidia-vision-mcp": "./src/server.js"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "scripts": {
19
+ "check": "node --check src/server.js"
20
+ },
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "^1.0.0",
23
+ "zod": "^3.24.0"
24
+ }
25
+ }
package/src/server.js ADDED
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFile, unlink } from "node:fs/promises";
4
+ import { existsSync } from "node:fs";
5
+ import { extname, resolve } from "node:path";
6
+
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import { z } from "zod";
10
+
11
+ const NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1";
12
+ const DEFAULT_NVIDIA_MODEL = "meta/llama-4-maverick-17b-128e-instruct";
13
+
14
+ const server = new McpServer({
15
+ name: "nvidia-vision",
16
+ version: "0.1.0",
17
+ });
18
+
19
+ function getApiKey() {
20
+ const apiKey = process.env.NVIDIA_API_KEY;
21
+ if (!apiKey) {
22
+ throw new Error("NVIDIA_API_KEY is not set. Add it to your MCP server environment.");
23
+ }
24
+ return apiKey;
25
+ }
26
+
27
+ function getModel() {
28
+ return process.env.NVIDIA_MODEL || DEFAULT_NVIDIA_MODEL;
29
+ }
30
+
31
+ function getMimeType(filePath) {
32
+ const mimeTypes = {
33
+ ".png": "image/png",
34
+ ".jpg": "image/jpeg",
35
+ ".jpeg": "image/jpeg",
36
+ ".webp": "image/webp",
37
+ ".gif": "image/gif",
38
+ ".bmp": "image/bmp",
39
+ };
40
+
41
+ return mimeTypes[extname(filePath).toLowerCase()] ?? "image/png";
42
+ }
43
+
44
+ async function callVision(prompt, imagePath) {
45
+ const fullPath = resolve(imagePath);
46
+
47
+ if (!existsSync(fullPath)) {
48
+ return `Error: file not found: ${imagePath}`;
49
+ }
50
+
51
+ const image = await readFile(fullPath);
52
+ const mimeType = getMimeType(fullPath);
53
+ const base64Image = image.toString("base64");
54
+
55
+ const response = await fetch(`${NVIDIA_BASE_URL}/chat/completions`, {
56
+ method: "POST",
57
+ headers: {
58
+ Authorization: `Bearer ${getApiKey()}`,
59
+ "Content-Type": "application/json",
60
+ },
61
+ body: JSON.stringify({
62
+ model: getModel(),
63
+ messages: [
64
+ {
65
+ role: "user",
66
+ content: [
67
+ { type: "text", text: prompt },
68
+ {
69
+ type: "image_url",
70
+ image_url: { url: `data:${mimeType};base64,${base64Image}` },
71
+ },
72
+ ],
73
+ },
74
+ ],
75
+ max_tokens: 1024,
76
+ temperature: 0.2,
77
+ }),
78
+ });
79
+
80
+ if (!response.ok) {
81
+ const body = await response.text();
82
+ throw new Error(`NVIDIA API request failed (${response.status}): ${body}`);
83
+ }
84
+
85
+ const data = await response.json();
86
+ return data.choices?.[0]?.message?.content ?? "No response returned from NVIDIA API.";
87
+ }
88
+
89
+ async function removeFile(filePath) {
90
+ const fullPath = resolve(filePath);
91
+
92
+ if (!existsSync(fullPath)) {
93
+ return `File not found: ${filePath}`;
94
+ }
95
+
96
+ try {
97
+ await unlink(fullPath);
98
+ return `Deleted: ${filePath}`;
99
+ } catch (error) {
100
+ return `Failed to delete ${filePath}: ${error.message}`;
101
+ }
102
+ }
103
+
104
+ function textResponse(text) {
105
+ return {
106
+ content: [{ type: "text", text }],
107
+ };
108
+ }
109
+
110
+ server.tool(
111
+ "describe_image",
112
+ "Describe a local image in detail. Useful when the current AI model cannot see screenshots directly.",
113
+ {
114
+ image_path: z.string().describe("Absolute or relative path to the image file."),
115
+ cleanup: z.boolean().default(false).describe("Delete the image file after reading it."),
116
+ },
117
+ async ({ image_path, cleanup }) => {
118
+ let result = await callVision(
119
+ "Describe this image in detail. Include visible elements, text, colors, layout, and anything unusual.",
120
+ image_path,
121
+ );
122
+
123
+ if (cleanup) {
124
+ result += `\n\n${await removeFile(image_path)}`;
125
+ }
126
+
127
+ return textResponse(result);
128
+ },
129
+ );
130
+
131
+ server.tool(
132
+ "extract_text_from_image",
133
+ "Extract visible text from a local image or screenshot.",
134
+ {
135
+ image_path: z.string().describe("Absolute or relative path to the image file."),
136
+ cleanup: z.boolean().default(false).describe("Delete the image file after reading it."),
137
+ },
138
+ async ({ image_path, cleanup }) => {
139
+ let result = await callVision(
140
+ "Extract all visible text from this image. Preserve line breaks and structure where possible. If there is no text, say that no text was found.",
141
+ image_path,
142
+ );
143
+
144
+ if (cleanup) {
145
+ result += `\n\n${await removeFile(image_path)}`;
146
+ }
147
+
148
+ return textResponse(result);
149
+ },
150
+ );
151
+
152
+ server.tool(
153
+ "analyze_image",
154
+ "Ask a specific question about a local image.",
155
+ {
156
+ image_path: z.string().describe("Absolute or relative path to the image file."),
157
+ question: z.string().describe("Question to answer using the image."),
158
+ cleanup: z.boolean().default(false).describe("Delete the image file after reading it."),
159
+ },
160
+ async ({ image_path, question, cleanup }) => {
161
+ let result = await callVision(question, image_path);
162
+
163
+ if (cleanup) {
164
+ result += `\n\n${await removeFile(image_path)}`;
165
+ }
166
+
167
+ return textResponse(result);
168
+ },
169
+ );
170
+
171
+ server.tool(
172
+ "delete_file",
173
+ "Delete a local file, usually a temporary screenshot that is no longer needed.",
174
+ {
175
+ file_path: z.string().describe("Path to the file to delete."),
176
+ },
177
+ async ({ file_path }) => textResponse(await removeFile(file_path)),
178
+ );
179
+
180
+ const transport = new StdioServerTransport();
181
+ await server.connect(transport);