Open16

飼い主,MCPを遊ぶってよ

ごまふあざらしごまふあざらし

おみくじを引く MCP を作ってみよう.

$ mkdir omikuji_mcp_julia
$ uv init
Initialized project `omikuji-mcp-julia`
$ uv run main.py
Using CPython 3.13.3 interpreter at: /opt/homebrew/opt/python@3.13/bin/python3.13
Creating virtual environment at: .venv
Hello from omikuji-mcp-julia!
ごまふあざらしごまふあざらし

Quickstart の指示に従ってそのまま server.py を作成する.

# server.py
from mcp.server.fastmcp import FastMCP

# Create an MCP server
mcp = FastMCP("Demo")


# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b


# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """Get a personalized greeting"""
    return f"Hello, {name}!"

サーバを起動 インストールする.

$ uv run mcp install server.py
[05/10/25 14:18:28] INFO     Added server       claude.py:141
                             'Demo' to Claude
                             config
                    INFO     Successfully          cli.py:467
                             installed Demo in
                             Claude app
ごまふあざらしごまふあざらし

インストールされた MCP サーバを確認する.デスクトップアプリのClaudeを起動して下記のように Demo という場所が見える.

add が見える. def add(a: int, b: int) -> int: に対応するものだと思われる.

ごまふあざらしごまふあざらし

add(3, 4) と命令(Prompt 入力欄に書き込みを)すると下記のように警告が出る.

ここでは Allow once を実行して実行を許可する.

下記のような thinking ログを残していたことがわかった.

{ `a`: 3, `b` : 4}

ごまふあざらしごまふあざらし

server.py を次のように書き換える.

server
# server.py
import random
from mcp.server.fastmcp import FastMCP

# Create an MCP server
mcp = FastMCP("Demo")


# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

FORTUNES = [
    "大吉", "中吉", "小吉", "末吉",
]

@mcp.tool()
def omikuji() -> str:
    return FORTUNES[random.randint(0, len(FORTUNES))]


# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """Get a personalized greeting"""
    return f"Hello, {name}!"

下記のコマンドでインストールを開始する.

$ uv run mcp install server.py
ごまふあざらしごまふあざらし

Run omikuji をプロンプトとして入力

下記のように "list index out of range" で怒られる.

random.randint の仕様を勘違いしていた.end points も含むことに注意しなければいけない.

In [15]: ?random.randint
Signature: random.randint(a, b)
Docstring:
Return random integer in range [a, b], including both end points.
@mcp.tool()
def omikuji() -> str:
    return FORTUNES[random.randint(0, len(FORTUNES)-1)]

↑のように書き直す

ごまふあざらしごまふあざらし

Claude を再起動して Run omikuji とか Draw again とか One more time で複数回行う.

小吉と中吉が出ている.とりあえず実行ごとに異なる答えが得られてるのがわかる.

ごまふあざらしごまふあざらし

画像データを返却できるらしいので試してみよう.

server.py
# server.py
import random
from mcp.server.fastmcp import FastMCP
import numpy as np
from PIL import Image

# Create an MCP server
mcp = FastMCP("Demo")


# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

FORTUNES = [
    "大吉", "中吉", "小吉", "末吉",
]

@mcp.tool()
def omikuji() -> str:
    return FORTUNES[random.randint(0, len(FORTUNES)-1)]

@mcp.tool()
def create_image() -> Image.Image:
    img = np.random.random((100, 100, 3))
    return Image.fromarray((img * 255).astype(np.uint8))

# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """Get a personalized greeting"""
    return f"Hello, {name}!"

下記のように --with pillow オプションをつけておく

$ uv run mcp install server.py --with pillow --with numpy
ごまふあざらしごまふあざらし

Call create_image というプロンプトを投げる.Claude app だと画像を表示してくれないっぽい.残念.

ごまふあざらしごまふあざらし

Python からJuliaを呼べるのでこんな感じのコードを作ってみる.

server.py
# server.py
import random
from mcp.server.fastmcp import FastMCP
import numpy as np
from PIL import Image
# ここで↓をロードするとMCPサーバが立ち上がらない.
#from juliacall import Main as jl

# Create an MCP server
mcp = FastMCP("Demo")


# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

FORTUNES = [
    "大吉", "中吉", "小吉", "末吉",
]

@mcp.tool()
def omikuji() -> str:
    return FORTUNES[random.randint(0, len(FORTUNES)-1)]

@mcp.tool()
def create_image() -> Image.Image:
    img = np.random.random((100, 100, 3))
    return Image.fromarray((img * 255).astype(np.uint8))

@mcp.tool()
def call_julia(expression: str) -> str:
    from juliacall import Main as jl
    try:
        out = str(jl.seval(expression))
        return out
    except Exception as e:
        return str(e)


# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """Get a personalized greeting"""
    return f"Hello, {name}!"
$ uv run mcp install server.py --with pillow --with numpy --with juliacall
ごまふあざらしごまふあざらし

一応 Julia の式を評価してそれを返すようなものはできた.任意の指揮を評価するようになってるので注意が必要.

ごまふあざらしごまふあざらし

Cursor で遊ぶ場合

下記のような mcp.json を記述

{
  "mcpServers": {
    "Demo": {
      "command": "~/.local/bin/uv",
      "args": [
        "run",
        "--with",
        "juliacall",
        "--with",
        "mcp[cli]",
        "--with",
        "numpy",
        "--with",
        "pillow",
        "mcp",
        "run",
        "~/work/omikuji_mcp_julia/server.py"
      ]
    }
  }
}

@ で呼び出すコマンド名が Python の関数名に対応しているようだ

ごまふあざらしごまふあざらし

相性問題かもしれないけれど,juliacall に関するコードを実行すると,一回は実行できるけれど次回以降,クライアントが止まる・・・.