Skip to content

Commit 161190d

Browse files
shinprclaude
andcommitted
feat: add Google Search grounding support for real-time information
Add useGoogleSearch parameter to enable Google Search grounding in image generation. This allows the model to access real-time web information for factually accurate image generation when current or time-sensitive data is needed. Changes: - Add useGoogleSearch parameter to generate_image tool - Implement Google Search grounding via tools API - Add comprehensive tests for useGoogleSearch functionality - Update README with useGoogleSearch parameter documentation - Bump version to 0.4.1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent aca7a1b commit 161190d

9 files changed

Lines changed: 256 additions & 5 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ The MCP server exposes a single tool for all image operations. Internally, it us
177177
| `blendImages` | boolean | - | Enable multi-image blending for combining multiple visual elements naturally |
178178
| `maintainCharacterConsistency` | boolean | - | Maintain character appearance consistency across different poses and scenes |
179179
| `useWorldKnowledge` | boolean | - | Use real-world knowledge for accurate context (recommended for historical figures, landmarks, or factual scenarios) |
180+
| `useGoogleSearch` | boolean | - | Enable Google Search grounding to access real-time web information for factually accurate image generation. Use when prompt requires current or time-sensitive data that may have changed since the model's knowledge cutoff. Leave disabled for creative, fictional, historical, or timeless content. |
180181

