@apresai/gimage-mcp 1.2.133 → 1.2.137
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 +56 -0
- package/gimage-mcp.js +92 -0
- package/package.json +6 -5
- package/scripts/install.js +72 -80
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @apresai/gimage-mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for AI-powered image generation and processing,
|
|
4
|
+
backed by the [`gimage`](https://github.com/apresai/gimage) CLI. Exposes image
|
|
5
|
+
generation (Gemini, Vertex AI, AWS Bedrock, xAI Grok) plus resize/scale/crop/compress/
|
|
6
|
+
convert and batch operations as MCP tools for Claude Desktop and other MCP clients.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g @apresai/gimage-mcp
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This package is a thin Node wrapper. On install it downloads the platform-native
|
|
15
|
+
`gimage` binary from the matching GitHub release into the package. If your package
|
|
16
|
+
manager blocks postinstall scripts, the binary is fetched automatically on first run,
|
|
17
|
+
or it falls back to a `gimage` already on your `PATH` (e.g. installed via Homebrew:
|
|
18
|
+
`brew install apresai/tap/gimage`).
|
|
19
|
+
|
|
20
|
+
## Use with Claude Desktop
|
|
21
|
+
|
|
22
|
+
Add to your MCP configuration
|
|
23
|
+
(`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"gimage": {
|
|
29
|
+
"command": "npx",
|
|
30
|
+
"args": ["-y", "@apresai/gimage-mcp"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Running `gimage-mcp` with no arguments starts the MCP server over stdio. Any arguments
|
|
37
|
+
are forwarded to the underlying `gimage` CLI, so `gimage-mcp --version` prints the
|
|
38
|
+
version and `gimage-mcp serve` is equivalent to the default.
|
|
39
|
+
|
|
40
|
+
## Tools
|
|
41
|
+
|
|
42
|
+
`generate_image`, `resize_image`, `scale_image`, `crop_image`, `compress_image`,
|
|
43
|
+
`convert_image`, `batch_resize`, `batch_compress`, `batch_convert`, `list_models`.
|
|
44
|
+
|
|
45
|
+
## Configure API keys
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
gimage auth setup gemini
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
See the [main project README](https://github.com/apresai/gimage#readme) for full
|
|
52
|
+
documentation, supported models, and pricing.
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
package/gimage-mcp.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn, spawnSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
// The platform gimage binary lives beside this wrapper at <package>/bin/gimage.
|
|
8
|
+
function packageBinaryPath() {
|
|
9
|
+
const ext = process.platform === 'win32' ? '.exe' : '';
|
|
10
|
+
return path.join(__dirname, 'bin', `gimage${ext}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Resolve a system-installed gimage (e.g. via Homebrew) on PATH to its full
|
|
14
|
+
// path, or null if absent. Returning the resolved path (rather than the bare
|
|
15
|
+
// name) lets spawn launch it directly — important on Windows, where
|
|
16
|
+
// spawn('gimage') without a shell does not append .exe / search PATHEXT.
|
|
17
|
+
function systemGimagePath() {
|
|
18
|
+
const probe = process.platform === 'win32' ? 'where' : 'which';
|
|
19
|
+
const result = spawnSync(probe, ['gimage'], { encoding: 'utf8' });
|
|
20
|
+
if (result.status !== 0 || !result.stdout) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
// `where` may list several matches; take the first line.
|
|
24
|
+
const first = result.stdout.split(/\r?\n/)[0].trim();
|
|
25
|
+
return first || null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Resolve the gimage binary, in order of preference:
|
|
29
|
+
// 1. the version-pinned binary bundled into this package (downloaded by the
|
|
30
|
+
// postinstall hook),
|
|
31
|
+
// 2. a system gimage on PATH (Homebrew, manual install),
|
|
32
|
+
// 3. a lazy download into the package bin/ — this covers the case where npm's
|
|
33
|
+
// allow-scripts gate blocked postinstall so the binary was never fetched.
|
|
34
|
+
async function resolveBinary() {
|
|
35
|
+
const pkgBinary = packageBinaryPath();
|
|
36
|
+
if (fs.existsSync(pkgBinary)) {
|
|
37
|
+
return pkgBinary;
|
|
38
|
+
}
|
|
39
|
+
const systemBinary = systemGimagePath();
|
|
40
|
+
if (systemBinary) {
|
|
41
|
+
return systemBinary;
|
|
42
|
+
}
|
|
43
|
+
// install.js logs progress to stderr, so this is safe to run right before
|
|
44
|
+
// launching the MCP server (whose stdout is the JSON-RPC channel).
|
|
45
|
+
const { ensureBinary } = require('./scripts/install.js');
|
|
46
|
+
return ensureBinary();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function main() {
|
|
50
|
+
let binaryPath;
|
|
51
|
+
try {
|
|
52
|
+
binaryPath = await resolveBinary();
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Failed to locate or download the gimage binary:', error.message);
|
|
55
|
+
console.error('\nInstall gimage manually:');
|
|
56
|
+
console.error(' npm install -g @apresai/gimage-mcp');
|
|
57
|
+
console.error(' or');
|
|
58
|
+
console.error(' brew install apresai/tap/gimage');
|
|
59
|
+
console.error('\nSee: https://github.com/apresai/gimage');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Forward any args straight through to gimage. With no args, default to the MCP
|
|
64
|
+
// server so `npx -y @apresai/gimage-mcp` launches it for Claude Desktop, while
|
|
65
|
+
// `gimage-mcp --version`, `gimage-mcp serve`, etc. pass through unchanged.
|
|
66
|
+
const forwarded = process.argv.slice(2);
|
|
67
|
+
const args = forwarded.length > 0 ? forwarded : ['serve'];
|
|
68
|
+
|
|
69
|
+
const child = spawn(binaryPath, args, {
|
|
70
|
+
stdio: 'inherit',
|
|
71
|
+
env: process.env
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
child.on('error', (error) => {
|
|
75
|
+
console.error('Failed to start gimage:', error.message);
|
|
76
|
+
console.error('\nPlease ensure gimage is installed:');
|
|
77
|
+
console.error(' npm install -g @apresai/gimage-mcp');
|
|
78
|
+
console.error(' or');
|
|
79
|
+
console.error(' brew install apresai/tap/gimage');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
child.on('exit', (code) => {
|
|
84
|
+
process.exit(code || 0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Forward termination signals for graceful shutdown.
|
|
88
|
+
process.on('SIGINT', () => child.kill('SIGINT'));
|
|
89
|
+
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apresai/gimage-mcp",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.137",
|
|
4
4
|
"description": "MCP server for AI-powered image generation and processing with gimage",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -26,13 +26,14 @@
|
|
|
26
26
|
"url": "https://github.com/apresai/gimage/issues"
|
|
27
27
|
},
|
|
28
28
|
"bin": {
|
|
29
|
-
"gimage-mcp": "./
|
|
29
|
+
"gimage-mcp": "./gimage-mcp.js"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"postinstall": "node scripts/install.js"
|
|
32
|
+
"postinstall": "node scripts/install.js",
|
|
33
|
+
"test": "node --test"
|
|
33
34
|
},
|
|
34
35
|
"engines": {
|
|
35
|
-
"node": ">=
|
|
36
|
+
"node": ">=22.0.0"
|
|
36
37
|
},
|
|
37
38
|
"os": [
|
|
38
39
|
"darwin",
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"arm64"
|
|
45
46
|
],
|
|
46
47
|
"files": [
|
|
47
|
-
"
|
|
48
|
+
"gimage-mcp.js",
|
|
48
49
|
"scripts/",
|
|
49
50
|
"README.md"
|
|
50
51
|
],
|
package/scripts/install.js
CHANGED
|
@@ -6,7 +6,10 @@ const path = require('path');
|
|
|
6
6
|
const tar = require('tar');
|
|
7
7
|
|
|
8
8
|
const GITHUB_REPO = 'apresai/gimage';
|
|
9
|
-
|
|
9
|
+
// npm sets npm_package_version during the install lifecycle (postinstall). When
|
|
10
|
+
// this module is require()'d at runtime by the bin wrapper that env var is unset,
|
|
11
|
+
// so fall back to the package's own version.
|
|
12
|
+
const VERSION = process.env.npm_package_version || require('../package.json').version;
|
|
10
13
|
|
|
11
14
|
function getPlatformInfo() {
|
|
12
15
|
const platform = process.platform;
|
|
@@ -27,7 +30,11 @@ function getPlatformInfo() {
|
|
|
27
30
|
const mappedArch = archMap[arch];
|
|
28
31
|
|
|
29
32
|
if (!mappedPlatform || !mappedArch) {
|
|
30
|
-
throw new Error(
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Unsupported platform: ${platform}-${arch}. ` +
|
|
35
|
+
`Install gimage manually from https://github.com/${GITHUB_REPO}/releases ` +
|
|
36
|
+
`or via Homebrew: brew install apresai/tap/gimage`
|
|
37
|
+
);
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
return {
|
|
@@ -37,6 +44,13 @@ function getPlatformInfo() {
|
|
|
37
44
|
};
|
|
38
45
|
}
|
|
39
46
|
|
|
47
|
+
// binaryPath returns where the platform gimage binary lives (or will live) inside
|
|
48
|
+
// the package: <package>/bin/gimage[.exe].
|
|
49
|
+
function binaryPath() {
|
|
50
|
+
const { ext } = getPlatformInfo();
|
|
51
|
+
return path.join(__dirname, '..', 'bin', `gimage${ext}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
40
54
|
async function downloadBinary() {
|
|
41
55
|
const { platform, arch, ext } = getPlatformInfo();
|
|
42
56
|
const binaryName = `gimage${ext}`;
|
|
@@ -51,100 +65,74 @@ async function downloadBinary() {
|
|
|
51
65
|
const url = `https://github.com/${GITHUB_REPO}/releases/download/v${VERSION}/${tarballName}`;
|
|
52
66
|
|
|
53
67
|
const binDir = path.join(__dirname, '..', 'bin');
|
|
54
|
-
const
|
|
68
|
+
const binaryDest = path.join(binDir, binaryName);
|
|
55
69
|
|
|
56
70
|
// Create bin directory if it doesn't exist
|
|
57
71
|
if (!fs.existsSync(binDir)) {
|
|
58
72
|
fs.mkdirSync(binDir, { recursive: true });
|
|
59
73
|
}
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
// Progress goes to stderr so it never corrupts the MCP server's stdout
|
|
76
|
+
// JSON-RPC stream when this runs as a lazy download just before `gimage serve`.
|
|
77
|
+
console.error(`Downloading gimage binary for ${platform}-${arch}...`);
|
|
78
|
+
console.error(`URL: ${url}`);
|
|
79
|
+
|
|
80
|
+
function extractFrom(response, resolve, reject) {
|
|
81
|
+
const tarPath = path.join(binDir, tarballName);
|
|
82
|
+
const file = fs.createWriteStream(tarPath);
|
|
83
|
+
response.pipe(file);
|
|
84
|
+
file.on('finish', async () => {
|
|
85
|
+
file.close();
|
|
86
|
+
try {
|
|
87
|
+
await tar.x({ file: tarPath, cwd: binDir });
|
|
88
|
+
fs.unlinkSync(tarPath);
|
|
89
|
+
if (platform !== 'windows') {
|
|
90
|
+
fs.chmodSync(binaryDest, 0o755);
|
|
91
|
+
}
|
|
92
|
+
console.error('✓ gimage binary installed successfully');
|
|
93
|
+
resolve();
|
|
94
|
+
} catch (err) {
|
|
95
|
+
reject(err);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
file.on('error', reject);
|
|
99
|
+
}
|
|
63
100
|
|
|
64
101
|
return new Promise((resolve, reject) => {
|
|
65
102
|
https.get(url, (response) => {
|
|
66
103
|
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
67
|
-
// Follow redirect
|
|
104
|
+
// Follow redirect (GitHub release assets redirect to a CDN)
|
|
68
105
|
https.get(response.headers.location, (redirectResponse) => {
|
|
69
106
|
if (redirectResponse.statusCode !== 200) {
|
|
70
|
-
reject(new Error(`Download failed with status ${redirectResponse.statusCode}`));
|
|
107
|
+
reject(new Error(`Download failed with status ${redirectResponse.statusCode} for ${url}`));
|
|
71
108
|
return;
|
|
72
109
|
}
|
|
73
|
-
|
|
74
|
-
const tarPath = path.join(binDir, tarballName);
|
|
75
|
-
const file = fs.createWriteStream(tarPath);
|
|
76
|
-
|
|
77
|
-
redirectResponse.pipe(file);
|
|
78
|
-
|
|
79
|
-
file.on('finish', async () => {
|
|
80
|
-
file.close();
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
// Extract tarball
|
|
84
|
-
await tar.x({
|
|
85
|
-
file: tarPath,
|
|
86
|
-
cwd: binDir
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Remove tarball
|
|
90
|
-
fs.unlinkSync(tarPath);
|
|
91
|
-
|
|
92
|
-
// Make binary executable (Unix-like systems)
|
|
93
|
-
if (platform !== 'windows') {
|
|
94
|
-
fs.chmodSync(binaryPath, 0o755);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
console.log('✓ gimage binary installed successfully');
|
|
98
|
-
resolve();
|
|
99
|
-
} catch (err) {
|
|
100
|
-
reject(err);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
file.on('error', reject);
|
|
110
|
+
extractFrom(redirectResponse, resolve, reject);
|
|
105
111
|
}).on('error', reject);
|
|
106
112
|
} else if (response.statusCode === 200) {
|
|
107
|
-
|
|
108
|
-
const file = fs.createWriteStream(tarPath);
|
|
109
|
-
|
|
110
|
-
response.pipe(file);
|
|
111
|
-
|
|
112
|
-
file.on('finish', async () => {
|
|
113
|
-
file.close();
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
// Extract tarball
|
|
117
|
-
await tar.x({
|
|
118
|
-
file: tarPath,
|
|
119
|
-
cwd: binDir
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Remove tarball
|
|
123
|
-
fs.unlinkSync(tarPath);
|
|
124
|
-
|
|
125
|
-
// Make binary executable (Unix-like systems)
|
|
126
|
-
if (platform !== 'windows') {
|
|
127
|
-
fs.chmodSync(binaryPath, 0o755);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
console.log('✓ gimage binary installed successfully');
|
|
131
|
-
resolve();
|
|
132
|
-
} catch (err) {
|
|
133
|
-
reject(err);
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
file.on('error', reject);
|
|
113
|
+
extractFrom(response, resolve, reject);
|
|
138
114
|
} else {
|
|
139
|
-
reject(new Error(`Download failed with status ${response.statusCode}`));
|
|
115
|
+
reject(new Error(`Download failed with status ${response.statusCode} for ${url}`));
|
|
140
116
|
}
|
|
141
117
|
}).on('error', reject);
|
|
142
118
|
});
|
|
143
119
|
}
|
|
144
120
|
|
|
121
|
+
// ensureBinary is idempotent: it returns the path to the platform binary,
|
|
122
|
+
// downloading it only if it is not already present. Safe to call from both the
|
|
123
|
+
// postinstall hook and the runtime bin wrapper.
|
|
124
|
+
async function ensureBinary() {
|
|
125
|
+
const target = binaryPath();
|
|
126
|
+
if (fs.existsSync(target)) {
|
|
127
|
+
return target;
|
|
128
|
+
}
|
|
129
|
+
await downloadBinary();
|
|
130
|
+
return target;
|
|
131
|
+
}
|
|
132
|
+
|
|
145
133
|
async function main() {
|
|
146
134
|
try {
|
|
147
|
-
await
|
|
135
|
+
await ensureBinary();
|
|
148
136
|
console.log('\n✓ Installation complete!');
|
|
149
137
|
console.log('\nTo use with Claude Desktop, add this to your MCP configuration:');
|
|
150
138
|
console.log('\nmacOS: ~/Library/Application Support/Claude/claude_desktop_config.json');
|
|
@@ -159,16 +147,20 @@ async function main() {
|
|
|
159
147
|
console.log(' }');
|
|
160
148
|
console.log('}');
|
|
161
149
|
console.log('\nBefore using, configure your API keys:');
|
|
162
|
-
console.log(' gimage auth gemini');
|
|
150
|
+
console.log(' gimage auth setup gemini');
|
|
163
151
|
console.log('\nFor more information: https://github.com/apresai/gimage');
|
|
164
152
|
} catch (error) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
console.error('
|
|
168
|
-
console.error('
|
|
169
|
-
console.error('
|
|
170
|
-
|
|
153
|
+
// Non-fatal: the bin wrapper will lazily retry the download (or fall back to
|
|
154
|
+
// a system gimage on PATH) on first run, so don't fail the whole install.
|
|
155
|
+
console.error('Postinstall could not download the gimage binary:', error.message);
|
|
156
|
+
console.error('It will be fetched automatically on first run, or install manually:');
|
|
157
|
+
console.error('1. From releases: https://github.com/' + GITHUB_REPO + '/releases');
|
|
158
|
+
console.error('2. Via Homebrew: brew install apresai/tap/gimage');
|
|
171
159
|
}
|
|
172
160
|
}
|
|
173
161
|
|
|
174
|
-
|
|
162
|
+
module.exports = { ensureBinary, downloadBinary, binaryPath, getPlatformInfo };
|
|
163
|
+
|
|
164
|
+
if (require.main === module) {
|
|
165
|
+
main();
|
|
166
|
+
}
|