tomo2chin2 commited on
Commit
736224e
·
verified ·
1 Parent(s): 479a750

Upload 8 files

Browse files
Files changed (2) hide show
  1. app.py +112 -158
  2. requirements.txt +0 -2
app.py CHANGED
@@ -6,13 +6,11 @@ from PIL import Image
6
  from google import genai
7
  from google.genai import types
8
  import json
9
- import asyncio
10
- from typing import Dict, Any, List, Optional
11
  import logging
12
  import traceback
 
13
  from fastapi import FastAPI, Request
14
  from fastapi.responses import JSONResponse
15
- import uvicorn
16
 
17
  # ログ設定
18
  logging.basicConfig(
@@ -21,13 +19,6 @@ logging.basicConfig(
21
  )
22
  logger = logging.getLogger(__name__)
23
 
24
- # FastAPIアプリケーションの初期化(root_pathを設定)
25
- app = FastAPI(
26
- title="Image Generation MCP Server",
27
- version="1.0.0",
28
- root_path="/api/v1" # 明示的なroot_path設定
29
- )
30
-
31
  # Gemini APIクライアントの初期化
32
  def get_gemini_client():
33
  api_key = os.environ.get("GEMINI_API_KEY")
@@ -35,10 +26,10 @@ def get_gemini_client():
35
  raise ValueError("GEMINI_API_KEY環境変数が設定されていません")
36
  return genai.Client(api_key=api_key)
37
 
38
- # 画像生成関数(同期版)
39
- def generate_image_sync(prompt: str, previous_image: Optional[Image.Image] = None) -> tuple[Optional[Image.Image], str]:
40
  """
41
- Gemini 2.0 Flashを使用して画像を生成する(同期版)
42
 
43
  Args:
44
  prompt: 生成したい画像の説明
@@ -122,86 +113,6 @@ def generate_image_sync(prompt: str, previous_image: Optional[Image.Image] = Non
122
  logger.error(traceback.format_exc())
123
  return None, f"エラーが発生しました: {str(e)}"
124
 
125
- # 非同期画像生成関数
126
- async def generate_image_async(prompt: str) -> tuple[bool, str, Optional[str]]:
127
- """非同期で画像を生成"""
128
- loop = asyncio.get_event_loop()
129
- image, message = await loop.run_in_executor(None, generate_image_sync, prompt, None)
130
-
131
- if image:
132
- buffered = io.BytesIO()
133
- image.save(buffered, format="PNG")
134
- img_str = base64.b64encode(buffered.getvalue()).decode()
135
- return True, message, img_str
136
- else:
137
- return False, message, None
138
-
139
- # FastAPI MCPエンドポイント
140
- @app.post("/mcp/list_tools")
141
- async def mcp_list_tools():
142
- """MCPツールのリストを返す"""
143
- return {
144
- "tools": [
145
- {
146
- "name": "generate_image",
147
- "description": "Gemini 2.0 Flash Previewを使用して画像を生成します",
148
- "inputSchema": {
149
- "type": "object",
150
- "properties": {
151
- "prompt": {
152
- "type": "string",
153
- "description": "生成したい画像の説明"
154
- }
155
- },
156
- "required": ["prompt"]
157
- }
158
- }
159
- ]
160
- }
161
-
162
- @app.post("/mcp/call_tool")
163
- async def mcp_call_tool(request: Request):
164
- """MCPツールを実行する(非同期)"""
165
- try:
166
- data = await request.json()
167
- tool_name = data.get("name")
168
- arguments = data.get("arguments", {})
169
-
170
- if tool_name == "generate_image":
171
- prompt = arguments.get("prompt", "")
172
-
173
- # 非同期で画像生成
174
- success, message, img_str = await generate_image_async(prompt)
175
-
176
- if success:
177
- return JSONResponse({
178
- "success": True,
179
- "message": message,
180
- "image_base64": img_str
181
- })
182
- else:
183
- return JSONResponse({
184
- "success": False,
185
- "message": message
186
- })
187
-
188
- return JSONResponse({
189
- "success": False,
190
- "message": f"Unknown tool: {tool_name}"
191
- }, status_code=400)
192
-
193
- except Exception as e:
194
- logger.error(f"MCPエラー: {str(e)}")
195
- return JSONResponse({
196
- "success": False,
197
- "message": f"Error: {str(e)}"
198
- }, status_code=500)
199
-
200
- @app.get("/health")
201
- async def health_check():
202
- """ヘルスチェックエンドポイント"""
203
- return {"status": "healthy", "service": "image-gen-mcp"}
204
-
205
  # Gradioインターフェースの作成
206
  def create_gradio_interface():
207
  with gr.Blocks(title="画像生成MCP - Gemini 2.0 Flash") as demo:
@@ -210,11 +121,6 @@ def create_gradio_interface():
210
 
211
  このアプリケーションは主にClaudeCodeから利用するためのMCPサーバーです。
212
  Gemini 2.0 Flash Previewを使用して画像を生成します。
213
-
214
- ## APIエンドポイント
215
- - `POST /api/v1/mcp/list_tools` - ツール一覧
216
- - `POST /api/v1/mcp/call_tool` - 画像生成実行
217
- - `GET /api/v1/health` - ヘルスチェック
218
  """)
