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を使用します。
{
"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 タイムゾーン名を使用してタイムゾーン変換を実行し、システムのタイムゾーンを自動検出できるようになります。
上で解説した設定ファイルを使用します。
{
"mcpServers": {
"time": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"mcp/time"
],
"transport": "stdio"
}
}
}
Brave Search MCP Server
Brave Search API を統合し、Web 検索機能とローカル検索機能の両方を提供する MCP サーバー実装
env
のYOUR_API_KEY_HERE
は、APIキーに置き換えてください
{
"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を指定します。
{
"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 .
{
"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 .
{
"mcpServers": {
"playwright": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-v",
"/tmp:/tmp",
"playwright-mcp",
"--headless"
],
"transport": "stdio"
}
}
}
GoogleにアクセスしてPDFにしてみます。
うまくPDFを作成してくれてます。
今回の実装では、一回の会話のターンでMCPツールとの接続を閉じる実装になっています。(多分)
なので、会話をまたいでブラウザ操作を指せることはできないと思います。(多分)
Claude Code
Claude CodeをMCPサーバーとして利用します。
Claude CodeのGitHubリポジトリから、Dockerfile
とinit-firewall.sh
をコピーしてきます。
ビルドします。
docker build -t claude-code .
{
"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などにも対応
- チャットのスレッドに対応
- ツール利用を促すシステムプロンプト作成機能