codemax-mcp 1.0.0 → 1.0.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/README.md +63 -63
- package/index.js +373 -373
- package/package.json +31 -31
package/README.md
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
# codemax-mcp
|
|
2
|
-
|
|
3
|
-
MCP server for [ClaudeMax](https://codemax.pro) — adds **web search** and **image analysis** tools to your Claude Code session.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install -g codemax-mcp
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Or use with npx (no install needed):
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npx -y codemax-mcp
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Tools
|
|
18
|
-
|
|
19
|
-
### `web_search`
|
|
20
|
-
Search the web for real-time information — docs, news, APIs, anything.
|
|
21
|
-
|
|
22
|
-
### `understand_image`
|
|
23
|
-
Analyze images with AI — screenshots, diagrams, UI mockups, error messages.
|
|
24
|
-
|
|
25
|
-
## Setup
|
|
26
|
-
|
|
27
|
-
### Automatic (Recommended)
|
|
28
|
-
Run the one-click setup script:
|
|
29
|
-
|
|
30
|
-
**Windows (PowerShell):**
|
|
31
|
-
```powershell
|
|
32
|
-
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
|
33
|
-
irm https://api.
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### Manual
|
|
37
|
-
Add to your `~/.claude.json`:
|
|
38
|
-
|
|
39
|
-
```json
|
|
40
|
-
{
|
|
41
|
-
"mcpServers": {
|
|
42
|
-
"ClaudeMax": {
|
|
43
|
-
"command": "npx",
|
|
44
|
-
"args": ["-y", "claudemax-mcp"],
|
|
45
|
-
"env": {
|
|
46
|
-
"CLAUDEMAX_API_KEY": "YOUR_API_KEY",
|
|
47
|
-
"CLAUDEMAX_URL": "https://api.
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Environment Variables
|
|
55
|
-
|
|
56
|
-
| Variable | Description |
|
|
57
|
-
|----------|-------------|
|
|
58
|
-
| `CLAUDEMAX_API_KEY` | Your ClaudeMax API key |
|
|
59
|
-
| `CLAUDEMAX_URL` | API endpoint (default: `https://api.
|
|
60
|
-
|
|
61
|
-
## License
|
|
62
|
-
|
|
63
|
-
MIT
|
|
1
|
+
# codemax-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [ClaudeMax](https://codemax.pro) — adds **web search** and **image analysis** tools to your Claude Code session.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g codemax-mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use with npx (no install needed):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx -y codemax-mcp
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Tools
|
|
18
|
+
|
|
19
|
+
### `web_search`
|
|
20
|
+
Search the web for real-time information — docs, news, APIs, anything.
|
|
21
|
+
|
|
22
|
+
### `understand_image`
|
|
23
|
+
Analyze images with AI — screenshots, diagrams, UI mockups, error messages.
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
### Automatic (Recommended)
|
|
28
|
+
Run the one-click setup script:
|
|
29
|
+
|
|
30
|
+
**Windows (PowerShell):**
|
|
31
|
+
```powershell
|
|
32
|
+
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
|
33
|
+
irm https://api.codemax.pro/setup.ps1 | iex
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Manual
|
|
37
|
+
Add to your `~/.claude.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"ClaudeMax": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["-y", "claudemax-mcp"],
|
|
45
|
+
"env": {
|
|
46
|
+
"CLAUDEMAX_API_KEY": "YOUR_API_KEY",
|
|
47
|
+
"CLAUDEMAX_URL": "https://api.codemax.pro"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Environment Variables
|
|
55
|
+
|
|
56
|
+
| Variable | Description |
|
|
57
|
+
|----------|-------------|
|
|
58
|
+
| `CLAUDEMAX_API_KEY` | Your ClaudeMax API key |
|
|
59
|
+
| `CLAUDEMAX_URL` | API endpoint (default: `https://api.codemax.pro`) |
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
package/index.js
CHANGED
|
@@ -1,373 +1,373 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* ClaudeMax MCP Server
|
|
5
|
-
*
|
|
6
|
-
* Provides MCP tools for Claude Code:
|
|
7
|
-
* - web_search: Search the web for real-time information
|
|
8
|
-
* - understand_image: Analyze images with AI
|
|
9
|
-
*
|
|
10
|
-
* Environment variables:
|
|
11
|
-
* CLAUDEMAX_API_KEY — Your ClaudeMax API key
|
|
12
|
-
* CLAUDEMAX_URL — ClaudeMax API endpoint (default: https://api.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
16
|
-
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
17
|
-
const {
|
|
18
|
-
CallToolRequestSchema,
|
|
19
|
-
ListToolsRequestSchema,
|
|
20
|
-
} = require("@modelcontextprotocol/sdk/types.js");
|
|
21
|
-
const https = require("https");
|
|
22
|
-
const http = require("http");
|
|
23
|
-
const fs = require("fs");
|
|
24
|
-
const path = require("path");
|
|
25
|
-
|
|
26
|
-
// ─── Config ───
|
|
27
|
-
const API_KEY = process.env.CLAUDEMAX_API_KEY || process.env.ANTHROPIC_API_KEY || "";
|
|
28
|
-
const BASE_URL = process.env.CLAUDEMAX_URL || "https://api.
|
|
29
|
-
const SERPER_URL = "https://google.serper.dev";
|
|
30
|
-
|
|
31
|
-
// ─── HTTP helper ───
|
|
32
|
-
function httpRequest(url, options, body) {
|
|
33
|
-
return new Promise((resolve, reject) => {
|
|
34
|
-
const parsedUrl = new URL(url);
|
|
35
|
-
const isHttps = parsedUrl.protocol === "https:";
|
|
36
|
-
const lib = isHttps ? https : http;
|
|
37
|
-
|
|
38
|
-
const req = lib.request(
|
|
39
|
-
{
|
|
40
|
-
hostname: parsedUrl.hostname,
|
|
41
|
-
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
42
|
-
path: parsedUrl.pathname + parsedUrl.search,
|
|
43
|
-
method: options.method || "GET",
|
|
44
|
-
headers: options.headers || {},
|
|
45
|
-
timeout: 30000,
|
|
46
|
-
},
|
|
47
|
-
(res) => {
|
|
48
|
-
let data = "";
|
|
49
|
-
res.on("data", (chunk) => (data += chunk));
|
|
50
|
-
res.on("end", () => {
|
|
51
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
52
|
-
try {
|
|
53
|
-
resolve(JSON.parse(data));
|
|
54
|
-
} catch {
|
|
55
|
-
resolve(data);
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
reject(new Error(`HTTP ${res.statusCode}: ${data.substring(0, 500)}`));
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
req.on("error", reject);
|
|
65
|
-
req.on("timeout", () => {
|
|
66
|
-
req.destroy();
|
|
67
|
-
reject(new Error("Request timeout"));
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
if (body) req.write(typeof body === "string" ? body : JSON.stringify(body));
|
|
71
|
-
req.end();
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ─── Web Search via ClaudeMax backend ───
|
|
76
|
-
async function webSearch(query, numResults = 5) {
|
|
77
|
-
// Try to use the ClaudeMax API's search endpoint
|
|
78
|
-
// Falls back to direct Serper.dev if available
|
|
79
|
-
try {
|
|
80
|
-
const body = JSON.stringify({ q: query, num: numResults });
|
|
81
|
-
|
|
82
|
-
const result = await httpRequest(`${SERPER_URL}/search`, {
|
|
83
|
-
method: "POST",
|
|
84
|
-
headers: {
|
|
85
|
-
"Content-Type": "application/json",
|
|
86
|
-
// The API key is forwarded through the ClaudeMax proxy search endpoint
|
|
87
|
-
"X-API-KEY": process.env.SERPER_API_KEY || "",
|
|
88
|
-
},
|
|
89
|
-
}, body);
|
|
90
|
-
|
|
91
|
-
const results = [];
|
|
92
|
-
|
|
93
|
-
// Knowledge graph
|
|
94
|
-
if (result.knowledgeGraph) {
|
|
95
|
-
const kg = result.knowledgeGraph;
|
|
96
|
-
results.push({
|
|
97
|
-
title: kg.title || "",
|
|
98
|
-
snippet: kg.description || "",
|
|
99
|
-
url: kg.descriptionLink || "",
|
|
100
|
-
type: "knowledge_graph",
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Answer box
|
|
105
|
-
if (result.answerBox) {
|
|
106
|
-
results.push({
|
|
107
|
-
title: result.answerBox.title || "Answer",
|
|
108
|
-
snippet: result.answerBox.answer || result.answerBox.snippet || "",
|
|
109
|
-
url: result.answerBox.link || "",
|
|
110
|
-
type: "answer_box",
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Organic results
|
|
115
|
-
if (result.organic) {
|
|
116
|
-
for (const r of result.organic.slice(0, numResults)) {
|
|
117
|
-
results.push({
|
|
118
|
-
title: r.title || "",
|
|
119
|
-
snippet: r.snippet || "",
|
|
120
|
-
url: r.link || "",
|
|
121
|
-
type: "organic",
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return results;
|
|
127
|
-
} catch (error) {
|
|
128
|
-
// Fallback: use ClaudeMax proxy search endpoint
|
|
129
|
-
try {
|
|
130
|
-
const result = await httpRequest(`${BASE_URL}/v1/search`, {
|
|
131
|
-
method: "POST",
|
|
132
|
-
headers: {
|
|
133
|
-
"Content-Type": "application/json",
|
|
134
|
-
"x-api-key": API_KEY,
|
|
135
|
-
},
|
|
136
|
-
}, JSON.stringify({ query, numResults }));
|
|
137
|
-
|
|
138
|
-
return result.results || [];
|
|
139
|
-
} catch {
|
|
140
|
-
throw new Error(`Search failed: ${error.message}`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function formatSearchResults(results) {
|
|
146
|
-
if (!results || results.length === 0) return "No search results found.";
|
|
147
|
-
|
|
148
|
-
let text = "";
|
|
149
|
-
for (let i = 0; i < results.length; i++) {
|
|
150
|
-
const r = results[i];
|
|
151
|
-
text += `[${i + 1}] ${r.title}\n`;
|
|
152
|
-
if (r.snippet) text += `${r.snippet}\n`;
|
|
153
|
-
if (r.url) text += `Source: ${r.url}\n`;
|
|
154
|
-
text += "\n";
|
|
155
|
-
}
|
|
156
|
-
return text.trim();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ─── Image Analysis via ClaudeMax API ───
|
|
160
|
-
async function analyzeImage(imageInput, question) {
|
|
161
|
-
let imageData;
|
|
162
|
-
let mediaType = "image/jpeg";
|
|
163
|
-
|
|
164
|
-
// Check if it's a URL or file path
|
|
165
|
-
if (imageInput.startsWith("http://") || imageInput.startsWith("https://")) {
|
|
166
|
-
// Download image from URL
|
|
167
|
-
const imageBuffer = await httpRequest(imageInput, { method: "GET" }, null);
|
|
168
|
-
if (typeof imageBuffer === "string") {
|
|
169
|
-
imageData = Buffer.from(imageBuffer, "binary").toString("base64");
|
|
170
|
-
} else {
|
|
171
|
-
imageData = Buffer.from(JSON.stringify(imageBuffer)).toString("base64");
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Detect media type from URL
|
|
175
|
-
const ext = imageInput.split(".").pop()?.toLowerCase()?.split("?")[0];
|
|
176
|
-
if (ext === "png") mediaType = "image/png";
|
|
177
|
-
else if (ext === "webp") mediaType = "image/webp";
|
|
178
|
-
else if (ext === "gif") mediaType = "image/gif";
|
|
179
|
-
} else {
|
|
180
|
-
// Read from file path
|
|
181
|
-
const filePath = path.resolve(imageInput);
|
|
182
|
-
if (!fs.existsSync(filePath)) {
|
|
183
|
-
throw new Error(`File not found: ${filePath}`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const buffer = fs.readFileSync(filePath);
|
|
187
|
-
imageData = buffer.toString("base64");
|
|
188
|
-
|
|
189
|
-
// Detect media type from extension
|
|
190
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
191
|
-
if (ext === ".png") mediaType = "image/png";
|
|
192
|
-
else if (ext === ".webp") mediaType = "image/webp";
|
|
193
|
-
else if (ext === ".gif") mediaType = "image/gif";
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Send to ClaudeMax API for analysis
|
|
197
|
-
const body = {
|
|
198
|
-
model: "Opus 4.6",
|
|
199
|
-
max_tokens: 4096,
|
|
200
|
-
messages: [
|
|
201
|
-
{
|
|
202
|
-
role: "user",
|
|
203
|
-
content: [
|
|
204
|
-
{
|
|
205
|
-
type: "image",
|
|
206
|
-
source: {
|
|
207
|
-
type: "base64",
|
|
208
|
-
media_type: mediaType,
|
|
209
|
-
data: imageData,
|
|
210
|
-
},
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
type: "text",
|
|
214
|
-
text: question || "Describe this image in detail. What do you see?",
|
|
215
|
-
},
|
|
216
|
-
],
|
|
217
|
-
},
|
|
218
|
-
],
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const result = await httpRequest(`${BASE_URL}/v1/messages`, {
|
|
222
|
-
method: "POST",
|
|
223
|
-
headers: {
|
|
224
|
-
"Content-Type": "application/json",
|
|
225
|
-
"x-api-key": API_KEY,
|
|
226
|
-
"anthropic-version": "2023-06-01",
|
|
227
|
-
},
|
|
228
|
-
}, JSON.stringify(body));
|
|
229
|
-
|
|
230
|
-
// Extract text from response
|
|
231
|
-
if (result.content && Array.isArray(result.content)) {
|
|
232
|
-
return result.content
|
|
233
|
-
.filter((b) => b.type === "text")
|
|
234
|
-
.map((b) => b.text)
|
|
235
|
-
.join("\n");
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return result.text || JSON.stringify(result);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ─── MCP Server Setup ───
|
|
242
|
-
const server = new Server(
|
|
243
|
-
{
|
|
244
|
-
name: "claudemax-mcp",
|
|
245
|
-
version: "1.0.0",
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
capabilities: {
|
|
249
|
-
tools: {},
|
|
250
|
-
},
|
|
251
|
-
}
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
// List available tools
|
|
255
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
256
|
-
return {
|
|
257
|
-
tools: [
|
|
258
|
-
{
|
|
259
|
-
name: "web_search",
|
|
260
|
-
description:
|
|
261
|
-
"Search the web for real-time information — documentation, news, APIs, current events, pricing, anything that requires up-to-date data from the internet.",
|
|
262
|
-
inputSchema: {
|
|
263
|
-
type: "object",
|
|
264
|
-
properties: {
|
|
265
|
-
query: {
|
|
266
|
-
type: "string",
|
|
267
|
-
description: "The search query to find information on the web",
|
|
268
|
-
},
|
|
269
|
-
num_results: {
|
|
270
|
-
type: "number",
|
|
271
|
-
description: "Number of results to return (default: 5, max: 10)",
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
required: ["query"],
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
name: "understand_image",
|
|
279
|
-
description:
|
|
280
|
-
"Analyze images with AI — screenshots, diagrams, UI mockups, error messages, charts, photos. Provide a URL or file path to the image.",
|
|
281
|
-
inputSchema: {
|
|
282
|
-
type: "object",
|
|
283
|
-
properties: {
|
|
284
|
-
image: {
|
|
285
|
-
type: "string",
|
|
286
|
-
description:
|
|
287
|
-
"URL or local file path to the image to analyze (supports JPEG, PNG, WebP, GIF)",
|
|
288
|
-
},
|
|
289
|
-
question: {
|
|
290
|
-
type: "string",
|
|
291
|
-
description:
|
|
292
|
-
"What to analyze or ask about the image (default: general description)",
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
required: ["image"],
|
|
296
|
-
},
|
|
297
|
-
},
|
|
298
|
-
],
|
|
299
|
-
};
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// Handle tool calls
|
|
303
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
304
|
-
const { name, arguments: args } = request.params;
|
|
305
|
-
|
|
306
|
-
try {
|
|
307
|
-
if (name === "web_search") {
|
|
308
|
-
const query = args.query;
|
|
309
|
-
const numResults = Math.min(args.num_results || 5, 10);
|
|
310
|
-
|
|
311
|
-
if (!query || typeof query !== "string") {
|
|
312
|
-
return {
|
|
313
|
-
content: [{ type: "text", text: "Error: 'query' parameter is required and must be a string." }],
|
|
314
|
-
isError: true,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const results = await webSearch(query, numResults);
|
|
319
|
-
const formatted = formatSearchResults(results);
|
|
320
|
-
|
|
321
|
-
return {
|
|
322
|
-
content: [{ type: "text", text: formatted }],
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (name === "understand_image") {
|
|
327
|
-
const image = args.image;
|
|
328
|
-
const question = args.question;
|
|
329
|
-
|
|
330
|
-
if (!image || typeof image !== "string") {
|
|
331
|
-
return {
|
|
332
|
-
content: [{ type: "text", text: "Error: 'image' parameter is required and must be a URL or file path." }],
|
|
333
|
-
isError: true,
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (!API_KEY) {
|
|
338
|
-
return {
|
|
339
|
-
content: [{ type: "text", text: "Error: CLAUDEMAX_API_KEY environment variable is not set. Please run the setup script or set it manually." }],
|
|
340
|
-
isError: true,
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const analysis = await analyzeImage(image, question);
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
content: [{ type: "text", text: analysis }],
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return {
|
|
352
|
-
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
353
|
-
isError: true,
|
|
354
|
-
};
|
|
355
|
-
} catch (error) {
|
|
356
|
-
return {
|
|
357
|
-
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
358
|
-
isError: true,
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// ─── Start Server ───
|
|
364
|
-
async function main() {
|
|
365
|
-
const transport = new StdioServerTransport();
|
|
366
|
-
await server.connect(transport);
|
|
367
|
-
console.error("[ClaudeMax MCP] Server started");
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
main().catch((error) => {
|
|
371
|
-
console.error("[ClaudeMax MCP] Fatal error:", error);
|
|
372
|
-
process.exit(1);
|
|
373
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ClaudeMax MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Provides MCP tools for Claude Code:
|
|
7
|
+
* - web_search: Search the web for real-time information
|
|
8
|
+
* - understand_image: Analyze images with AI
|
|
9
|
+
*
|
|
10
|
+
* Environment variables:
|
|
11
|
+
* CLAUDEMAX_API_KEY — Your ClaudeMax API key
|
|
12
|
+
* CLAUDEMAX_URL — ClaudeMax API endpoint (default: https://api.codemax.pro)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
16
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
17
|
+
const {
|
|
18
|
+
CallToolRequestSchema,
|
|
19
|
+
ListToolsRequestSchema,
|
|
20
|
+
} = require("@modelcontextprotocol/sdk/types.js");
|
|
21
|
+
const https = require("https");
|
|
22
|
+
const http = require("http");
|
|
23
|
+
const fs = require("fs");
|
|
24
|
+
const path = require("path");
|
|
25
|
+
|
|
26
|
+
// ─── Config ───
|
|
27
|
+
const API_KEY = process.env.CLAUDEMAX_API_KEY || process.env.ANTHROPIC_API_KEY || "";
|
|
28
|
+
const BASE_URL = process.env.CLAUDEMAX_URL || "https://api.codemax.pro";
|
|
29
|
+
const SERPER_URL = "https://google.serper.dev";
|
|
30
|
+
|
|
31
|
+
// ─── HTTP helper ───
|
|
32
|
+
function httpRequest(url, options, body) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const parsedUrl = new URL(url);
|
|
35
|
+
const isHttps = parsedUrl.protocol === "https:";
|
|
36
|
+
const lib = isHttps ? https : http;
|
|
37
|
+
|
|
38
|
+
const req = lib.request(
|
|
39
|
+
{
|
|
40
|
+
hostname: parsedUrl.hostname,
|
|
41
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
42
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
43
|
+
method: options.method || "GET",
|
|
44
|
+
headers: options.headers || {},
|
|
45
|
+
timeout: 30000,
|
|
46
|
+
},
|
|
47
|
+
(res) => {
|
|
48
|
+
let data = "";
|
|
49
|
+
res.on("data", (chunk) => (data += chunk));
|
|
50
|
+
res.on("end", () => {
|
|
51
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
52
|
+
try {
|
|
53
|
+
resolve(JSON.parse(data));
|
|
54
|
+
} catch {
|
|
55
|
+
resolve(data);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
reject(new Error(`HTTP ${res.statusCode}: ${data.substring(0, 500)}`));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
req.on("error", reject);
|
|
65
|
+
req.on("timeout", () => {
|
|
66
|
+
req.destroy();
|
|
67
|
+
reject(new Error("Request timeout"));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (body) req.write(typeof body === "string" ? body : JSON.stringify(body));
|
|
71
|
+
req.end();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── Web Search via ClaudeMax backend ───
|
|
76
|
+
async function webSearch(query, numResults = 5) {
|
|
77
|
+
// Try to use the ClaudeMax API's search endpoint
|
|
78
|
+
// Falls back to direct Serper.dev if available
|
|
79
|
+
try {
|
|
80
|
+
const body = JSON.stringify({ q: query, num: numResults });
|
|
81
|
+
|
|
82
|
+
const result = await httpRequest(`${SERPER_URL}/search`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
// The API key is forwarded through the ClaudeMax proxy search endpoint
|
|
87
|
+
"X-API-KEY": process.env.SERPER_API_KEY || "",
|
|
88
|
+
},
|
|
89
|
+
}, body);
|
|
90
|
+
|
|
91
|
+
const results = [];
|
|
92
|
+
|
|
93
|
+
// Knowledge graph
|
|
94
|
+
if (result.knowledgeGraph) {
|
|
95
|
+
const kg = result.knowledgeGraph;
|
|
96
|
+
results.push({
|
|
97
|
+
title: kg.title || "",
|
|
98
|
+
snippet: kg.description || "",
|
|
99
|
+
url: kg.descriptionLink || "",
|
|
100
|
+
type: "knowledge_graph",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Answer box
|
|
105
|
+
if (result.answerBox) {
|
|
106
|
+
results.push({
|
|
107
|
+
title: result.answerBox.title || "Answer",
|
|
108
|
+
snippet: result.answerBox.answer || result.answerBox.snippet || "",
|
|
109
|
+
url: result.answerBox.link || "",
|
|
110
|
+
type: "answer_box",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Organic results
|
|
115
|
+
if (result.organic) {
|
|
116
|
+
for (const r of result.organic.slice(0, numResults)) {
|
|
117
|
+
results.push({
|
|
118
|
+
title: r.title || "",
|
|
119
|
+
snippet: r.snippet || "",
|
|
120
|
+
url: r.link || "",
|
|
121
|
+
type: "organic",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return results;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// Fallback: use ClaudeMax proxy search endpoint
|
|
129
|
+
try {
|
|
130
|
+
const result = await httpRequest(`${BASE_URL}/v1/search`, {
|
|
131
|
+
method: "POST",
|
|
132
|
+
headers: {
|
|
133
|
+
"Content-Type": "application/json",
|
|
134
|
+
"x-api-key": API_KEY,
|
|
135
|
+
},
|
|
136
|
+
}, JSON.stringify({ query, numResults }));
|
|
137
|
+
|
|
138
|
+
return result.results || [];
|
|
139
|
+
} catch {
|
|
140
|
+
throw new Error(`Search failed: ${error.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function formatSearchResults(results) {
|
|
146
|
+
if (!results || results.length === 0) return "No search results found.";
|
|
147
|
+
|
|
148
|
+
let text = "";
|
|
149
|
+
for (let i = 0; i < results.length; i++) {
|
|
150
|
+
const r = results[i];
|
|
151
|
+
text += `[${i + 1}] ${r.title}\n`;
|
|
152
|
+
if (r.snippet) text += `${r.snippet}\n`;
|
|
153
|
+
if (r.url) text += `Source: ${r.url}\n`;
|
|
154
|
+
text += "\n";
|
|
155
|
+
}
|
|
156
|
+
return text.trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ─── Image Analysis via ClaudeMax API ───
|
|
160
|
+
async function analyzeImage(imageInput, question) {
|
|
161
|
+
let imageData;
|
|
162
|
+
let mediaType = "image/jpeg";
|
|
163
|
+
|
|
164
|
+
// Check if it's a URL or file path
|
|
165
|
+
if (imageInput.startsWith("http://") || imageInput.startsWith("https://")) {
|
|
166
|
+
// Download image from URL
|
|
167
|
+
const imageBuffer = await httpRequest(imageInput, { method: "GET" }, null);
|
|
168
|
+
if (typeof imageBuffer === "string") {
|
|
169
|
+
imageData = Buffer.from(imageBuffer, "binary").toString("base64");
|
|
170
|
+
} else {
|
|
171
|
+
imageData = Buffer.from(JSON.stringify(imageBuffer)).toString("base64");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Detect media type from URL
|
|
175
|
+
const ext = imageInput.split(".").pop()?.toLowerCase()?.split("?")[0];
|
|
176
|
+
if (ext === "png") mediaType = "image/png";
|
|
177
|
+
else if (ext === "webp") mediaType = "image/webp";
|
|
178
|
+
else if (ext === "gif") mediaType = "image/gif";
|
|
179
|
+
} else {
|
|
180
|
+
// Read from file path
|
|
181
|
+
const filePath = path.resolve(imageInput);
|
|
182
|
+
if (!fs.existsSync(filePath)) {
|
|
183
|
+
throw new Error(`File not found: ${filePath}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const buffer = fs.readFileSync(filePath);
|
|
187
|
+
imageData = buffer.toString("base64");
|
|
188
|
+
|
|
189
|
+
// Detect media type from extension
|
|
190
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
191
|
+
if (ext === ".png") mediaType = "image/png";
|
|
192
|
+
else if (ext === ".webp") mediaType = "image/webp";
|
|
193
|
+
else if (ext === ".gif") mediaType = "image/gif";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Send to ClaudeMax API for analysis
|
|
197
|
+
const body = {
|
|
198
|
+
model: "Opus 4.6",
|
|
199
|
+
max_tokens: 4096,
|
|
200
|
+
messages: [
|
|
201
|
+
{
|
|
202
|
+
role: "user",
|
|
203
|
+
content: [
|
|
204
|
+
{
|
|
205
|
+
type: "image",
|
|
206
|
+
source: {
|
|
207
|
+
type: "base64",
|
|
208
|
+
media_type: mediaType,
|
|
209
|
+
data: imageData,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
type: "text",
|
|
214
|
+
text: question || "Describe this image in detail. What do you see?",
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const result = await httpRequest(`${BASE_URL}/v1/messages`, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: {
|
|
224
|
+
"Content-Type": "application/json",
|
|
225
|
+
"x-api-key": API_KEY,
|
|
226
|
+
"anthropic-version": "2023-06-01",
|
|
227
|
+
},
|
|
228
|
+
}, JSON.stringify(body));
|
|
229
|
+
|
|
230
|
+
// Extract text from response
|
|
231
|
+
if (result.content && Array.isArray(result.content)) {
|
|
232
|
+
return result.content
|
|
233
|
+
.filter((b) => b.type === "text")
|
|
234
|
+
.map((b) => b.text)
|
|
235
|
+
.join("\n");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return result.text || JSON.stringify(result);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─── MCP Server Setup ───
|
|
242
|
+
const server = new Server(
|
|
243
|
+
{
|
|
244
|
+
name: "claudemax-mcp",
|
|
245
|
+
version: "1.0.0",
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
capabilities: {
|
|
249
|
+
tools: {},
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// List available tools
|
|
255
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
256
|
+
return {
|
|
257
|
+
tools: [
|
|
258
|
+
{
|
|
259
|
+
name: "web_search",
|
|
260
|
+
description:
|
|
261
|
+
"Search the web for real-time information — documentation, news, APIs, current events, pricing, anything that requires up-to-date data from the internet.",
|
|
262
|
+
inputSchema: {
|
|
263
|
+
type: "object",
|
|
264
|
+
properties: {
|
|
265
|
+
query: {
|
|
266
|
+
type: "string",
|
|
267
|
+
description: "The search query to find information on the web",
|
|
268
|
+
},
|
|
269
|
+
num_results: {
|
|
270
|
+
type: "number",
|
|
271
|
+
description: "Number of results to return (default: 5, max: 10)",
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
required: ["query"],
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "understand_image",
|
|
279
|
+
description:
|
|
280
|
+
"Analyze images with AI — screenshots, diagrams, UI mockups, error messages, charts, photos. Provide a URL or file path to the image.",
|
|
281
|
+
inputSchema: {
|
|
282
|
+
type: "object",
|
|
283
|
+
properties: {
|
|
284
|
+
image: {
|
|
285
|
+
type: "string",
|
|
286
|
+
description:
|
|
287
|
+
"URL or local file path to the image to analyze (supports JPEG, PNG, WebP, GIF)",
|
|
288
|
+
},
|
|
289
|
+
question: {
|
|
290
|
+
type: "string",
|
|
291
|
+
description:
|
|
292
|
+
"What to analyze or ask about the image (default: general description)",
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
required: ["image"],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Handle tool calls
|
|
303
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
304
|
+
const { name, arguments: args } = request.params;
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
if (name === "web_search") {
|
|
308
|
+
const query = args.query;
|
|
309
|
+
const numResults = Math.min(args.num_results || 5, 10);
|
|
310
|
+
|
|
311
|
+
if (!query || typeof query !== "string") {
|
|
312
|
+
return {
|
|
313
|
+
content: [{ type: "text", text: "Error: 'query' parameter is required and must be a string." }],
|
|
314
|
+
isError: true,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const results = await webSearch(query, numResults);
|
|
319
|
+
const formatted = formatSearchResults(results);
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
content: [{ type: "text", text: formatted }],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (name === "understand_image") {
|
|
327
|
+
const image = args.image;
|
|
328
|
+
const question = args.question;
|
|
329
|
+
|
|
330
|
+
if (!image || typeof image !== "string") {
|
|
331
|
+
return {
|
|
332
|
+
content: [{ type: "text", text: "Error: 'image' parameter is required and must be a URL or file path." }],
|
|
333
|
+
isError: true,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!API_KEY) {
|
|
338
|
+
return {
|
|
339
|
+
content: [{ type: "text", text: "Error: CLAUDEMAX_API_KEY environment variable is not set. Please run the setup script or set it manually." }],
|
|
340
|
+
isError: true,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const analysis = await analyzeImage(image, question);
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
content: [{ type: "text", text: analysis }],
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
353
|
+
isError: true,
|
|
354
|
+
};
|
|
355
|
+
} catch (error) {
|
|
356
|
+
return {
|
|
357
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
358
|
+
isError: true,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// ─── Start Server ───
|
|
364
|
+
async function main() {
|
|
365
|
+
const transport = new StdioServerTransport();
|
|
366
|
+
await server.connect(transport);
|
|
367
|
+
console.error("[ClaudeMax MCP] Server started");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
main().catch((error) => {
|
|
371
|
+
console.error("[ClaudeMax MCP] Fatal error:", error);
|
|
372
|
+
process.exit(1);
|
|
373
|
+
});
|
package/package.json
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "codemax-mcp",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "ClaudeMax MCP server — web search & image analysis tools for Claude Code",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"codemax-mcp": "index.js"
|
|
8
|
-
},
|
|
9
|
-
"keywords": [
|
|
10
|
-
"mcp",
|
|
11
|
-
"claude",
|
|
12
|
-
"claudemax",
|
|
13
|
-
"claude-code",
|
|
14
|
-
"web-search",
|
|
15
|
-
"image-analysis",
|
|
16
|
-
"anthropic",
|
|
17
|
-
"model-context-protocol"
|
|
18
|
-
],
|
|
19
|
-
"author": "ClaudeMax",
|
|
20
|
-
"license": "MIT",
|
|
21
|
-
"dependencies": {
|
|
22
|
-
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
23
|
-
},
|
|
24
|
-
"engines": {
|
|
25
|
-
"node": ">=18"
|
|
26
|
-
},
|
|
27
|
-
"files": [
|
|
28
|
-
"index.js",
|
|
29
|
-
"README.md"
|
|
30
|
-
]
|
|
31
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "codemax-mcp",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "ClaudeMax MCP server — web search & image analysis tools for Claude Code",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codemax-mcp": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"mcp",
|
|
11
|
+
"claude",
|
|
12
|
+
"claudemax",
|
|
13
|
+
"claude-code",
|
|
14
|
+
"web-search",
|
|
15
|
+
"image-analysis",
|
|
16
|
+
"anthropic",
|
|
17
|
+
"model-context-protocol"
|
|
18
|
+
],
|
|
19
|
+
"author": "ClaudeMax",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"index.js",
|
|
29
|
+
"README.md"
|
|
30
|
+
]
|
|
31
|
+
}
|