219
 
220
  with gr.Tab("画像生成テスト"):
@@ -244,66 +150,121 @@ def create_gradio_interface():
244
  interactive=False
245
  )
246
 
247
- with gr.Tab("MCP APIテスト"):
248
  gr.Markdown("""
249
- ### ClaudeCode設定例
 
 
 
 
 
 
 
 
 
 
250
  ```json
251
  {
252
- "mcpServers": {
253
- "image-gen": {
254
- "url": "https://[your-space-name].hf.space/api/v1/mcp/"
255
  }
256
- }
257
  }
258
  ```
259
- """)
260
 
261
- # 簡易APIテスター
262
- with gr.Row():
263
- api_endpoint = gr.Dropdown(
264
- choices=["/api/v1/mcp/list_tools", "/api/v1/mcp/call_tool", "/api/v1/health"],
265
- value="/api/v1/health",
266
- label="エンドポイント"
267
- )
268
-
269
- api_response = gr.JSON(label="APIレスポンス")
270
- test_api_btn = gr.Button("APIをテスト")
271
-
272
- async def test_api(endpoint):
273
- """APIエンドポイントをテスト"""
274
- import httpx
275
- try:
276
- base_url = os.environ.get("SPACE_HOST", "http://localhost:7860")
277
- async with httpx.AsyncClient() as client:
278
- if endpoint == "/api/v1/health":
279
- response = await client.get(f"{base_url}{endpoint}")
280
- elif endpoint == "/api/v1/mcp/list_tools":
281
- response = await client.post(f"{base_url}{endpoint}")
282
- else:
283
- # call_toolのテスト
284
- response = await client.post(
285
- f"{base_url}{endpoint}",
286
- json={"name": "generate_image", "arguments": {"prompt": "テスト画像"}}
287
- )
288
- return response.json()
289
- except Exception as e:
290
- return {"error": str(e)}
291
-
292
- test_api_btn.click(
293
- fn=test_api,
294
- inputs=[api_endpoint],
295
- outputs=[api_response]
296
- )
297
 
298
  # イベントハンドラ
299
  generate_btn.click(
300
- fn=generate_image_sync,
301
  inputs=[prompt_input, reference_image],
302
  outputs=[output_image, status_output]
303
  )
304
 
305
  return demo
306
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  # メイン実行部分
308
  if __name__ == "__main__":
309
  # APIキーチェック
@@ -319,25 +280,18 @@ if __name__ == "__main__":
319
  - Name: `GEMINI_API_KEY`
320
  - Value: あなたのGemini APIキー
321
  """)
 
 
322
  demo.launch()
323
  else:
324
  # Gradioインターフェース作成
325
- logger.info("MCPサーバーとGradio UIを起動中...")
326
  demo = create_gradio_interface()
327
 
328
- # 環境変数でroot_pathを設定(Hugging Face Spaces用)
329
- if os.environ.get("GRADIO_ROOT_PATH"):
330
- os.environ["GRADIO_ROOT_PATH"] = "/gradio"
331
-
332
- # FastAPIにGradioをマウント(最適化されたパス設定)
333
- app = gr.mount_gradio_app(app, demo, path="/gradio")
334
 
335
- # サーバー起動
336
- config = uvicorn.Config(
337
- app=app,
338
- host="0.0.0.0",
339
- port=7860,
340
- root_path=os.environ.get("ROOT_PATH", "")
341
- )
342
- server = uvicorn.Server(config)
343
- server.run()
 
6
  from google import genai
7
  from google.genai import types
8
  import json
 
 
9
  import logging
10
  import traceback
11
+ from typing import Optional
12
  from fastapi import FastAPI, Request
13
  from fastapi.responses import JSONResponse
 
14
 
15
  # ログ設定
16
  logging.basicConfig(
 
19
  )
20
  logger = logging.getLogger(__name__)
21
 
 
 
 
 
 
 
 
22
  # Gemini APIクライアントの初期化
23
  def get_gemini_client():
24
  api_key = os.environ.get("GEMINI_API_KEY")
 
26
  raise ValueError("GEMINI_API_KEY環境変数が設定されていません")
27
  return genai.Client(api_key=api_key)
28
 
29
+ # 画像生成関数
30
+ def generate_image(prompt: str, previous_image: Optional[Image.Image] = None) -> tuple[Optional[Image.Image], str]:
31
  """
32
+ Gemini 2.0 Flashを使用して画像を生成する
33
 
34
  Args:
35
  prompt: 生成したい画像の説明
 
113
  logger.error(traceback.format_exc())
114
  return None, f"エラーが発生しました: {str(e)}"
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  # Gradioインターフェースの作成
117
  def create_gradio_interface():
118
  with gr.Blocks(title="画像生成MCP - Gemini 2.0 Flash") as demo:
 
121
 
122
  このアプリケーションは主にClaudeCodeから利用するためのMCPサーバーです。
123
  Gemini 2.0 Flash Previewを使用して画像を生成します。
 
 
 
 
 
