forgemesh-imagegen 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/LICENSE +1 -1
- package/README.md +9 -4
- package/index.js +74 -9
- package/package.json +4 -4
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ Part of the [ForgeMesh](https://github.com/forgemeshlabs/forgemesh) ecosystem
|
|
|
16
16
|
| `generate_pro` | Generate + bg removal + 4x upscale | $0.30 USDC |
|
|
17
17
|
|
|
18
18
|
All tools accept `prompt` (required) and `aspect` (optional: `1:1`, `16:9`, `9:16`, `4:3`).
|
|
19
|
+
`generate_image` also accepts `affiliate_id` for Pyrimid attribution, or uses `PYRIMID_AFFILIATE_ID` from the environment.
|
|
19
20
|
|
|
20
21
|
---
|
|
21
22
|
|
|
@@ -32,7 +33,8 @@ Add to `claude_desktop_config.json`:
|
|
|
32
33
|
"command": "npx",
|
|
33
34
|
"args": ["-y", "forgemesh-imagegen"],
|
|
34
35
|
"env": {
|
|
35
|
-
"WALLET_PRIVATE_KEY": "0x..."
|
|
36
|
+
"WALLET_PRIVATE_KEY": "0x...",
|
|
37
|
+
"PYRIMID_AFFILIATE_ID": "af_your_id"
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -68,6 +70,9 @@ Get USDC on Base: [Coinbase](https://coinbase.com) → Bridge to Base, or buy di
|
|
|
68
70
|
generate_image(prompt="a red panda in a spacesuit", aspect="1:1")
|
|
69
71
|
→ { image_url: "https://...", prompt: "...", aspect: "1:1", tier: "image" }
|
|
70
72
|
|
|
73
|
+
generate_image(prompt="a red panda in a spacesuit", affiliate_id="af_your_id")
|
|
74
|
+
→ routes through Pyrimid product 3 with no extra cost to the caller
|
|
75
|
+
|
|
71
76
|
generate_clean(prompt="a product photo of a ceramic mug")
|
|
72
77
|
→ { image_url: "https://...", tier: "clean" } // transparent PNG
|
|
73
78
|
|
|
@@ -86,14 +91,14 @@ Each tool call makes an HTTP request to the ForgeMesh imagegen service gated by
|
|
|
86
91
|
|
|
87
92
|
- Network: Base mainnet (eip155:8453)
|
|
88
93
|
- Token: USDC
|
|
89
|
-
-
|
|
94
|
+
- Protocol: [x402](https://x402.org)
|
|
90
95
|
|
|
91
96
|
---
|
|
92
97
|
|
|
93
98
|
## Links
|
|
94
99
|
|
|
95
|
-
- [ForgeMesh](https://forgemesh
|
|
96
|
-
- [
|
|
100
|
+
- [ForgeMesh](https://github.com/forgemeshlabs/forgemesh) — ecosystem overview
|
|
101
|
+
- [npm](https://www.npmjs.com/package/forgemesh-imagegen)
|
|
97
102
|
- [x402 Protocol](https://x402.org)
|
|
98
103
|
|
|
99
104
|
---
|
package/index.js
CHANGED
|
@@ -8,9 +8,23 @@ const { x402Client, x402HTTPClient } = require("@x402/core/client");
|
|
|
8
8
|
const { ExactEvmScheme } = require("@x402/evm/exact/client");
|
|
9
9
|
const { toClientEvmSigner } = require("@x402/evm");
|
|
10
10
|
const { privateKeyToAccount } = require("viem/accounts");
|
|
11
|
+
const { createPublicClient, createWalletClient, http, parseAbi } = require("viem");
|
|
12
|
+
const { base } = require("viem/chains");
|
|
11
13
|
|
|
12
14
|
const BASE_URL = "https://imagegen.coinopai.com";
|
|
13
15
|
const VALID_ASPECTS = ["1:1", "16:9", "9:16", "4:3"];
|
|
16
|
+
const PYRIMID_ROUTER = "0xc949AEa380D7b7984806143ddbfE519B03ABd68B";
|
|
17
|
+
const PYRIMID_VENDOR_ID = "0x034604e25078e293d7b181fa23b3f2f6";
|
|
18
|
+
const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
19
|
+
const ROUTER_ABI = parseAbi([
|
|
20
|
+
"function routePayment(bytes16 vendorId, uint256 productId, bytes16 affiliateId, address buyer, uint256 maxPrice) external",
|
|
21
|
+
]);
|
|
22
|
+
const USDC_ABI = parseAbi([
|
|
23
|
+
"function approve(address spender, uint256 amount) external returns (bool)",
|
|
24
|
+
]);
|
|
25
|
+
const PYRIMID_PRODUCTS = {
|
|
26
|
+
"/generate": { productId: 3n, priceUsdc: 100000n },
|
|
27
|
+
};
|
|
14
28
|
|
|
15
29
|
const TOOLS = [
|
|
16
30
|
{
|
|
@@ -21,6 +35,7 @@ const TOOLS = [
|
|
|
21
35
|
properties: {
|
|
22
36
|
prompt: { type: "string", description: "Text description of the image to generate" },
|
|
23
37
|
aspect: { type: "string", enum: VALID_ASPECTS, description: "Aspect ratio — 1:1 (default), 16:9, 9:16, 4:3" },
|
|
38
|
+
affiliate_id: { type: "string", description: "Optional Pyrimid affiliate ID. Affiliate earns a commission from within the listed price — no extra cost to you." },
|
|
24
39
|
},
|
|
25
40
|
required: ["prompt"],
|
|
26
41
|
},
|
|
@@ -82,14 +97,63 @@ function buildHttpClient() {
|
|
|
82
97
|
const account = privateKeyToAccount(pk);
|
|
83
98
|
const signer = toClientEvmSigner(account);
|
|
84
99
|
const coreClient = new x402Client().register("eip155:*", new ExactEvmScheme(signer));
|
|
85
|
-
return new x402HTTPClient(coreClient);
|
|
100
|
+
return { httpClient: new x402HTTPClient(coreClient), account };
|
|
86
101
|
}
|
|
87
102
|
|
|
88
|
-
|
|
103
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
104
|
+
|
|
105
|
+
async function callPyrimid(account, url, product, affiliateId) {
|
|
106
|
+
const transport = http();
|
|
107
|
+
const publicClient = createPublicClient({ chain: base, transport });
|
|
108
|
+
const walletClient = createWalletClient({ account, chain: base, transport });
|
|
109
|
+
|
|
110
|
+
const approveHash = await walletClient.writeContract({
|
|
111
|
+
address: USDC_ADDRESS,
|
|
112
|
+
abi: USDC_ABI,
|
|
113
|
+
functionName: "approve",
|
|
114
|
+
args: [PYRIMID_ROUTER, product.priceUsdc],
|
|
115
|
+
});
|
|
116
|
+
await publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
117
|
+
await sleep(3000);
|
|
118
|
+
|
|
119
|
+
const routeHash = await walletClient.writeContract({
|
|
120
|
+
address: PYRIMID_ROUTER,
|
|
121
|
+
abi: ROUTER_ABI,
|
|
122
|
+
functionName: "routePayment",
|
|
123
|
+
args: [PYRIMID_VENDOR_ID, product.productId, "0x00000000000000000000000000000000", account.address, product.priceUsdc],
|
|
124
|
+
});
|
|
125
|
+
await publicClient.waitForTransactionReceipt({ hash: routeHash });
|
|
126
|
+
await sleep(3000);
|
|
127
|
+
|
|
128
|
+
const paidRes = await fetch(url.toString(), {
|
|
129
|
+
headers: { "X-Affiliate-ID": affiliateId, "X-Payment": routeHash },
|
|
130
|
+
});
|
|
131
|
+
if (!paidRes.ok) {
|
|
132
|
+
const errBody = await paidRes.text().catch(() => paidRes.statusText);
|
|
133
|
+
throw new Error(`Pyrimid payment failed — HTTP ${paidRes.status}: ${errBody.slice(0, 200)}`);
|
|
134
|
+
}
|
|
135
|
+
return paidRes.json();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function callPaid(ctx, path, params, affiliateId) {
|
|
139
|
+
const { httpClient, account } = ctx;
|
|
89
140
|
const url = new URL(BASE_URL + path);
|
|
90
141
|
if (params) Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
91
142
|
|
|
92
|
-
const
|
|
143
|
+
const product = PYRIMID_PRODUCTS[path];
|
|
144
|
+
if (!product) affiliateId = null;
|
|
145
|
+
const extraHeaders = affiliateId ? { "X-Affiliate-ID": affiliateId } : {};
|
|
146
|
+
|
|
147
|
+
if (affiliateId && product) {
|
|
148
|
+
try {
|
|
149
|
+
return await callPyrimid(account, url, product, affiliateId);
|
|
150
|
+
} catch (_) {
|
|
151
|
+
affiliateId = null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const directHeaders = affiliateId ? extraHeaders : {};
|
|
156
|
+
const res = await fetch(url.toString(), { headers: directHeaders });
|
|
93
157
|
|
|
94
158
|
if (res.status === 402) {
|
|
95
159
|
let body;
|
|
@@ -100,7 +164,7 @@ async function callPaid(httpClient, path, params) {
|
|
|
100
164
|
);
|
|
101
165
|
const paymentPayload = await httpClient.createPaymentPayload(paymentRequired);
|
|
102
166
|
const paidRes = await fetch(url.toString(), {
|
|
103
|
-
headers: httpClient.encodePaymentSignatureHeader(paymentPayload),
|
|
167
|
+
headers: { ...httpClient.encodePaymentSignatureHeader(paymentPayload), ...directHeaders },
|
|
104
168
|
});
|
|
105
169
|
if (!paidRes.ok) {
|
|
106
170
|
const errBody = await paidRes.text().catch(() => paidRes.statusText);
|
|
@@ -117,16 +181,16 @@ async function callPaid(httpClient, path, params) {
|
|
|
117
181
|
}
|
|
118
182
|
|
|
119
183
|
async function main() {
|
|
120
|
-
let
|
|
184
|
+
let ctx;
|
|
121
185
|
try {
|
|
122
|
-
|
|
186
|
+
ctx = buildHttpClient();
|
|
123
187
|
} catch (e) {
|
|
124
188
|
process.stderr.write("[forgemesh-imagegen] " + e.message + "\n");
|
|
125
189
|
process.exit(1);
|
|
126
190
|
}
|
|
127
191
|
|
|
128
192
|
const server = new Server(
|
|
129
|
-
{ name: "forgemesh-imagegen", version: "1.0.
|
|
193
|
+
{ name: "forgemesh-imagegen", version: "1.0.1" },
|
|
130
194
|
{ capabilities: { tools: {} } }
|
|
131
195
|
);
|
|
132
196
|
|
|
@@ -147,10 +211,11 @@ async function main() {
|
|
|
147
211
|
throw new Error(`Invalid aspect ratio '${aspect}'. Valid: ${VALID_ASPECTS.join(", ")}`);
|
|
148
212
|
}
|
|
149
213
|
|
|
150
|
-
const
|
|
214
|
+
const affiliateId = args.affiliate_id || process.env.PYRIMID_AFFILIATE_ID || null;
|
|
215
|
+
const data = await callPaid(ctx, endpoint.path, {
|
|
151
216
|
prompt: args.prompt.trim(),
|
|
152
217
|
aspect,
|
|
153
|
-
});
|
|
218
|
+
}, affiliateId);
|
|
154
219
|
|
|
155
220
|
return {
|
|
156
221
|
content: [{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgemesh-imagegen",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"mcpName": "io.github.forgemeshlabs/imagegen-mcp",
|
|
5
5
|
"description": "MCP server for AI image generation — generate, clean (bg removal), HD (4x upscale), Pro. Pay per image in USDC via x402 on Base. No API key needed.",
|
|
6
6
|
"main": "index.js",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"repository": {
|
|
43
43
|
"type": "git",
|
|
44
|
-
"url": "https://github.com/forgemeshlabs/
|
|
44
|
+
"url": "https://github.com/forgemeshlabs/imagegen-mcp.git"
|
|
45
45
|
},
|
|
46
|
-
"homepage": "https://github.com/forgemeshlabs/
|
|
46
|
+
"homepage": "https://github.com/forgemeshlabs/imagegen-mcp",
|
|
47
47
|
"bugs": {
|
|
48
|
-
"url": "https://github.com/forgemeshlabs/
|
|
48
|
+
"url": "https://github.com/forgemeshlabs/imagegen-mcp/issues"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@modelcontextprotocol/sdk": "^1.10.1",
|