[備忘録] Gradio MCPサーバーを試してみたら、docstringの重要性に気づいた話

こんにちは！最近、Gradioの`mcp_server=True`という機能を試してみて驚いたことがあります。1行の設定で、Python関数がClaudeなどのLLMと連携できるようになります。でも、なぜこれが可能なのか？その秘密を探るうちに、意外な主役が見つかりました—それが**docstring**（関数のコメント）だったのです。

![gradio-docstring-implementation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/b0c87c81-741a-4904-b636-586fcc12ab16.png)


## はじめに：1行の設定なのか (？)

Gradioは、Pythonでチャットボット、データ分析のUIを簡単に作れるライブラリとして知られています。最近のバージョンでは、MCPサーバー機能が追加されました。

```python
demo.launch(mcp_server=True)  
```
1行で、あなたのGradioアプリはMCP（Model Context Protocol）サーバーとして動作し、Claudeなどの大規模言語モデルから直接呼び出せるようになります。でも、この機能の裏側では、何が起きているのでしょうか？

## MCPとは何か？簡単に説明

MCP（Model Control Protocol）は、大規模言語モデル（LLM）が外部ツールや関数を自然言語ベースで呼び出すための軽量なプロトコルであり、計算処理や画像生成などを外部リソースに委ねることを可能にします。

## 実際に試してみた

つぎのページの公式のページの最初のサンプルプログラムを試してみました。

https://www.gradio.app/guides/building-mcp-server-with-gradio

```python
import gradio as gr

def letter_counter(word, letter):
    """
    Count the number of occurrences of a letter in a word or text.

    Args:
        word (str): The input text to search through
        letter (str): The letter to search for

    Returns:
        int: The number of times the letter appears
    """
    word = word.lower()
    letter = letter.lower()
    count = word.count(letter)
    return count

demo = gr.Interface(
    fn=letter_counter,
    inputs=["textbox", "textbox"],
    outputs="number",
    title="Letter Counter",
    description="Enter text and a letter to count how many times the letter appears in the text."
)

if __name__ == "__main__":
    demo.launch(mcp_server=True)  # MCPサーバーとして起動
```

このアプリを起動すると、コンソールにMCPサーバーのURLが表示されます：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/e21696ca-4fdc-40fb-a534-a122813641d9.png)

gradioのUIのページに遷移すると以下のように、wordのletterをカウントしてくれるアプリが起動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/a9bf2df3-4ec5-4ef0-9f27-01ef51e9ad67.png)


このURLをMCPクライアントの設定に追加すると...驚くべきことに、MCPサーバー化して連携できるようです。以下のように自分の場合はvscodeの設定のmcpの設定 (gradio01の項目) を追記して、サーバーを起動して試してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/2f8cc5d7-0f7a-4717-8e4f-880007743798.png)

```
"mcp": {
        "inputs": [],
        "servers": {
            "mcp-server-time": {
                "command": "python",
                "args": [
                    "-m",
                    "mcp_server_time",
                    "--local-timezone=America/Los_Angeles"
                ],
                "env": {}
            },
            "gradio01': {
                "type": "sse",
                "url": "http://127.0.0.1:7860/gradio_api/mcp/sse"
            },
            }
        }
    },

```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/52dfe32d-8447-4b4c-bee4-652df09539d5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/94c77919-6fd4-4d99-8854-d423e00cfe25.png)

設定の再生ボタンから起動すると、上記のように起動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/460f6e96-508d-48eb-ad13-23672f738c1a.png)

GitHub Copilotのチャットの画面を起動して、エージェントモードを選択します。
ツールのアイコンをクリックすると、どのmcpサーバと連携するか一覧がでてくるので、対象のmcpサーバを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/eee8fd07-8179-4444-a701-64377ff94c49.png)

「strawberryの中にrはいくつありますか？」と質問すると、文字カウント関数を使って正確に答えてくれました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/e58b00ca-7b4e-4885-ab9e-c11285d6ad50.png)

