64
57

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MCPツールを使う汎用的なStreamlitチャットアプリを作って、いろんなMCPツールを使ってみた。

Posted at

MCPがあれば何でもできる。

MCPとStreamlitを使って汎用的なチャットアプリを作ってみました。

今回は「LangChain MCP Adapters」を使用します。

「LangChain MCP Adapters」のREADMEをChatGPTにREADMEを要約してもらいました。

  • LangChain MCP Adapters は、Anthropic Model Context Protocol (MCP) のツールを LangChain や LangGraph で使えるようにする軽量なラッパーです
  • MCP ツールを LangChain のツールとして変換し、複数の MCP サーバーと接続できるクライアント実装を提供します
  • 簡単なインストールとセットアップで、MCP ツールを LangGraph エージェントに統合できます

やってみよう!

まずはただのチャットを作る

ライブラリーをインストールします。

pip install langchain langchain-aws langchain-mcp-adapters streamlit

Pythonスクリプトを書きます。

import asyncio

import streamlit as st
from langchain_aws import ChatBedrockConverse
from langchain_core.messages import AIMessage, HumanMessage


async def main():
    st.title("Bedrock chat with MCP tools")

    if "messages" not in st.session_state:
        st.session_state.messages = []
    messages = st.session_state.messages

    for message in messages:
        with st.chat_message(message.type):
            st.write(message.content)

    if prompt := st.chat_input():
        with st.chat_message("human"):
            st.write(prompt)

        messages.append(HumanMessage(prompt))

        chat_model = ChatBedrockConverse(
            model="us.anthropic.claude-3-haiku-20240307-v1:0"
        )
        ai_response = await chat_model.ainvoke(messages)

        messages.append(ai_response)

        with st.chat_message("ai"):
            st.write(ai_response.content)


asyncio.run(main())

起動します。

streamlit run app.py 

ブラウザでhttp://localhost:8501/を表示します。

この状態から始めます。

MCPツールを使用するよう修正していきます

mcp_config.jsonを作成します。今回はTime MCP Serverを使用します。

mcp_config.json
{
    "mcpServers": {
        "time": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "mcp/time"
            ],
            "transport": "stdio"
        }
    }
}

Claude DesktopでMCPを使用する際の設定ファイルとほぼ同じですが、"transport": "stdio"だけ追加してください。

インポートを追加します。

import json

from langchain_mcp_adapters.client import MultiServerMCPClient

mcp_config.jsonを読み込みます。

        with open("mcp_config.json", "r") as f:
            config = json.load(f)

ここで「LangChain MCP Adapters」を使用します。

MultiServerMCPClientを使ってMCPツールを取得します。

        async with MultiServerMCPClient(config["mcpServers"]) as mcp_client:
            tools = mcp_client.get_tools()

toolsの型は、list[BaseTool]です。なんと、MCPのツールがLangChainのツールになりました。

ここから先はもう、MCPの世界ではなくLangChainの世界の話になります。

ツールをバインドしてBedrockを呼び出します。

            ai_response = await chat_model.bind_tools(tools).ainvoke(messages)

ツールを使用する場合、Bedrockからの返答に含まれるcontentが配列になる場合があります。そのため出力処理を修正します。

                if isinstance(ai_response.content, str): # 文字列の場合
                    with st.chat_message("ai"):
                        st.write(ai_response.content)
                elif isinstance(ai_response.content, list): # 配列の場合
                    for content in ai_response.content:
                        if content["type"] == "text":
                            with st.chat_message("ai"):
                                st.write(content["text"])

応答にツール実行要求が含まれている場合はツールを実行します。

                if ai_response.tool_calls:
                    for tool_call in ai_response.tool_calls:
                        # LangChainツールの配列から、呼び出し対象のツールを取得
                        selected_tool = {tool.name.lower(): tool for tool in tools}[
                            tool_call["name"].lower()
                        ]
                        # ツールを実行
                        tool_msg = await selected_tool.ainvoke(tool_call)

ツール実行結果をメッセージに追加します。

                        messages.append(tool_msg)

ツール呼び出し要求がなくなるまでBedrockを呼び続けます。Whileループで囲います。

            while True:

                ...
                
                if ai_response.tool_calls:

                ...
                
                else:
                    break

