Skip to content

Commit bae3ff5

Browse files
authored
Merge pull request #16 from shinpr/update-models
feat: update to gemini-2.5-flash-image model and add aspect ratio support
2 parents c3f7d83 + a2f9858 commit bae3ff5

10 files changed

Lines changed: 392 additions & 25 deletions

File tree

package-lock.json

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

package.json

Lines changed: 2 additions & 2 deletions
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.2.3",
4+
"version": "0.3.0",
55
"description": "MCP server for AI image generation",
66
"main": "dist/index.js",
77
"bin": {
@@ -49,7 +49,7 @@
4949
"test:safe": "npm test && npm run cleanup:processes"
5050
},
5151
"dependencies": {
52-
"@google/genai": "^1.17.0",
52+
"@google/genai": "^1.22.0",
5353
"@modelcontextprotocol/sdk": "^1.0.0"
5454
},
5555
"devDependencies": {

server.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
"url": "https://github.com/shinpr/mcp-image",
1010
"source": "github"
1111
},
12-
"version": "0.2.3",
12+
"version": "0.3.0",
1313
"packages": [
1414
{
1515
"registry_type": "npm",
1616
"registry_base_url": "https://registry.npmjs.org",
1717
"identifier": "mcp-image",
18-
"version": "0.2.3",
18+
"version": "0.3.0",
1919
"transport": {
2020
"type": "stdio"
2121
},

src/api/__tests__/geminiClient.test.ts

Lines changed: 195 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('geminiClient', () => {
9898
expect(result.success).toBe(true)
9999
if (result.success) {
100100
expect(result.data.imageData).toBeInstanceOf(Buffer)
101-
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image-preview')
101+
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image')
102102
expect(result.data.metadata.prompt).toBe('Generate a beautiful landscape')
103103
expect(result.data.metadata.mimeType).toBe('image/png')
104104
}
@@ -146,13 +146,13 @@ describe('geminiClient', () => {
146146
expect(result.success).toBe(true)
147147
if (result.success) {
148148
expect(result.data.imageData).toBeInstanceOf(Buffer)
149-
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image-preview')
149+
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image')
150150
expect(result.data.metadata.prompt).toBe('Enhance this image')
151151
expect(result.data.metadata.mimeType).toBe('image/jpeg')
152152
}
153153

154154
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
155-
model: 'gemini-2.5-flash-image-preview',
155+
model: 'gemini-2.5-flash-image',
156156
contents: [
157157
{
158158
parts: [
@@ -168,6 +168,9 @@ describe('geminiClient', () => {
168168
],
169169
},
170170
],
171+
config: {
172+
responseModalities: ['IMAGE'],
173+
},
171174
})
172175
})
173176

@@ -482,14 +485,14 @@ describe('geminiClient', () => {
482485
expect(result.success).toBe(true)
483486
if (result.success) {
484487
expect(result.data.imageData).toBeInstanceOf(Buffer)
485-
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image-preview')
488+
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image')
486489
// Features are passed to the API but not stored in metadata
487490
expect(result.data.metadata.prompt).toBe('Generate character with blending')
488491
}
489492

490493
// Verify API was called with original prompt (no enhancement at GeminiClient level)
491494
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
492-
model: 'gemini-2.5-flash-image-preview',
495+
model: 'gemini-2.5-flash-image',
493496
contents: [
494497
{
495498
parts: [
@@ -499,6 +502,9 @@ describe('geminiClient', () => {
499502
],
500503
},
501504
],
505+
config: {
506+
responseModalities: ['IMAGE'],
507+
},
502508
})
503509
})
504510

@@ -542,12 +548,12 @@ describe('geminiClient', () => {
542548
if (result.success) {
543549
// Features are passed to the API but not stored in metadata
544550
expect(result.data.metadata.prompt).toBe('Generate factually accurate historical scene')
545-
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image-preview')
551+
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image')
546552
}
547553

548554
// Verify API was called with original prompt (no processing at GeminiClient level)
549555
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
550-
model: 'gemini-2.5-flash-image-preview',
556+
model: 'gemini-2.5-flash-image',
551557
contents: [
552558
{
553559
parts: [
@@ -557,6 +563,9 @@ describe('geminiClient', () => {
557563
],
558564
},
559565
],
566+
config: {
567+
responseModalities: ['IMAGE'],
568+
},
560569
})
561570
})
562571

@@ -599,12 +608,12 @@ describe('geminiClient', () => {
599608
if (result.success) {
600609
// Features not specified - standard metadata only
601610
expect(result.data.metadata.prompt).toBe('Generate simple landscape')
602-
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image-preview')
611+
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image')
603612
}
604613

605-
// Verify API was called without generation config
614+
// Verify API was called with config
606615
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
607-
model: 'gemini-2.5-flash-image-preview',
616+
model: 'gemini-2.5-flash-image',
608617
contents: [
609618
{
610619
parts: [
@@ -614,6 +623,9 @@ describe('geminiClient', () => {
614623
],
615624
},
616625
],
626+
config: {
627+
responseModalities: ['IMAGE'],
628+
},
617629
})
618630
})
619631

@@ -663,12 +675,12 @@ describe('geminiClient', () => {
663675
expect(result.data.metadata.inputImageProvided).toBe(true)
664676
// Features are passed to the API but not stored in metadata
665677
expect(result.data.metadata.prompt).toBe('Blend this character with fantasy elements')
666-
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image-preview')
678+
expect(result.data.metadata.model).toBe('gemini-2.5-flash-image')
667679
}
668680

669681
// Verify API was called with input image and original prompt (no processing at GeminiClient level)
670682
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
671-
model: 'gemini-2.5-flash-image-preview',
683+
model: 'gemini-2.5-flash-image',
672684
contents: [
673685
{
674686
parts: [
@@ -684,6 +696,177 @@ describe('geminiClient', () => {
684696
],
685697
},
686698
],
699+
config: {
700+
responseModalities: ['IMAGE'],
701+
},
702+
})
703+
})
704+
})
705+
706+
describe('GeminiClient.generateImage with aspectRatio', () => {
707+
it('should call API with imageConfig when aspectRatio is specified', async () => {
708+
// Arrange
709+
const mockResponse = {
710+
response: {
711+
candidates: [
712+
{
713+
content: {
714+
parts: [
715+
{
716+
inlineData: {
717+
data: 'base64-image-data-16-9',
718+
mimeType: 'image/png',
719+
},
720+
},
721+
],
722+
},
723+
},
724+
],
725+
},
726+
}
727+
728+
mockGeminiClientInstance.models.generateContent = vi.fn().mockResolvedValue(mockResponse)
729+
730+
const clientResult = createGeminiClient(testConfig)
731+
expect(clientResult.success).toBe(true)
732+
733+
if (!clientResult.success) return
734+
const client = clientResult.data
735+
736+
// Act
737+
const result = await client.generateImage({
738+
prompt: 'test prompt for aspect ratio',
739+
aspectRatio: '16:9',
740+
})
741+
742+
// Assert
743+
expect(result.success).toBe(true)
744+
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
745+
model: 'gemini-2.5-flash-image',
746+
contents: [
747+
{
748+
parts: [
749+
{
750+
text: 'test prompt for aspect ratio',
751+
},
752+
],
753+
},
754+
],
755+
config: {
756+
imageConfig: { aspectRatio: '16:9' },
757+
responseModalities: ['IMAGE'],
758+
},
759+
})
760+
})
761+
762+
it('should use default when aspectRatio is not specified', async () => {
763+
// Arrange
764+
const mockResponse = {
765+
response: {
766+
candidates: [
767+
{
768+
content: {
769+
parts: [
770+
{
771+
inlineData: {
772+
data: 'base64-default-image',
773+
mimeType: 'image/png',
774+
},
775+
},
776+
],
777+
},
778+
},
779+
],
780+
},
781+
}
782+
783+
mockGeminiClientInstance.models.generateContent = vi.fn().mockResolvedValue(mockResponse)
784+
785+
const clientResult = createGeminiClient(testConfig)
786+
expect(clientResult.success).toBe(true)
787+
788+
if (!clientResult.success) return
789+
const client = clientResult.data
790+
791+
// Act
792+
const result = await client.generateImage({
793+
prompt: 'test prompt without aspect ratio',
794+
})
795+
796+
// Assert
797+
expect(result.success).toBe(true)
798+
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
799+
model: 'gemini-2.5-flash-image',
800+
contents: [
801+
{
802+
parts: [
803+
{
804+
text: 'test prompt without aspect ratio',
805+
},
806+
],
807+
},
808+
],
809+
config: {
810+
responseModalities: ['IMAGE'],
811+
},
812+
})
813+
// Verify imageConfig.aspectRatio is not included
814+
const callArgs = (mockGeminiClientInstance.models.generateContent as any).mock.calls[0][0]
815+
expect(callArgs.config.imageConfig).toBeUndefined()
816+
})
817+
818+
it('should include responseModalities: ["IMAGE"] in API call with aspectRatio', async () => {
819+
// Arrange
820+
const mockResponse = {
821+
response: {
822+
candidates: [
823+
{
824+
content: {
825+
parts: [
826+
{
827+
inlineData: {
828+
data: 'base64-image-data-21-9',
829+
mimeType: 'image/png',
830+
},
831+
},
832+
],
833+
},
834+
},
835+
],
836+
},
837+
}
838+
839+
mockGeminiClientInstance.models.generateContent = vi.fn().mockResolvedValue(mockResponse)
840+
841+
const clientResult = createGeminiClient(testConfig)
842+
expect(clientResult.success).toBe(true)
843+
844+
if (!clientResult.success) return
845+
const client = clientResult.data
846+
847+
// Act
848+
const result = await client.generateImage({
849+
prompt: 'test prompt with 21:9 aspect ratio',
850+
aspectRatio: '21:9',
851+
})
852+
853+
// Assert
854+
expect(result.success).toBe(true)
855+
expect(mockGeminiClientInstance.models.generateContent).toHaveBeenCalledWith({
856+
model: 'gemini-2.5-flash-image',
857+
contents: [
858+
{
859+
parts: [
860+
{
861+
text: 'test prompt with 21:9 aspect ratio',
862+
},
863+
],
864+
},
865+
],
866+
config: {
867+
imageConfig: { aspectRatio: '21:9' },
868+
responseModalities: ['IMAGE'],
869+
},
687870
})
688871
})
689872
})

0 commit comments

Comments
 (0)