opuscode-mcp 1.0.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/README.md +144 -0
- package/index.js +167 -0
- package/package.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# OpusCode MCP
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server that provides **web search** and **image understanding** tools for AI coding agents — Claude Code, Cursor, OpenCode, and more.
|
|
4
|
+
|
|
5
|
+
## Quick Install
|
|
6
|
+
|
|
7
|
+
### Claude Code (recommended)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
claude mcp add -s user OpusCode \
|
|
11
|
+
--env OPUSCODE_API_KEY=your_key \
|
|
12
|
+
--env OPUSCODE_URL=https://your-server.com \
|
|
13
|
+
-- npx opuscode-mcp
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Global install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g opuscode-mcp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Manual Configuration
|
|
23
|
+
|
|
24
|
+
### Claude Code
|
|
25
|
+
|
|
26
|
+
Add to your Claude Code MCP config (`~/.claude/claude_desktop_config.json`):
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"OpusCode": {
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["opuscode-mcp"],
|
|
34
|
+
"env": {
|
|
35
|
+
"OPUSCODE_API_KEY": "your_key",
|
|
36
|
+
"OPUSCODE_URL": "https://your-server.com"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Cursor
|
|
44
|
+
|
|
45
|
+
Add to Cursor MCP settings (`.cursor/mcp.json`):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"OpusCode": {
|
|
51
|
+
"command": "npx",
|
|
52
|
+
"args": ["opuscode-mcp"],
|
|
53
|
+
"env": {
|
|
54
|
+
"OPUSCODE_API_KEY": "your_key",
|
|
55
|
+
"OPUSCODE_URL": "https://your-server.com"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### OpenCode
|
|
63
|
+
|
|
64
|
+
Add to your OpenCode config:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcp": {
|
|
69
|
+
"OpusCode": {
|
|
70
|
+
"command": "npx",
|
|
71
|
+
"args": ["opuscode-mcp"],
|
|
72
|
+
"env": {
|
|
73
|
+
"OPUSCODE_API_KEY": "your_key",
|
|
74
|
+
"OPUSCODE_URL": "https://your-server.com"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Tools
|
|
82
|
+
|
|
83
|
+
| Tool | Description |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `web_search` | Search the web for real-time information. Best with 3–5 keywords. |
|
|
86
|
+
| `understand_image` | Analyze images using AI. Accepts URLs, local file paths, or base64 data URLs. |
|
|
87
|
+
|
|
88
|
+
## Usage Examples
|
|
89
|
+
|
|
90
|
+
### Web Search
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Search for: "Node.js 22 new features 2025"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The `web_search` tool accepts a `query` string and returns structured search results from the web.
|
|
97
|
+
|
|
98
|
+
### Image Understanding
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Prompt: "Describe this UI mockup and list all the components"
|
|
102
|
+
Image source: "./screenshot.png"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The `understand_image` tool accepts:
|
|
106
|
+
|
|
107
|
+
- **`prompt`** — What you want to know about the image
|
|
108
|
+
- **`image_source`** — One of:
|
|
109
|
+
- An HTTP/HTTPS URL (`https://example.com/image.png`)
|
|
110
|
+
- A local file path (`./diagram.png` or `/home/user/photo.jpg`)
|
|
111
|
+
- A base64 data URL (`data:image/png;base64,...`)
|
|
112
|
+
|
|
113
|
+
Supported formats: JPEG, PNG, WebP, GIF, BMP, SVG.
|
|
114
|
+
|
|
115
|
+
## Environment Variables
|
|
116
|
+
|
|
117
|
+
| Variable | Required | Default | Description |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| `OPUSCODE_API_KEY` | Yes | — | API key for authentication |
|
|
120
|
+
| `OPUSCODE_URL` | No | `http://localhost:3001` | OpusCode proxy server URL |
|
|
121
|
+
|
|
122
|
+
## Troubleshooting
|
|
123
|
+
|
|
124
|
+
### "Error: Proxy request failed: 401"
|
|
125
|
+
|
|
126
|
+
Your `OPUSCODE_API_KEY` is missing or invalid. Make sure it's set in your environment or MCP config.
|
|
127
|
+
|
|
128
|
+
### "Error: Proxy request failed: ECONNREFUSED"
|
|
129
|
+
|
|
130
|
+
The OpusCode proxy server is not running or `OPUSCODE_URL` points to the wrong address. Verify the server is up and the URL is correct.
|
|
131
|
+
|
|
132
|
+
### "Error: File not found"
|
|
133
|
+
|
|
134
|
+
When using `understand_image` with a local file path, ensure the path is correct relative to where the MCP server process is running.
|
|
135
|
+
|
|
136
|
+
### Tools not showing up
|
|
137
|
+
|
|
138
|
+
1. Restart your AI coding tool after adding the MCP config.
|
|
139
|
+
2. Check that `npx opuscode-mcp` runs without errors in your terminal.
|
|
140
|
+
3. Verify your MCP config JSON is valid.
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
4
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
5
|
+
const { CallToolRequestSchema, ListToolsRequestSchema } = require('@modelcontextprotocol/sdk/types.js');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const API_KEY = process.env.OPUSCODE_API_KEY || '';
|
|
10
|
+
const BASE_URL = (process.env.OPUSCODE_URL || 'http://localhost:3001').replace(/\/+$/, '');
|
|
11
|
+
|
|
12
|
+
const MIME_TYPES = {
|
|
13
|
+
'.jpg': 'image/jpeg',
|
|
14
|
+
'.jpeg': 'image/jpeg',
|
|
15
|
+
'.png': 'image/png',
|
|
16
|
+
'.gif': 'image/gif',
|
|
17
|
+
'.webp': 'image/webp',
|
|
18
|
+
'.bmp': 'image/bmp',
|
|
19
|
+
'.svg': 'image/svg+xml',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
async function processImageSource(imageSource) {
|
|
23
|
+
let source = imageSource.trim();
|
|
24
|
+
|
|
25
|
+
// Strip leading @ (common in curl-style references)
|
|
26
|
+
if (source.startsWith('@')) {
|
|
27
|
+
source = source.slice(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Already a data URL — pass through
|
|
31
|
+
if (source.startsWith('data:')) {
|
|
32
|
+
return source;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// HTTP(S) URL — download and convert
|
|
36
|
+
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
37
|
+
const res = await fetch(source);
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
throw new Error(`Failed to download image: ${res.status} ${res.statusText}`);
|
|
40
|
+
}
|
|
41
|
+
const contentType = res.headers.get('content-type') || 'image/png';
|
|
42
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
43
|
+
const base64 = buffer.toString('base64');
|
|
44
|
+
return `data:${contentType};base64,${base64}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Local file path
|
|
48
|
+
const resolved = path.resolve(source);
|
|
49
|
+
if (!fs.existsSync(resolved)) {
|
|
50
|
+
throw new Error(`File not found: ${resolved}`);
|
|
51
|
+
}
|
|
52
|
+
const ext = path.extname(resolved).toLowerCase();
|
|
53
|
+
const mime = MIME_TYPES[ext] || 'image/png';
|
|
54
|
+
const fileBuffer = fs.readFileSync(resolved);
|
|
55
|
+
const base64 = fileBuffer.toString('base64');
|
|
56
|
+
return `data:${mime};base64,${base64}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function callProxy(endpoint, body) {
|
|
60
|
+
const url = `${BASE_URL}${endpoint}`;
|
|
61
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
62
|
+
if (API_KEY) {
|
|
63
|
+
headers['Authorization'] = `Bearer ${API_KEY}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const res = await fetch(url, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers,
|
|
69
|
+
body: JSON.stringify(body),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
const text = await res.text().catch(() => '');
|
|
74
|
+
throw new Error(`Proxy request failed: ${res.status} ${res.statusText} — ${text}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return res.json();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const server = new Server(
|
|
81
|
+
{ name: 'opuscode-mcp', version: '1.0.0' },
|
|
82
|
+
{ capabilities: { tools: {} } }
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
86
|
+
tools: [
|
|
87
|
+
{
|
|
88
|
+
name: 'web_search',
|
|
89
|
+
description:
|
|
90
|
+
'Search the web for real-time information. Use 3-5 keywords for best results. For time-sensitive topics, include the current date.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
query: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'Search query (3-5 keywords recommended)',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
required: ['query'],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'understand_image',
|
|
104
|
+
description:
|
|
105
|
+
'Analyze images with AI. Supports JPEG, PNG, WebP. Accepts HTTP URLs, local file paths, or base64 data URLs.',
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
prompt: {
|
|
110
|
+
type: 'string',
|
|
111
|
+
description: 'What you want to know about the image',
|
|
112
|
+
},
|
|
113
|
+
image_source: {
|
|
114
|
+
type: 'string',
|
|
115
|
+
description:
|
|
116
|
+
'Image source: an HTTP/HTTPS URL, a local file path, or a base64 data URL',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
required: ['prompt', 'image_source'],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
126
|
+
const { name, arguments: args } = request.params;
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
if (name === 'web_search') {
|
|
130
|
+
const { query } = args;
|
|
131
|
+
if (!query) {
|
|
132
|
+
return { content: [{ type: 'text', text: 'Error: "query" parameter is required.' }], isError: true };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = await callProxy('/tools/web_search', { query });
|
|
136
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (name === 'understand_image') {
|
|
140
|
+
const { prompt, image_source } = args;
|
|
141
|
+
if (!prompt || !image_source) {
|
|
142
|
+
return {
|
|
143
|
+
content: [{ type: 'text', text: 'Error: "prompt" and "image_source" parameters are required.' }],
|
|
144
|
+
isError: true,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const imageUrl = await processImageSource(image_source);
|
|
149
|
+
const result = await callProxy('/tools/understand_image', { prompt, image_url: imageUrl });
|
|
150
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
154
|
+
} catch (err) {
|
|
155
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
async function main() {
|
|
160
|
+
const transport = new StdioServerTransport();
|
|
161
|
+
await server.connect(transport);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
main().catch((err) => {
|
|
165
|
+
console.error('Fatal error starting OpusCode MCP server:', err);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opuscode-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpusCode MCP server — web search and image understanding tools for AI coding agents",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"opuscode-mcp": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": ["mcp", "opuscode", "web-search", "image-understanding", "ai-coding"],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.0.0"
|
|
16
|
+
}
|
|
17
|
+
}
|