これでツール呼び出し処理は完成です。

最後に、チャット履歴を表示する処理も一部変更します。(ツール呼び出しメッセージを表示しないように修正)

    printable_messages = [
        message for message in messages if message.type in ["ai", "human"]
    ]

    for message in printable_messages:
        if isinstance(message.content, str):
            with st.chat_message(message.type):
                st.write(message.content)
        elif isinstance(message.content, list):
            for content in message.content:
                if content["type"] == "text":
                    with st.chat_message(message.type):
                        st.write(content["text"])

完成したソースがこちらです。

import asyncio
import json

import streamlit as st
from langchain_aws import ChatBedrockConverse
from langchain_core.messages import HumanMessage
from langchain_mcp_adapters.client import MultiServerMCPClient


async def main():
    st.title("Bedrock chat with MCP tools")

    if "messages" not in st.session_state:
        st.session_state.messages = []
    messages = st.session_state.messages

    printable_messages = [
        message for message in messages if message.type in ["ai", "human"]
    ]

    for message in printable_messages:
        if isinstance(message.content, str):
            with st.chat_message(message.type):
                st.write(message.content)
        elif isinstance(message.content, list):
            for content in message.content:
                if content["type"] == "text":
                    with st.chat_message(message.type):
                        st.write(content["text"])

    if prompt := st.chat_input():
        with st.chat_message("human"):
            st.write(prompt)

        messages.append(HumanMessage(prompt))

        chat_model = ChatBedrockConverse(
            model="us.anthropic.claude-3-haiku-20240307-v1:0"
        )

        with open("mcp_config.json", "r") as f:
            config = json.load(f)

        async with MultiServerMCPClient(config["mcpServers"]) as mcp_client:
            tools = mcp_client.get_tools()

            while True:
                ai_response = await chat_model.bind_tools(tools).ainvoke(messages)

                messages.append(ai_response)

                if isinstance(ai_response.content, str):
                    with st.chat_message("ai"):
                        st.write(ai_response.content)
                elif isinstance(ai_response.content, list):
                    for content in ai_response.content:
                        if content["type"] == "text":
                            with st.chat_message("ai"):
                                st.write(content["text"])

                if ai_response.tool_calls:
                    for tool_call in ai_response.tool_calls:
                        selected_tool = {tool.name.lower(): tool for tool in tools}[
                            tool_call["name"].lower()
                        ]
                        tool_msg = await selected_tool.ainvoke(tool_call)
                        messages.append(tool_msg)
                else:
                    break


asyncio.run(main())

コンパクトに纏まってますね!

色々やってみた

では、色々MCPツールを登録して動作させてみました。

※ツールの実行結果を表示するように少しだけ修正しました。

                if ai_response.tool_calls:
                    for tool_call in ai_response.tool_calls:
+                       status = st.status(
+                           f"Tool Call: {tool_call['name']}", expanded=True
+                       )
+                       status.write(tool_call['args'])
                        selected_tool = {tool.name: tool for tool in tools}[
                            tool_call["name"].lower()
                        ]
                        tool_msg = await selected_tool.ainvoke(tool_call)
+                       status.update(state="complete")
                        messages.append(tool_msg)
                else:
                    break

Time MCP Server

時間とタイムゾーンの変換機能を提供するモデル コンテキスト プロトコル サーバー。このサーバーにより、LLM は現在の時間情報を取得し、IANA タイムゾーン名を使用してタイムゾーン変換を実行し、システムのタイムゾーンを自動検出できるようになります。

上で解説した設定ファイルを使用します。

mcp_config.json
{
    "mcpServers": {
        "time": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "mcp/time"
            ],
            "transport": "stdio"
        }
    }
}

localhost_8501_ (10).png

Brave Search MCP Server

Brave Search API を統合し、Web 検索機能とローカル検索機能の両方を提供する MCP サーバー実装

envYOUR_API_KEY_HEREは、APIキーに置き換えてください

mcp_config.json
{
    "mcpServers": {
        "brave-search": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "-e",
                "BRAVE_API_KEY",
                "mcp/brave-search"
            ],
            "env": {
                "BRAVE_API_KEY": "YOUR_API_KEY_HERE"
            },
            "transport": "stdio"
        }
    }
}

Fetch MCP Server