181182
#### Response
182183

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "mcp-image",
33
"mcpName": "io.github.shinpr/mcp-image",
4-
"version": "0.4.0",
4+
"version": "0.4.1",
55
"description": "MCP server for AI image generation",
66
"main": "dist/index.js",
77
"bin": {

server.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
"url": "https://github.com/shinpr/mcp-image",
99
"source": "github"
1010
},
11-
"version": "0.4.0",
11+
"version": "0.4.1",
1212
"packages": [
1313
{
1414
"registryType": "npm",
1515
"registryBaseUrl": "https://registry.npmjs.org",
1616
"identifier": "mcp-image",
17-
"version": "0.4.0",
17+
"version": "0.4.1",
1818
"transport": {
1919
"type": "stdio"
2020
},

src/api/__tests__/geminiClient.test.ts

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,4 +870,237 @@ describe('geminiClient', () => {
870870
})
871871
})
872872
})
873+
874+
describe('GeminiClient.generateImage with useGoogleSearch', () => {
875+
it('should call API with tools parameter when useGoogleSearch is true', async () => {
876+
// Arrange
877+
const mockResponse = {
878+
response: {
879+
candidates: [
880+
{
881+
content: {
882+
parts: [
883+
{
884+
inlineData: {
885+
data: 'base64-grounded-image-data',
886+
mimeType: 'image/png',
887+
},
888+
},
889+
],
890+
},
891+
},
892+
],
893+
},
894+
}
895+
896+
mockGeminiClientInstance.models.generateContent = vi.fn().mockResolvedValue(mockResponse)
897+
898+
const clientResult = createGeminiClient(testConfig)
899+
expect(clientResult.success).toBe(true)
900+
901+
if (!clientResult.success) return
902+
const client = clientResult.data
903+
904+
// Act
905+
const result = await client.generateImage({
906+
prompt: 'Generate current 2025 weather map of Tokyo',
907+
useGoogleSearch: true,
908+
})
909+
910+
// Assert
911+
expect(result.success).toBe(true)
912+
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
913+
model: 'gemini-3-pro-image-preview',
914+
contents: [
915+
{
916+
parts: [
917+
{
918+
text: 'Generate current 2025 weather map of Tokyo',
919+
},
920+
],
921+
},
922+
],
923+
config: {
924+
responseModalities: ['IMAGE'],
925+
},
926+
tools: [{ googleSearch: {} }],
927+
})
928+
})
929+
930+
it('should not include tools parameter when useGoogleSearch is false', async () => {
931+
// Arrange
932+
const mockResponse = {
933+
response: {
934+
candidates: [
935+
{
936+
content: {
937+
parts: [
938+
{
939+
inlineData: {
940+
data: 'base64-standard-image',
941+
mimeType: 'image/png',
942+
},
943+
},
944+
],
945+
},
946+
},
947+
],
948+
},
949+
}
950+
951+
mockGeminiClientInstance.models.generateContent = vi.fn().mockResolvedValue(mockResponse)
952+
953+
const clientResult = createGeminiClient(testConfig)
954+
expect(clientResult.success).toBe(true)
955+
956+
if (!clientResult.success) return
957+
const client = clientResult.data
958+
959+
// Act
960+
const result = await client.generateImage({
961+
prompt: 'Generate creative fantasy landscape',
962+
useGoogleSearch: false,
963+
})
964+
965+
// Assert
966+
expect(result.success).toBe(true)
967+
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
968+
model: 'gemini-3-pro-image-preview',
969+
contents: [
970+
{
971+
parts: [
972+
{
973+
text: 'Generate creative fantasy landscape',
974+
},
975+
],
976+
},
977+
],
978+
config: {
979+
responseModalities: ['IMAGE'],
980+
},
981+
})
982+
983+
// Verify tools parameter is not included
984+
const callArgs = (mockGeminiClientInstance.models.generateContent as any).mock.calls[0][0]
985+
expect(callArgs.tools).toBeUndefined()
986+
})
987+
988+
it('should not include tools parameter when useGoogleSearch is undefined', async () => {
989+
// Arrange
990+
const mockResponse = {
991+
response: {
992+
candidates: [
993+
{
994+
content: {
995+
parts: [
996+
{
997+
inlineData: {
998+
data: 'base64-default-image',
999+
mimeType: 'image/png',
1000+
},
1001+
},
1002+
],
1003+
},
1004+
},
1005+
],
1006+
},
1007+
}
1008+
1009+
mockGeminiClientInstance.models.generateContent = vi.fn().mockResolvedValue(mockResponse)
1010+
1011+
const clientResult = createGeminiClient(testConfig)
1012+
expect(clientResult.success).toBe(true)
1013+
1014+
if (!clientResult.success) return
1015+
const client = clientResult.data
1016+
1017+
// Act
1018+
const result = await client.generateImage({
1019+
prompt: 'Generate image without grounding',
1020+
})
1021+
1022+
// Assert
1023+
expect(result.success).toBe(true)
1024+
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
1025+
model: 'gemini-3-pro-image-preview',
1026+
contents: [
1027+
{
1028+
parts: [
1029+
{
1030+
text: 'Generate image without grounding',
1031+
},
1032+
],
1033+
},
1034+
],
1035+
config: {
1036+
responseModalities: ['IMAGE'],
1037+
},
1038+
})
1039+
1040+
// Verify tools parameter is not included
1041+
const callArgs = (mockGeminiClientInstance.models.generateContent as any).mock.calls[0][0]
1042+
expect(callArgs.tools).toBeUndefined()
1043+
})
1044+
1045+
it('should combine useGoogleSearch with aspectRatio and imageSize', async () => {
1046+
// Arrange
1047+
const mockResponse = {
1048+
response: {
1049+
candidates: [
1050+
{
1051+
content: {
1052+
parts: [
1053+
{
1054+
inlineData: {
1055+
data: 'base64-grounded-4k-image',
1056+
mimeType: 'image/png',
1057+
},
1058+
},
1059+
],
1060+
},
1061+
},
1062+
],
1063+
},
1064+
}
1065+
1066+
mockGeminiClientInstance.models.generateContent = vi.fn().mockResolvedValue(mockResponse)
1067+
1068+
const clientResult = createGeminiClient(testConfig)
1069+
expect(clientResult.success).toBe(true)
1070+
1071+
if (!clientResult.success) return
1072+
const client = clientResult.data
1073+
1074+
// Act
1075+
const result = await client.generateImage({
1076+
prompt: 'Generate 2025 Japan foodtech industry chaos map',
1077+
useGoogleSearch: true,
1078+
aspectRatio: '16:9',
1079+
imageSize: '4K',
1080+
})
1081+
1082+
// Assert
1083+
expect(result.success).toBe(true)
1084+
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
1085+
model: 'gemini-3-pro-image-preview',
1086+
contents: [
1087+
{
1088+
parts: [
1089+
{
1090+
text: 'Generate 2025 Japan foodtech industry chaos map',
1091+
},
1092+
],
1093+
},
1094+
],
1095+
config: {
1096+
imageConfig: {
1097+
aspectRatio: '16:9',
1098+
imageSize: '4K',
1099+
},
1100+
responseModalities: ['IMAGE'],
1101+
},
1102+
tools: [{ googleSearch: {} }],
1103+
})
1104+
})
1105+
})
8731106
})

