agent-avatar-mcp 1.0.0 → 1.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.
- package/README.md +12 -12
- package/dist/generate.js +48 -43
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -21,9 +21,7 @@ Each AI agent has a **DNA** — a detailed description of their human physical a
|
|
|
21
21
|
## Prerequisites
|
|
22
22
|
|
|
23
23
|
- **Node.js** >= 18
|
|
24
|
-
- **
|
|
25
|
-
- **Google Gemini API Key** (`GEMINI_API_KEY`)
|
|
26
|
-
- **`uv`** (recommended) or Python with `google-genai` and `pillow` installed
|
|
24
|
+
- **Google Gemini API Key** (`GEMINI_API_KEY`) — the only external dependency
|
|
27
25
|
|
|
28
26
|
---
|
|
29
27
|
|
|
@@ -68,10 +66,9 @@ Each AI agent has a **DNA** — a detailed description of their human physical a
|
|
|
68
66
|
### Environment variables
|
|
69
67
|
|
|
70
68
|
| Variable | Required | Description |
|
|
71
|
-
|
|
69
|
+
| --- | --- | --- |
|
|
72
70
|
| `AGENT_NAME` | Recommended | Agent name/handle. If omitted and only one agent is configured, it is auto-detected. |
|
|
73
|
-
| `
|
|
74
|
-
| `GEMINI_API_KEY` | Yes | Google Gemini API key used by the image generator |
|
|
71
|
+
| `GEMINI_API_KEY` | Yes | Google Gemini API key for image generation |
|
|
75
72
|
| `AVATAR_OUTPUT_DIR` | No | Where generated images are saved. Default: `~/.agent-avatar/generated/` |
|
|
76
73
|
|
|
77
74
|
---
|
|
@@ -80,27 +77,30 @@ Each AI agent has a **DNA** — a detailed description of their human physical a
|
|
|
80
77
|
|
|
81
78
|
### Initial setup (run once)
|
|
82
79
|
|
|
83
|
-
```
|
|
80
|
+
```text
|
|
84
81
|
1. read_identity_files → reads your soul.md / persona files to extract appearance
|
|
85
82
|
2. save_dna → saves your human visual DNA
|
|
86
83
|
3. generate_reference → generates reference portrait (front, neutral, three_quarter, side)
|
|
87
84
|
```
|
|
88
85
|
|
|
89
86
|
Or, if you already have a photo:
|
|
90
|
-
|
|
87
|
+
|
|
88
|
+
```text
|
|
91
89
|
3. set_reference_image → registers an existing photo as reference for a given angle
|
|
92
90
|
```
|
|
93
91
|
|
|
94
92
|
### Generating photos
|
|
95
93
|
|
|
96
94
|
**Normal photo:**
|
|
97
|
-
|
|
95
|
+
|
|
96
|
+
```text
|
|
98
97
|
generate_image
|
|
99
98
|
scene: "selfie at the beach at sunset"
|
|
100
99
|
```
|
|
101
100
|
|
|
102
101
|
**Sponsored post (agent + product):**
|
|
103
|
-
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
104
|
generate_image
|
|
105
105
|
scene: "holding the bottle in a luxury bathroom mirror"
|
|
106
106
|
product_name: "Chanel No.5"
|
|
@@ -113,7 +113,7 @@ generate_image
|
|
|
113
113
|
## Available tools
|
|
114
114
|
|
|
115
115
|
| Tool | Description |
|
|
116
|
-
|
|
116
|
+
| --- | --- |
|
|
117
117
|
| `read_identity_files` | Reads soul.md / persona files to extract your physical appearance |
|
|
118
118
|
| `save_dna` | Saves your visual DNA (human appearance only — never robotic) |
|
|
119
119
|
| `show_dna` | Displays your current DNA and reference image status |
|
|
@@ -128,7 +128,7 @@ generate_image
|
|
|
128
128
|
## Supported scenarios
|
|
129
129
|
|
|
130
130
|
| Scenario | Supported |
|
|
131
|
-
|
|
131
|
+
| --- | --- |
|
|
132
132
|
| Agent alone in any scene | ✅ |
|
|
133
133
|
| Agent featuring a physical product | ✅ |
|
|
134
134
|
| Two agents in the same scene | ⚠️ Approximate (no precise likeness for secondary person) |
|
package/dist/generate.js
CHANGED
|
@@ -1,16 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync, mkdirSync } from "fs";
|
|
1
|
+
import { GoogleGenAI } from "@google/genai";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
|
-
const SCRIPT_PATH = process.env.NANO_BANANA_SCRIPT ??
|
|
6
|
-
join(homedir(), ".openclaw", "skills", "nano-banana-pro", "scripts", "generate_image.py");
|
|
7
5
|
const OUTPUT_DIR = process.env.AVATAR_OUTPUT_DIR ??
|
|
8
6
|
join(homedir(), ".agent-avatar", "generated");
|
|
7
|
+
const MODEL = "gemini-3-pro-image-preview";
|
|
9
8
|
export function ensureOutputDir() {
|
|
10
9
|
if (!existsSync(OUTPUT_DIR))
|
|
11
10
|
mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
12
11
|
return OUTPUT_DIR;
|
|
13
12
|
}
|
|
13
|
+
function getClient() {
|
|
14
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
throw new Error("GEMINI_API_KEY environment variable is required.\n" +
|
|
17
|
+
"Set it in your MCP server config under 'env'.");
|
|
18
|
+
}
|
|
19
|
+
return new GoogleGenAI({ apiKey });
|
|
20
|
+
}
|
|
21
|
+
function imageToInlinePart(imagePath) {
|
|
22
|
+
const ext = imagePath.toLowerCase().split(".").pop() ?? "png";
|
|
23
|
+
const mimeTypes = {
|
|
24
|
+
png: "image/png",
|
|
25
|
+
jpg: "image/jpeg",
|
|
26
|
+
jpeg: "image/jpeg",
|
|
27
|
+
webp: "image/webp",
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
inlineData: {
|
|
31
|
+
mimeType: mimeTypes[ext] ?? "image/png",
|
|
32
|
+
data: readFileSync(imagePath).toString("base64"),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
14
36
|
export function buildConsistencyPrompt(dna, sceneDescription, hasReference, product) {
|
|
15
37
|
const productBlock = product
|
|
16
38
|
? [
|
|
@@ -32,7 +54,6 @@ export function buildConsistencyPrompt(dna, sceneDescription, hasReference, prod
|
|
|
32
54
|
productBlock,
|
|
33
55
|
].join("\n");
|
|
34
56
|
}
|
|
35
|
-
// First generation — full DNA description
|
|
36
57
|
return [
|
|
37
58
|
`Ultra-realistic portrait photography. No artistic style. No illustration.`,
|
|
38
59
|
``,
|
|
@@ -51,47 +72,31 @@ export function buildConsistencyPrompt(dna, sceneDescription, hasReference, prod
|
|
|
51
72
|
].join("\n");
|
|
52
73
|
}
|
|
53
74
|
export async function generateImage(prompt, outputFilename, referenceImages = []) {
|
|
54
|
-
|
|
55
|
-
throw new Error(`Nano Banana Pro script not found at: ${SCRIPT_PATH}\n` +
|
|
56
|
-
`Set NANO_BANANA_SCRIPT env var to the correct path.`);
|
|
57
|
-
}
|
|
75
|
+
const client = getClient();
|
|
58
76
|
const outDir = ensureOutputDir();
|
|
59
77
|
const outputPath = join(outDir, outputFilename);
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let mediaPath = "";
|
|
73
|
-
let stderr = "";
|
|
74
|
-
proc.stdout.on("data", (data) => {
|
|
75
|
-
const line = data.toString();
|
|
76
|
-
if (line.includes("MEDIA:")) {
|
|
77
|
-
mediaPath = line.replace("MEDIA:", "").trim();
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
proc.stderr.on("data", (data) => {
|
|
81
|
-
stderr += data.toString();
|
|
82
|
-
});
|
|
83
|
-
proc.on("close", (code) => {
|
|
84
|
-
if (code !== 0) {
|
|
85
|
-
reject(new Error(`Image generation failed (exit ${code}):\n${stderr}`));
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
resolve(mediaPath || outputPath);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
proc.on("error", (err) => {
|
|
92
|
-
reject(new Error(`Failed to spawn image generator: ${err.message}\nTry installing uv: winget install astral-sh.uv`));
|
|
93
|
-
});
|
|
78
|
+
// Build parts: reference images first (anchor), then prompt text
|
|
79
|
+
const parts = [
|
|
80
|
+
...referenceImages.map(imageToInlinePart),
|
|
81
|
+
{ text: prompt },
|
|
82
|
+
];
|
|
83
|
+
const response = await client.models.generateContent({
|
|
84
|
+
model: MODEL,
|
|
85
|
+
contents: [{ role: "user", parts }],
|
|
86
|
+
config: {
|
|
87
|
+
responseModalities: ["IMAGE"],
|
|
88
|
+
imageConfig: { imageSize: "1K" },
|
|
89
|
+
},
|
|
94
90
|
});
|
|
91
|
+
const responseParts = response.candidates?.[0]?.content?.parts ?? [];
|
|
92
|
+
for (const part of responseParts) {
|
|
93
|
+
if (part.inlineData?.data) {
|
|
94
|
+
const imageBuffer = Buffer.from(part.inlineData.data, "base64");
|
|
95
|
+
writeFileSync(outputPath, imageBuffer);
|
|
96
|
+
return outputPath;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
throw new Error("No image was generated in the response. Check your GEMINI_API_KEY and model availability.");
|
|
95
100
|
}
|
|
96
101
|
export function makeFilename(agentName, scene) {
|
|
97
102
|
const slug = scene.toLowerCase().replace(/[^a-z0-9]/g, "-").slice(0, 30);
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-avatar-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MCP Server — visual identity and self-portrait generation for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"agent-avatar-mcp": "
|
|
7
|
+
"agent-avatar-mcp": "dist/index.js"
|
|
8
8
|
},
|
|
9
|
-
"files": [
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
10
13
|
"scripts": {
|
|
11
14
|
"build": "tsc",
|
|
12
15
|
"dev": "tsx src/index.ts",
|
|
@@ -15,13 +18,16 @@
|
|
|
15
18
|
"prepublishOnly": "npm run build"
|
|
16
19
|
},
|
|
17
20
|
"dependencies": {
|
|
21
|
+
"@google/genai": "^1.45.0",
|
|
18
22
|
"@modelcontextprotocol/sdk": "^1.5.0"
|
|
19
23
|
},
|
|
20
24
|
"devDependencies": {
|
|
21
|
-
"typescript": "^5.4.0",
|
|
22
25
|
"@types/node": "^20.0.0",
|
|
23
|
-
"tsx": "^4.0.0"
|
|
26
|
+
"tsx": "^4.0.0",
|
|
27
|
+
"typescript": "^5.4.0"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
24
31
|
},
|
|
25
|
-
"engines": { "node": ">=18" },
|
|
26
32
|
"license": "MIT"
|
|
27
33
|
}
|