
M5StackのModule-LLMはNPUを搭載し、ローカルでLLMを実行できる組み込みモジュールです。LLMでAIと対話をしたり、TTSのテキストの音声合成など、様々なモデルを使用することで手元でAIを体験することができます。今回はこのローカルで動作するAIを、MCPという仕組みを使って、別のAIエージェントと連携してみました。
MCPに触れて1週間程度の人間が書いてる内容なので、間違いや説明不足があるかもしれませんが大目に見てください。
2025/05/04 追記「MCPライブラリをFastMCPからgradioに変更してみた」
MCPサーバーとは?
MCPサーバーとは、AIが外部のツールと連携してデータのやりとりを行ったり、操作をするためのものです。例えばPCのファイルの内容を取得したり、ソフトの操作するというような作業をAIができるようにするためのインターフェースがMCPで、それを外部から接続できる機能を提供するのがMCPサーバーです。
APIと似てますが、APIはサーバー側の仕様に合わせてクライアント側のプログラムも修正しなくてはなりません。しかしMCPクライアントは、サーバー側の仕様を理解していい感じにやってくれるのが特徴です。ユーザーはいちいちMCPの使い方を説明する必要はありません。AIはMCPサーバーにどんな機能があるのかを知り、必要に応じて実行してくれます。ただし、たまに間違えます。
Module-LLMをはじめるには
とりあえず流行に乗ろうとModule-LLMを買ってみたのですが、何をすればいいのかわかりませんでした。何ができるのかもわからなかったので、とりあえずはModule-LLMの同人誌を買って、書いてあることをただ進めました。