src/api/geminiClient.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export interface GeminiApiParams {
144144
inputImage?: string
145145
aspectRatio?: string
146146
imageSize?: string
147+
useGoogleSearch?: boolean
147148
}
148149

149150
/**
@@ -224,11 +225,15 @@ class GeminiClientImpl implements GeminiClient {
224225
responseModalities: ['IMAGE'],
225226
}
226227

228+
// Construct tools array for Google Search grounding
229+
const tools = params.useGoogleSearch ? [{ googleSearch: {} }] : undefined
230+
227231
// Generate content using Gemini API (@google/genai v1.17.0+)
228232
const rawResponse = await this.genai.models.generateContent({
229233
model: this.modelName,
230234
contents: requestContent,
231235
config,
236+
...(tools && { tools }),
232237
})
233238

234239
// Validate response structure with type guard

src/business/structuredPromptGenerator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface FeatureFlags {
4949
maintainCharacterConsistency?: boolean
5050
blendImages?: boolean
5151
useWorldKnowledge?: boolean
52+
useGoogleSearch?: boolean
5253
}
5354

5455
/**

src/server/mcpServer.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ export class MCPServerImpl {
119119
description:
120120
'Use real-world knowledge for accurate context. Enable for historical figures, landmarks, or factual scenarios',
121121
},
122+
useGoogleSearch: {
123+
type: 'boolean' as const,
124+
description:
125+
"Enable Google Search grounding to access real-time web information for factually accurate image generation. Use when prompt requires current or time-sensitive data that may have changed since the model's knowledge cutoff. Leave disabled for creative, fictional, historical, or timeless content.",
126+
},
122127
aspectRatio: {
123128
type: 'string' as const,
124129
description: 'Aspect ratio for the generated image',
@@ -230,6 +235,9 @@ export class MCPServerImpl {
230235
if (params.useWorldKnowledge !== undefined) {
231236
features.useWorldKnowledge = params.useWorldKnowledge
232237
}
238+
if (params.useGoogleSearch !== undefined) {
239+
features.useGoogleSearch = params.useGoogleSearch
240+
}
233241

234242
const promptResult = await this.structuredPromptGenerator.generateStructuredPrompt(
235243
params.prompt,
@@ -264,6 +272,7 @@ export class MCPServerImpl {
264272
...(inputImageData && { inputImage: inputImageData }),
265273
...(params.aspectRatio && { aspectRatio: params.aspectRatio }),
266274
...(params.imageSize && { imageSize: params.imageSize }),
275+
...(params.useGoogleSearch !== undefined && { useGoogleSearch: params.useGoogleSearch }),
267276
})
268277

269278
if (!generationResult.success) {

src/types/mcp.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export interface GenerateImageParams {
4747
maintainCharacterConsistency?: boolean
4848
/** Use world knowledge integration for more accurate context (default: false) */
4949
useWorldKnowledge?: boolean
50+
/** Enable Google Search grounding for real-time web information (default: false) */
51+
useGoogleSearch?: boolean
5052
/** Aspect ratio for generated image (default: "1:1") */
5153
aspectRatio?: AspectRatio
5254
/** Image resolution for high-quality output (e.g., "2K", "4K"). Leave unspecified for standard quality */

0 commit comments

Comments
 (0)