124
  """)
125
 
126
  with gr.Tab("画像生成テスト"):
 
150
  interactive=False
151
  )
152
 
153
+ with gr.Tab("MCP API情報"):
154
  gr.Markdown("""
155
+ ### MCP APIエンドポイント
156
+
157
+ 以下のエンドポイントを使用してClaudeCodeから画像生成機能を利用できます:
158
+
159
+ - **ツール一覧取得**: `POST https://[your-space-name].hf.space/api/mcp/list_tools`
160
+ - **画像生成実行**: `POST https://[your-space-name].hf.space/api/mcp/call_tool`
161
+ - **ヘルスチェック**: `GET https://[your-space-name].hf.space/api/health`
162
+
163
+ ### リクエスト例
164
+
165
+ #### ツール実行 (call_tool)
166
  ```json
167
  {
168
+ "name": "generate_image",
169
+ "arguments": {
170
+ "prompt": "美しい夕日の風景"
171
  }
 
172
  }
173
  ```
 
174
 
175
+ ### レスポンス例
176
+ ```json
177
+ {
178
+ "success": true,
179
+ "message": "画像生成に成功しました!",
180
+ "image_base64": "iVBORw0KGgo..."
181
+ }
182
+ ```
183
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
  # イベントハンドラ
186
  generate_btn.click(
187
+ fn=generate_image,
188
  inputs=[prompt_input, reference_image],
189
  outputs=[output_image, status_output]
190
  )
191
 
192
  return demo
193
 
194
+ # カスタムFastAPIルートを追加
195
+ def setup_mcp_routes(app: FastAPI):
196
+ """MCPエンドポイントをFastAPIアプリに追加"""
197
+
198
+ @app.post("/api/mcp/list_tools")
199
+ async def mcp_list_tools():
200
+ """MCPツールのリストを返す"""
201
+ return {
202
+ "tools": [
203
+ {
204
+ "name": "generate_image",
205
+ "description": "Gemini 2.0 Flash Previewを使用して画像を生成します",
206
+ "inputSchema": {
207
+ "type": "object",
208
+ "properties": {
209
+ "prompt": {
210
+ "type": "string",
211
+ "description": "生成したい画像の説明"
212
+ }
213
+ },
214
+ "required": ["prompt"]
215
+ }
216
+ }
217
+ ]
218
+ }
219
+
220
+ @app.post("/api/mcp/call_tool")
221
+ async def mcp_call_tool(request: Request):
222
+ """MCPツールを実行する"""
223
+ try:
224
+ data = await request.json()
225
+ tool_name = data.get("name")
226
+ arguments = data.get("arguments", {})
227
+
228
+ if tool_name == "generate_image":
229
+ prompt = arguments.get("prompt", "")
230
+
231
+ # 画像生成を実行
232
+ image, message = generate_image(prompt)
233
+
234
+ if image:
235
+ # 画像をbase64エンコード
236
+ buffered = io.BytesIO()
237
+ image.save(buffered, format="PNG")
238
+ img_str = base64.b64encode(buffered.getvalue()).decode()
239
+
240
+ return JSONResponse({
241
+ "success": True,
242
+ "message": message,
243
+ "image_base64": img_str
244
+ })
245
+ else:
246
+ return JSONResponse({
247
+ "success": False,
248
+ "message": message
249
+ })
250
+
251
+ return JSONResponse({
252
+ "success": False,
253
+ "message": f"Unknown tool: {tool_name}"
254
+ }, status_code=400)
255
+
256
+ except Exception as e:
257
+ logger.error(f"MCPエラー: {str(e)}")
258
+ return JSONResponse({
259
+ "success": False,
260
+ "message": f"Error: {str(e)}"
261
+ }, status_code=500)
262
+
263
+ @app.get("/api/health")
264
+ async def health_check():
265
+ """ヘルスチェックエンドポイント"""
266
+ return {"status": "healthy", "service": "image-gen-mcp"}
267
+
268
  # メイン実行部分
269
  if __name__ == "__main__":
270
  # APIキーチェック
 
280
  - Name: `GEMINI_API_KEY`
281
  - Value: あなたのGemini APIキー
282
  """)
283
+ # Hugging Face Spaces用の設定
284
+ demo.queue()
285
  demo.launch()
286
  else:
287
  # Gradioインターフェース作成
288
+ logger.info("画像生成MCPサーバーを起動中...")
289
  demo = create_gradio_interface()
290
 
291
+ # Gradioアプリからunderlying FastAPIアプリを取得してMCPルートを追加
292
+ app = demo.app
293
+ setup_mcp_routes(app)
 
 
 
294
 
295
+ # Hugging Face Spaces用の設定
296
+ demo.queue()
297
+ demo.launch()
 
 
 
 
 
 
requirements.txt CHANGED
@@ -2,6 +2,4 @@ gradio==5.31.0
2
  google-genai
3
  pillow
4
  fastapi
5
- uvicorn[standard]
6
- httpx
7
  python-multipart
 
2
  google-genai
3
  pillow
4
  fastapi
 
 
5
  python-multipart