Web コンテンツの取得機能を提供するモデル コンテキスト プロトコル サーバー。このサーバーにより、LLM は Web ページからコンテンツを取得して処理し、HTML をマークダウンに変換して簡単に利用できるようになります。

MCPツールは複数指定できますので、Brave検索とFetchを指定します。

mcp_config.json
{
    "mcpServers": {
        "brave-search": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "-e",
                "BRAVE_API_KEY",
                "mcp/brave-search"
            ],
            "env": {
                "BRAVE_API_KEY": "YOUR_API_KEY_HERE"
            },
            "transport": "stdio"
        },
        "fetch": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "mcp/fetch"
            ],
            "transport": "stdio"
        }
    }
}

ツールを連続で呼び出してくれています。

Chroma MCP Server

このサーバーは Chroma を利用したデータ取得機能を提供し、AI モデルが生成されたデータとユーザー入力に基づいてコレクションを作成し、ベクトル検索、全文検索、メタデータ フィルタリングなどを使用してそのデータを取得できるようにします。

Dockerイメージが公開されていないので、Dockerfileからビルドします。

git clone https://github.com/chroma-core/chroma-mcp.git
cd chroma-mcp
docker build -t chroma-mcp .
mcp_config.json
{
    "mcpServers": {
        "chroma": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "-v",
                "/tmp/chroma-mcp:/tmp/chroma-mcp",
                "chroma-mcp",
                "chroma-mcp",
                "--client-type",
                "persistent",
                "--data-dir",
                "/tmp/chroma-mcp"
            ],
            "transport": "stdio"
        }
    }
}

コレクションを作成します。

ドキュメントを登録します。
※埋め込みモデルをダウンロードしてインストールします。Chromaサーバーを別途立ち上げて接続する形が良さそうです。

登録ドキュメントを取得します。

エスケープされてますが、一応取得はできました。

(Streamlitがずっと処理中なのが気になるところ)

Playwright MCP

Playwright を使用してブラウザ自動化機能を提供するモデル コンテキスト プロトコル (MCP) サーバー。このサーバーにより、LLM は構造化されたアクセシビリティ スナップショットを通じて Web ページと対話できるようになり、スクリーンショットや視覚的に調整されたモデルが不要になります。

話題のPlaywright MCPです。

Dockerfileが提供されていないので自作しました。

FROM node:lts-bookworm

RUN npm install -g playwright @playwright/mcp@latest && \
    npx playwright install --with-deps chrome

ENTRYPOINT [ "mcp-server-playwright" ]

ビルドします。

docker build -t playwright-mcp .
mcp_config.json
{
    "mcpServers": {
        "playwright": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "-v",
                "/tmp:/tmp",
                "playwright-mcp",
                "--headless"
            ],
            "transport": "stdio"
        }
    }
}

GoogleにアクセスしてPDFにしてみます。

localhost_8501_ (14).png

うまくPDFを作成してくれてます。

image.png

今回の実装では、一回の会話のターンでMCPツールとの接続を閉じる実装になっています。(多分)
なので、会話をまたいでブラウザ操作を指せることはできないと思います。(多分)

Claude Code

Claude CodeをMCPサーバーとして利用します。

Claude CodeのGitHubリポジトリから、Dockerfileinit-firewall.shをコピーしてきます。

ビルドします。

docker build -t claude-code .
mcp_config.json
{
    "mcpServers": {
        "claude-code": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "claude-code",
                "claude",
                "mcp",
                "serve"
            ],
            "transport": "stdio"
        }
    }
}

カレントディレクトリを取得してみます。

ファイルの作成はうまくいくようですが、commandがうまく動作しないようでした。(理由不明)

↓のBashツールの応答取得ができず、ずっと待ち状態です。

まとめ

mcp_config.jsonに必要なMCPツールを追記するだけで、そのツールを使ったチャットアプリが作れます!

おすすめです。

(MCPを使ったClaude Desktopっぽいものを、Claude以外の生成AIモデルで構築できますよ!)

おまけ

こちらのGitHubで公開しております。

今回紹介した内容以外にいくつか機能を追加しています。

  • OpenAIのモデルやGeminiなどにも対応
  • チャットのスレッドに対応
  • ツール利用を促すシステムプロンプト作成機能

64
57
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
64
57

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?