続行を選択する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/b29f37af-f461-4bd0-bcbe-dbcc8e4e6f93.png)

## なぜこれが動くのか？秘密はdocstringにあった

なぜLLMはgradioのUIや関数の入出力について理解できたのでしょうか？
実装を調べてみると、その答えにたどり着きました。


https://github.com/gradio-app/gradio/blob/main/gradio/mcp.py

https://github.com/gradio-app/gradio/blob/main/gradio/utils.py

Gradioの内部実装（以下はGradioのソースコードの一部）を見ると、`get_function_description`という関数が呼び出されています：

```python
description, parameters = utils.get_function_description(block_fn.fn)
```

この関数は何をしているのでしょうか？実装を見てみると：

```python
def get_function_description(fn: Callable) -> tuple[str, dict[str, str]]:
    """
    Get the description of a function and its parameters by parsing the docstring.
    ...
    """
    fn_docstring = fn.__doc__
    description = ""
    parameters = {}

    if not fn_docstring:
        return description, parameters

    lines = fn_docstring.strip().split("\n")

    # 説明部分の抽出
    description_lines = []
    for line in lines:
        line = line.strip()
        if line.startswith(("Args:", "Parameters:", "Arguments:")):
            break
        if line:
            description_lines.append(line)

    description = " ".join(description_lines)

    # パラメータ部分の抽出
    try:
        param_start_idx = next(
            (
                i
                for i, line in enumerate(lines)
                if line.strip().startswith(("Args:", "Parameters:", "Arguments:"))
            ),
            len(lines),
        )

        for line in lines[param_start_idx + 1 :]:
            line = line.strip()
            if not line:
                continue

            try:
                if ":" in line:
                    param_name, param_desc = line.split(":", 1)
                    param_name = param_name.split(" ")[0].strip()
                    if param_name:
                        parameters[param_name] = param_desc.strip()
            except Exception:
                continue

    except Exception:
        pass

    return description, parameters
```

そうです！Gradioは関数の**docstring**（コメント）を解析して、その内容をMCPツールの説明として使っているのです。関数の説明とパラメータの説明がdocstringから抽出され、LLMに提供されています。

これが、Docstringの書き方が重要である理由です。gr.Interface に渡す title や description、そして docstring は、単なるコメントではなく、大規模言語モデル（LLM）と連携する際の重要なメタ情報として利用されます。

## 効果的なdocstringの4つの要素

MCPツールとして機能するdocstringには以下の要素が重要です：

1. **関数の明確な説明** - 最初に何をする関数かを簡潔に
2. **パラメータセクションの明示** - `Args:` などの見出しを使用
3. **パラメータの詳細説明** - 型情報と役割を明記
4. **戻り値の説明** - `Returns:` セクションを追加

これらの要素を含めることで、LLMがあなたの関数を理解し、適切に活用できるようになります。

## まとめ：docstringはLLM連携の鍵

Gradio MCPサーバーの驚くべき点は、普段何気なく書いているdocstringが、LLMとの連携の要になることです。適切に書かれたdocstringは、そのままLLMへの説明書として機能し、関数の目的とパラメータを伝えています。

つまり、docstringの質がLLM連携の質を左右します。明確で構造化されたdocstringを意識して書くことが、大事だということがわかりました。


## 参考リンク

https://huggingface.co/blog/gradio-mcp


## docstringなしで起動した場合

以下のような警告が出力される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/3770559a-1a60-4c2b-bc3e-9ef39533c0e8.png)

## 免責事項

- 本記事の作成にあたり、文章および図解の一部に生成AIを活用しています。最終的な編集および内容の確認は筆者が責任を持って行っています。  
- 本記事は2025年5月初旬時点の情報に基づいて記載しています。仕様や実装内容は将来的に変更される可能性があるため、最新の情報は必ず公式サイト等でご確認ください。