Module-LLM MAniaX【電子書籍版】
著者 aNo研 A5サイズ・156ページ
こちらの内容は古く、現在のものとはちょっと違ったりしますが、しくみの解説やサンプルプログラムもあってModule-LLMの理解が進みます。最新情報は公式ドキュメントを参考にするといいと思います。
特にパッケージのインストール方法が新しくなってるのでWebの情報と合わせてみてください。
ほかには Module LLM Advent Calendar も情報の宝庫です。
LEDを光らせるMCPサーバー
まずは練習として、LEDを光らせるMCPサーバーを作ってみました。LLMとは何も関係ありませんね。でもまずは動くことを確認することが大切。
プログラムは GitHub にアップしました。(led.py)
#!/usr/bin/env python3
import asyncio
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
# Initialize FastMCP server
mcp = FastMCP(
"led",
host="0.0.0.0",
port=8000,
)
# LEDの明るさを設定する
def set_led_brightness(color: str, value: int) -> None:
with open(f"/sys/class/leds/{color}/brightness", "w") as f:
f.write(str(value))
# MCP Tool: LEDの明るさを設定する
@mcp.tool()
async def set_led_colors(red: int, green: int, blue: int) -> str:
"""Change the LED colors. (0-255)"""
if (red < 0 or red > 255 or green < 0 or green > 255 or blue < 0 or blue > 255):
return "Invalid color values. Please provide values between 0 and 255."
try:
set_led_brightness("R", red)
set_led_brightness("G", green)
set_led_brightness("B", blue)
return "\n---\nsuccess"
except Exception as e:
return f"\n---\nerror: {str(e)}"
if __name__ == "__main__":
# Run SSE over HTTP on port 8000
asyncio.run(
mcp.run_sse_async()
)
MCPサーバーの作り方は こちら のページを参考にしました。真似て書いただけなのでよくわかってませんが、set_led_colors() というのがMCPのツールになります。これにはred, green, blueという3つ整数型の引数があり、ここに0~255の値を与えると、本体のLEDが光るというわけです。
このMCPツールの関数のコメントに “Change the LED colors. (0-255)” という説明を入れてあり、AIには red, green, blue に0~255の数字を入れてくれるだろう、という期待をしています。APIではないのでうまくいくとは限りません。実際「LEDを赤くして」と頼んだところredだけの値を送信してエラーになることもありました。その後3つ与えないといけないと気付いて再実行してくれました。やっぱAIってすげーな、と思った瞬間です。
クライアント側の設定
Cursorの場合は歯車マーク→MCPでMCPサーバーの追加ができます。+Add new global MCP Serverを押すとJSONの編集画面が開きます。以下はCursorの設定です。
{
"mcpServers":{
"modulellm":{
"url":"http://192.168.xx.xx:8000/sse"
}
}
}
URLは module-llm.local でもいけますが、時々DNSの解決ができないことがあるので、IPアドレスで指定した方が安定して使えます。以下はModule-LLMのIPアドレスを固定する方法です。設定に失敗してアクセスできなくなったらシリアルからアクセスして直しましょう。
source /etc/network/interfaces.d/*
auto eth0
allow-hotplug eth0
iface eth0 inet static
address 192.168.xx.xxx
netmask 255.255.255.0
gateway 192.168.xx.x
dns-nameservers 8.8.8.8 1.1.1.1
MCPサーバーを起動して、Cursorの画面のスイッチをONにします。正常に接続されればスイッチが緑色になります。
Claude Desktopの場合は、直接リモートのホストに接続することはできないそうなので mcp-proxy を使って中継させて使いました。以下はClaude Desktopの設定です。
{
"mcpServers":{
"modulellm":{
"command":"C:your_opath_to\\mcp-proxy.exe",
"args":[
"http://192.168.xx.xxx:8000/sse"
]
}
}
}
実行してみた結果
ちゃんと動いてますね。データの与え方は何も指示してないのに、MCPツールの仕様を理解して実行してくれました。
LLMとTTSのMCPサーバーを作る
ここからがいよいよ本番。Module-LLMのLLM(AIエージェント)と対話したり、テキストの音声合成(TTS)を試してみたいと思います。プログラムは Module-LLM MAniaX のサンプルコードを参考にさせていただきました。
Module-LLMにはStackFlowというシステムが稼働しいて、StackFlowのサーバーにデータを送信するとLLMやTTSなどを制御してくれます。
今回はデフォルトでインストールされているLLMモデルを使用しました。
・LLM: qwen2.5-0.5B-prefill-20e
・TTS: melotts_zh-cn
現時点では日本語が喋れるTTSはリリースされていないので、喋れるのは英語か中国語のみになります。
作成したMCPサーバーのプログラムは GitHub にアップしました。(mcp_llmtts.py)
デバッグ用MCPクライアント
MCPサーバーを操作しようとしてうまく動かない場合は、まずは問題切り分けのために、デモ用のMCPクライアントからMCPサーバーにアクセスしてみるといいでしょう。(サンプルMCPクライアント client-http.py)このサンプルプログラムは、MCPサーバーで使えるツールの一覧表示と、send_message、speak_text、set_led_colorsを実行します。
root@m5stack-LLM:# uv run client-http.py
MCP Server URL: http://127.0.0.1:8000/sse
=== Available tools ===
- send_message: Send a message to the LLM and get the response.
Parameters:
- message (string): No description
- speak_text: It will speak when you give it a message. English only.
Parameters:
- message (string): No description
- set_led_colors: Change the LED colors. (0-255)
Parameters:
- red (integer): No description
- green (integer): No description
- blue (integer): No description
=== Send Message ===
[TextContent(type='text', text='\n---\n私はスタックチャンという名前のAIアシスタントで、親切で礼儀正しく正直な人間です。あなたの質問に答えたり、情報を提供したりするためのサポートを提供します。何かお手伝いできることがありますか?\n\n', annotations=None)]
=== SET LED COLORS ===
[TextContent(type='text', text='\n---\nsuccess', annotations=None)]
=== Seak Text ===
[TextContent(type='text', text='\n---\nsuccess', annotations=None)]
=== SET LED COLORS ===
[TextContent(type='text', text='\n---\nsuccess', annotations=None)]
それぞれ結果が返ってくれば準備完了です。もしエラーが出る場合は、MCPサーバーの方の出力にエラーが出てないか確認してみてください。Module-LLMは結構動作が不安定でよくに止まります。llm-sysを再起動したり、OS事態を再起動すると改善します。
systemctl restart llm-sys
AIエージェントに使ってもらう
それでは実際にAIにMCPサーバーのツールを操作してもらいましょう。クライアント側で正しくMCPサーバーが認識されているか確認します。Claude Desktopの場合は、+の横のツールボタンを押すと使用するツールのオンオフができます。

Cursorの場合は歯車アイコン→MCPでオンオフできます。

それではAIエージェントに、MCPツールを使ってもらいましょう。こちらからの指示は「メッセージを送って相手の名前を聞いてみてください。」とだけです。

ちゃんと自己紹介をしてくれましたね。名前はスタックチャンと言うようです。かわいいですね。
それでは次は「スタックチャンに喋ってもらいましょう。英語でスタックチャンに簡単な挨拶の英語を喋らせてください。」と指示してみます。

スタックチャンに英語で挨拶を…と伝えたものの日本語で返ってきましが、それをAIエージェントが英訳して、TTSで音声合成してくれました。AIとAIの競業ですね!
こんな感じでざっくりとMCPを体験してみましたが、想像以上にAIが賢くうまい感じに連携してくれたのが面白かったです。
余談 Module-LLMは普通にLinuxとして遊べる
Module-LLMにはUbuntuがインストールされているので、普通にLinuxサーバーとして遊べます。パッケージも普通にaptコマンドなどでインストールできます。メモリは1GB(4GB中3GBはLLM用)、ストレージは32GBあり、2コアのARM Cortex-A53があり、シングルコア性能ではRaspberry PI 4くらい、マルチコア性能ではRaspberry PI 2くらいあります。以下はUnixbenchの結果です。
BYTE UNIX Benchmarks (Version 5.1.3)
System: m5stack-LLM: GNU/Linux
OS: GNU/Linux -- 4.19.125 -- #1 SMP PREEMPT Wed Nov 20 14:43:36 CST 2024
Machine: aarch64 (aarch64)
Language: en_US.utf8 (charmap="UTF-8", collate="UTF-8")
CPU 0: ARM Cortex-A53 (48.0 bogomips)
CPU Features: fp asimd evtstrm crc32 cpuid
CPU 1: ARM Cortex-A53 (48.0 bogomips)
CPU Features: fp asimd evtstrm crc32 cpuid
14:46:39 up 42 min, 1 user, load average: 1.62, 1.57, 1.54; runlevel 2025-02-20
------------------------------------------------------------------------
Benchmark Run: Fri Apr 25 2025 14:46:39 - 15:14:38
2 CPUs in system; running 1 parallel copy of tests
Dhrystone 2 using register variables 6726783.3 lps (10.0 s, 7 samples)
Double-Precision Whetstone 1646.8 MWIPS (10.0 s, 7 samples)
Execl Throughput 695.7 lps (30.0 s, 2 samples)
File Copy 1024 bufsize 2000 maxblocks 180539.3 KBps (30.0 s, 2 samples)
File Copy 256 bufsize 500 maxblocks 54000.8 KBps (30.0 s, 2 samples)
File Copy 4096 bufsize 8000 maxblocks 437487.0 KBps (30.0 s, 2 samples)
Pipe Throughput 433941.2 lps (10.0 s, 7 samples)
Pipe-based Context Switching 85813.8 lps (10.0 s, 7 samples)
Process Creation 2170.7 lps (30.0 s, 2 samples)
Shell Scripts (1 concurrent) 1571.6 lpm (60.0 s, 2 samples)
Shell Scripts (8 concurrent) 320.3 lpm (60.1 s, 2 samples)
System Call Overhead 625569.8 lps (10.0 s, 7 samples)
System Benchmarks Index Values BASELINE RESULT INDEX
Dhrystone 2 using register variables 116700.0 6726783.3 576.4
Double-Precision Whetstone 55.0 1646.8 299.4
Execl Throughput 43.0 695.7 161.8
File Copy 1024 bufsize 2000 maxblocks 3960.0 180539.3 455.9
File Copy 256 bufsize 500 maxblocks 1655.0 54000.8 326.3
File Copy 4096 bufsize 8000 maxblocks 5800.0 437487.0 754.3
Pipe Throughput 12440.0 433941.2 348.8
Pipe-based Context Switching 4000.0 85813.8 214.5
Process Creation 126.0 2170.7 172.3
Shell Scripts (1 concurrent) 42.4 1571.6 370.6
Shell Scripts (8 concurrent) 6.0 320.3 533.8
System Call Overhead 15000.0 625569.8 417.0
========
System Benchmarks Index Score 349.6
------------------------------------------------------------------------
Benchmark Run: Fri Apr 25 2025 15:14:38 - 15:42:40
2 CPUs in system; running 2 parallel copies of tests
Dhrystone 2 using register variables 13359822.9 lps (10.0 s, 7 samples)
Double-Precision Whetstone 3267.9 MWIPS (10.0 s, 7 samples)
Execl Throughput 1226.9 lps (29.8 s, 2 samples)
File Copy 1024 bufsize 2000 maxblocks 339262.9 KBps (30.0 s, 2 samples)
File Copy 256 bufsize 500 maxblocks 102300.4 KBps (30.0 s, 2 samples)
File Copy 4096 bufsize 8000 maxblocks 837516.9 KBps (30.0 s, 2 samples)
Pipe Throughput 861233.8 lps (10.0 s, 7 samples)
Pipe-based Context Switching 121443.2 lps (10.0 s, 7 samples)
Process Creation 3015.9 lps (30.0 s, 2 samples)
Shell Scripts (1 concurrent) 2335.4 lpm (60.1 s, 2 samples)
Shell Scripts (8 concurrent) 324.3 lpm (60.1 s, 2 samples)
System Call Overhead 1239842.1 lps (10.0 s, 7 samples)
System Benchmarks Index Values BASELINE RESULT INDEX
Dhrystone 2 using register variables 116700.0 13359822.9 1144.8
Double-Precision Whetstone 55.0 3267.9 594.2
Execl Throughput 43.0 1226.9 285.3
File Copy 1024 bufsize 2000 maxblocks 3960.0 339262.9 856.7
File Copy 256 bufsize 500 maxblocks 1655.0 102300.4 618.1
File Copy 4096 bufsize 8000 maxblocks 5800.0 837516.9 1444.0
Pipe Throughput 12440.0 861233.8 692.3
Pipe-based Context Switching 4000.0 121443.2 303.6
Process Creation 126.0 3015.9 239.4
Shell Scripts (1 concurrent) 42.4 2335.4 550.8
Shell Scripts (8 concurrent) 6.0 324.3 540.5
System Call Overhead 15000.0 1239842.1 826.6
========
System Benchmarks Index Score 591.6
驚くのはその消費電力です。

LLMで推論を行っている最中の測定値ですが、5V 0.25Aと1.25Wしかありません。たったこれだけの消費電力でLLMが動いてるなんてすごいですね。
余談 ストレージをバックアップすべし!
Module-LLMはストレージが壊れやすいという印象です。実際に壊れてファームウェアのインストールからやり直したこともあります。せっかく作成したプログラムや設定が消えると悲しいので、SDカードにバックアップを取っておくと安心です。Module-LLMにSDカードを挿入すると自動的にマウントします。
dfコマンドで/dev/mmcblk1p1というデバイスが/mnt/mmcblk1p1にマウントしていることを確認
root@m5stack-LLM:# df -Th
Filesystem Type Size Used Avail Use% Mounted on
/dev/root ext4 28G 6.0G 22G 22% /
tmpfs tmpfs 480M 0 480M 0% /dev/shm
tmpfs tmpfs 192M 896K 191M 1% /run
tmpfs tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs tmpfs 480M 0 480M 0% /tmp
/dev/mmcblk1p1 ext2 30G 13G 15G 47% /mnt/mmcblk1p1
tmpfs tmpfs 96M 0 96M 0% /run/user/0
vfatからext4に変更してフォーマット
# umount /mnt/mmcblk1p1
# mkfs.ext2 /dev/mmcblk1p1
# mount -o noatime,nodiratime /dev/mmcblk1p1 /mnt/mmcblk1p1
バックアップスクリプト
#!/bin/bash
BACKUP_TO=/mnt/mmcblk1p1/backup
cd /
mkdir -p ${BACKUP_TO}
rsync -aH --delete \
--exclude=proc \
--exclude=sys \
--exclude=dev \
--exclude=run \
--exclude=tmp \
--exclude=mnt \
--exclude=media \
--exclude=lost+found \
--exclude=etc/configfs \
--exclude=opt/swapfile \
./ \
${BACKUP_TO}/
rsyncのオプションや–excludeは適宜修正してください。初回バックアップ時はフルバックアップするので時間がかかりますが、2回目以降は差分バックアップなので早く終わるはずです。
余談 その他のモデル
標準でインストールされているモデル以外にも、他のモデルが入手可能です。
root@m5stack-LLM:# apt list|grep llm-model
llm-model-audio-en-us/stable 0.2 arm64
llm-model-audio-zh-cn/stable 0.2 arm64
llm-model-deepseek-r1-1.5b-ax630c/stable 0.3 arm64
llm-model-deepseek-r1-1.5b-p256-ax630c/stable 0.4 arm64
llm-model-depth-anything-ax630c/stable 0.3 arm64
llm-model-internvl2.5-1b-ax630c/stable 0.4 arm64
llm-model-llama3.2-1b-p256-ax630c/stable 0.4 arm64
llm-model-llama3.2-1b-prefill-ax630c/stable 0.2 arm64
llm-model-melotts-zh-cn/stable 0.4 arm64
llm-model-openbuddy-llama3.2-1b-ax630c/stable 0.2 arm64
llm-model-qwen2.5-0.5b-int4-ax630c/stable 0.4 arm64
llm-model-qwen2.5-0.5b-p256-ax630c/stable 0.4 arm64
llm-model-qwen2.5-0.5b-prefill-20e/stable 0.2 arm64
llm-model-qwen2.5-1.5b-ax630c/stable 0.3 arm64
llm-model-qwen2.5-1.5b-int4-ax630c/stable 0.4 arm64
llm-model-qwen2.5-1.5b-p256-ax630c/stable 0.4 arm64
llm-model-qwen2.5-coder-0.5b-ax630c/stable 0.2 arm64
llm-model-sherpa-ncnn-streaming-zipformer-20m-2023-02-17/stable 0.2 arm64
llm-model-sherpa-ncnn-streaming-zipformer-zh-14m-2023-02-23/stable 0.2 arm64
llm-model-sherpa-onnx-kws-zipformer-gigaspeech-3.3m-2024-01-01/stable 0.3 arm64
llm-model-sherpa-onnx-kws-zipformer-wenetspeech-3.3m-2024-01-01/stable 0.3 arm64
llm-model-silero-vad/stable 0.3 arm64
llm-model-single-speaker-english-fast/stable 0.2 arm64
llm-model-single-speaker-fast/stable 0.2 arm64
llm-model-whisper-base/stable 0.3 arm64
llm-model-whisper-tiny/stable 0.3 arm64
llm-model-yolo11n-hand-pose/stable 0.3 arm64
llm-model-yolo11n-pose/stable 0.3 arm64
llm-model-yolo11n-seg/stable 0.3 arm64
llm-model-yolo11n/stable 0.2 arm64
例えば標準でインストールされているモデルは qwen2.5-0.5b-prefill-20e ですが、qwen2.5-1.5b-p256-ax630c を使うとかなり賢くなります。その代わり応答はとても遅くなります。性能か、スピードか、トレードオフですね。
追記 MCPライブラリをFastMCPからgradioに変更してみた
gradioというUIを提供するライブラリでMCPサーバーを構築できることを知りました。調べてみるととても簡単そうだったので、FastMCPからgradioに変更してみました。
修正したプログラムはこちら→ mcp_llmtts_gradio.py

これがgradioによるWebのUIです。初めて使ったんですが、ブラウザでツールのデバッグができて便利ですね。これは素晴らしい。
URLは http://192.168.x.xxx:7860/ のようになります。
MCPサーバーのURLは http://192.168.x.xxx:7860/gradio_api/mcp/sse のようになります。