Skip to content

Commit 5f354dd

Browse files
committed
[ACTION] Extend Vertex AI MCP server with Veo3 support
1 parent fe69b0d commit 5f354dd

File tree

9 files changed

+380
-13
lines changed

9 files changed

+380
-13
lines changed

components/google_vertex_ai/actions/analyze-image-video/analyze-image-video.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default {
44
key: "google_vertex_ai-analyze-image-video",
55
name: "Analyze Image/Video",
66
description: "Examines an image or video following given instructions. Results will contain the analysis findings. [See the documentation](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.publishers.models/generateContent)",
7-
version: "0.0.1",
7+
version: "0.0.2",
88
type: "action",
99
props: {
1010
vertexAi,

components/google_vertex_ai/actions/analyze-text-sentiment/analyze-text-sentiment.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default {
44
key: "google_vertex_ai-analyze-text-sentiment",
55
name: "Analyze Text Sentiment",
66
description: "Analyzes a specified text for its underlying sentiment. [See the documentation](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.publishers.models/generateContent)",
7-
version: "0.0.1",
7+
version: "0.0.2",
88
type: "action",
99
props: {
1010
vertexAi,

components/google_vertex_ai/actions/classify-text/classify-text.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default {
44
key: "google_vertex_ai-classify-text",
55
name: "Classify Text",
66
description: "Groups a provided text into predefined categories. [See the documentation](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.publishers.models/generateContent)",
7-
version: "0.0.1",
7+
version: "0.0.2",
88
type: "action",
99
props: {
1010
vertexAi,
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import app from "../../google_vertex_ai.app.mjs";
2+
3+
export default {
4+
props: {
5+
app,
6+
projectId: {
7+
propDefinition: [
8+
app,
9+
"projectId",
10+
],
11+
},
12+
model: {
13+
propDefinition: [
14+
app,
15+
"model",
16+
],
17+
},
18+
prompt: {
19+
type: "string",
20+
label: "Prompt",
21+
description: "The text prompt to guide video generation. For Veo 3, you can include audio cues like dialogue in quotes, sound effects, and ambient noise descriptions.",
22+
},
23+
aspectRatio: {
24+
type: "string",
25+
label: "Aspect Ratio",
26+
description: "The aspect ratio of the generated video",
27+
options: [
28+
{
29+
label: "16:9 (Landscape)",
30+
value: "16:9",
31+
},
32+
{
33+
label: "9:16 (Portrait) - Veo 2 only",
34+
value: "9:16",
35+
},
36+
],
37+
default: "16:9",
38+
},
39+
durationSeconds: {
40+
type: "integer",
41+
label: "Duration (seconds)",
42+
description: "The length of the video in seconds. Veo 2: 5-8 seconds, Veo 3: 8 seconds",
43+
default: 8,
44+
min: 5,
45+
max: 8,
46+
},
47+
enhancePrompt: {
48+
type: "boolean",
49+
label: "Enhance Prompt",
50+
description: "Use Gemini to enhance your prompts",
51+
default: true,
52+
},
53+
generateAudio: {
54+
type: "boolean",
55+
label: "Generate Audio",
56+
description: "Generate audio for the video (Veo 3 only)",
57+
default: true,
58+
},
59+
negativePrompt: {
60+
type: "string",
61+
label: "Negative Prompt",
62+
description: "A text string that describes anything you want to discourage the model from generating",
63+
optional: true,
64+
},
65+
personGeneration: {
66+
type: "string",
67+
label: "Person Generation",
68+
description: "The safety setting that controls whether people or face generation is allowed",
69+
options: [
70+
{
71+
label: "Allow Adult",
72+
value: "allow_adult",
73+
},
74+
{
75+
label: "Don't Allow",
76+
value: "dont_allow",
77+
},
78+
],
79+
default: "allow_adult",
80+
optional: true,
81+
},
82+
resolution: {
83+
type: "string",
84+
label: "Resolution",
85+
description: "The resolution of the generated video (Veo 3 models only)",
86+
options: [
87+
{
88+
label: "720p",
89+
value: "720p",
90+
},
91+
{
92+
label: "1080p",
93+
value: "1080p",
94+
},
95+
],
96+
default: "720p",
97+
optional: true,
98+
},
99+
sampleCount: {
100+
type: "integer",
101+
label: "Sample Count",
102+
description: "The number of output videos requested",
103+
default: 1,
104+
min: 1,
105+
max: 4,
106+
},
107+
storageUri: {
108+
type: "string",
109+
label: "Storage URI",
110+
description: "A Cloud Storage bucket URI to store the output video, in the format `gs://BUCKET_NAME/SUBDIRECTORY`. If a Cloud Storage bucket isn't provided, base64-encoded video bytes are returned in the response.",
111+
optional: true,
112+
},
113+
},
114+
methods: {
115+
async pollOperation({
116+
$, projectId, model, operationName,
117+
}) {
118+
const pollInterval = 45000; // 45 seconds
119+
const maxAttempts = 6;
120+
121+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
122+
try {
123+
const response = await this.app.fetchOperation({
124+
$,
125+
projectId,
126+
model,
127+
data: {
128+
operationName,
129+
},
130+
});
131+
132+
if (response.done) {
133+
return response;
134+
}
135+
136+
console.log(`Video generation in progress... (attempt ${attempt}/${maxAttempts})`);
137+
138+
if (attempt < maxAttempts) {
139+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
140+
}
141+
} catch (error) {
142+
throw new Error(`Error polling operation: ${error.message}`);
143+
}
144+
}
145+
146+
throw new Error(`Video generation not completed after ${maxAttempts} polling attempts`);
147+
},
148+
async streamToBase64(stream) {
149+
return new Promise((resolve, reject) => {
150+
const chunks = [];
151+
stream.on("data", (chunk) => chunks.push(chunk));
152+
stream.on("end", () => {
153+
const buffer = Buffer.concat(chunks);
154+
resolve(buffer.toString("base64"));
155+
});
156+
stream.on("error", reject);
157+
});
158+
},
159+
},
160+
};
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { getFileStreamAndMetadata } from "@pipedream/platform";
2+
import common from "../common/generate-video.mjs";
3+
4+
export default {
5+
...common,
6+
key: "google_vertex_ai-generate-video-from-image",
7+
name: "Generate Video from Image",
8+
description: "Generate a video from an image with optional text prompt using Google Vertex AI Veo models. [See the documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/veo-video-generation)",
9+
version: "0.0.1",
10+
type: "action",
11+
props: {
12+
...common.props,
13+
image: {
14+
type: "string",
15+
label: "Image Path Or URL",
16+
description: "Provide either a file URL or a path to a file in the `/tmp` directory (for example, `/tmp/image.jpg`). Supported formats: JPEG, PNG. For best quality, use 720p or higher (1280 x 720 pixels) with 16:9 or 9:16 aspect ratio.",
17+
},
18+
},
19+
async run({ $ }) {
20+
const {
21+
projectId,
22+
model,
23+
image,
24+
prompt,
25+
aspectRatio,
26+
durationSeconds,
27+
enhancePrompt,
28+
generateAudio,
29+
negativePrompt,
30+
personGeneration,
31+
resolution,
32+
sampleCount,
33+
storageUri,
34+
} = this;
35+
36+
const {
37+
stream, metadata,
38+
} = await getFileStreamAndMetadata(image);
39+
const imageBase64 = await this.streamToBase64(stream);
40+
41+
const operationResponse = await this.app.generateVideosLongRunning({
42+
$,
43+
projectId,
44+
model,
45+
data: {
46+
instances: [
47+
{
48+
prompt,
49+
image: {
50+
bytesBase64Encoded: imageBase64,
51+
mimeType: metadata.contentType,
52+
},
53+
},
54+
],
55+
parameters: {
56+
aspectRatio,
57+
durationSeconds,
58+
enhancePrompt,
59+
sampleCount,
60+
negativePrompt,
61+
personGeneration,
62+
generateAudio,
63+
resolution,
64+
storageUri,
65+
},
66+
},
67+
});
68+
69+
if (!operationResponse.name) {
70+
throw new Error("Failed to start video generation operation");
71+
}
72+
73+
// Poll the operation until completion
74+
const completedOperation = await this.pollOperation({
75+
$,
76+
projectId,
77+
model,
78+
operationName: operationResponse.name,
79+
});
80+
81+
if (completedOperation.error) {
82+
throw new Error(`Video generation failed: ${JSON.stringify(completedOperation.error)}`);
83+
}
84+
85+
if (!completedOperation.response) {
86+
throw new Error("No response received from completed operation");
87+
}
88+
89+
const videoCount = completedOperation.response?.videos?.length || 0;
90+
91+
$.export("$summary", `Successfully generated ${videoCount} video(s) from image`);
92+
93+
return completedOperation;
94+
},
95+
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import common from "../common/generate-video.mjs";
2+
3+
export default {
4+
...common,
5+
key: "google_vertex_ai-generate-video-from-text",
6+
name: "Generate Video from Text",
7+
description: "Generate a video from a text prompt using Google Vertex AI Veo models. [See the documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/veo-video-generation)",
8+
version: "0.0.1",
9+
type: "action",
10+
async run({ $ }) {
11+
const {
12+
projectId,
13+
model,
14+
prompt,
15+
aspectRatio,
16+
durationSeconds,
17+
enhancePrompt,
18+
generateAudio,
19+
negativePrompt,
20+
personGeneration,
21+
resolution,
22+
sampleCount,
23+
storageUri,
24+
} = this;
25+
26+
const operationResponse = await this.app.generateVideosLongRunning({
27+
$,
28+
projectId,
29+
model,
30+
data: {
31+
instances: [
32+
{
33+
prompt,
34+
},
35+
],
36+
parameters: {
37+
aspectRatio,
38+
durationSeconds,
39+
enhancePrompt,
40+
sampleCount,
41+
negativePrompt,
42+
personGeneration,
43+
generateAudio,
44+
resolution,
45+
storageUri,
46+
},
47+
},
48+
});
49+
50+
if (!operationResponse.name) {
51+
throw new Error("Failed to start video generation operation");
52+
}
53+
54+
// Poll the operation until completion
55+
const completedOperation = await this.pollOperation({
56+
$,
57+
projectId,
58+
model,
59+
operationName: operationResponse.name,
60+
});
61+
62+
if (completedOperation.error) {
63+
throw new Error(`Video generation failed: ${JSON.stringify(completedOperation.error)}`);
64+
}
65+
66+
if (!completedOperation.response) {
67+
throw new Error("No response received from completed operation");
68+
}
69+
70+
const videoCount = completedOperation.response?.videos?.length || 0;
71+
72+
$.export("$summary", `Successfully generated ${videoCount} video(s)`);
73+
74+
return completedOperation;
75+
},
76+
};

0 commit comments

Comments
 (0)