はじめに
昨今、生成AIが流行っているのは言うまでもありません。ChatGPTから始まり、そしてRAG(Retrieval Augmented Generation)と呼ばれる技術が注目され、さらにAIエージェントなんていうのも出てきています。そして、今、最も熱いのはMCP(Model Context Protocol)でしょう。
今回は、そんなMCPのテクノロジーとMoodle(オープンソースの学習管理システム)を組み合わせたMCPサーバーを作成しましたので、その紹介をしたいと思います。以下のGitリポジトリにてソースコードを公開しています。
このMCPサーバーは、勉学に励む学生を優しく支えるAIツールです。
ざっくりいってしまうと、例えば下図のようにチャット形式のアプリにこのMCPサーバーを組み込むことで、学生がチャット形式で質問した内容に応じて、Moodleの情報を取得して、回答することができます。
例えば、このチャットみたいに、ちょっと弱気な発言をすると、過去の成績を鑑みて、オススメの教材を提案して励ましてくれたり、締切間近の宿題を忘れないように教えてくれたりします。
MCPサーバーとは?
MCPとは、Model Context Protocolの略で、今流行りのAIエージェントに簡単に機能を追加するためのプロトコルです。
AIエージェントに関する詳しい説明は以下の記事を参考にしてください。
そして、このMCPというプロコトルに準拠して作成されたサーバーがMCPサーバーです。
では、このMCPサーバーの機能を理解するために、「MCPがない世界」と「MCPがある世界」を比較してみましょう。
というのは、今まではLLMアプリ(DifyやClaudeなど)はそれぞれ独自の実装方式を持っており、またAIエージェントが使うツールもそれぞれ独自の実装方式をもっており、それぞれの実装方式に合わせて、ツールの方を改修する必要がありました。
例えばAIエージェントAではクラウドストレージにアクセスして、ファイルの一覧や中身を取得する機能があったとしても、AIエージェントBではそのクラウドストレージにアクセスする方法が異なっている場合、AIエージェントB向けにクラウドストレージにアクセスする機能を実装し直す必要があります。
MCPがある世界
MCPがある世界では、AIエージェントAで実装した機能をそのままAIエージェントBでも利用することができます。なぜなら、MCPに準拠したインターフェースを通じて、異なるAIエージェント間で機能を共有できるからです。
ここでは、「MCPサーバーがない世界」で「ツール」と言われていたものは「MCPサーバー」と呼ばれるようになりました。当然「サーバー」と呼ばれるくらいなので、それにラクセスするクライアントである「MCPクライアント」も必要になります。
MCPクライントとMCPサーバーを用意し、その間の通信をMCPに準拠したものにして、MCPサーバー側ではインターネットやクラウドストレージなど各種リソースにアクセスして、MCPクライアントに結果を返すようにします。
このようにして、MCPサーバーを用意することで、異なるAIエージェント間で機能を共有できるようになります。
今回の記事では、MCPの話が本筋ではないので、MCPに関する詳しい説明は割愛します。MCPに関する詳しい説明は以下のYouTubeを参考にしてください。
Moodleとは?
Moodle(ムードル)は、学校や企業の学習をオンラインで運営するための「学習管理システム(LMS)」です。オープンソースで無償利用でき、ブラウザさえあれば受講から課題提出、採点、成績管理まで一通り行えます。
主な登場人物は管理者・教師・学習者の3つになります。
管理者はサイトやユーザーを統括したり、Moodleの基本的な設定(権限管理やメールサーバーなどの設定)を行います。
教師はコース(科目)を作って教材やテストを配置し、学生への各種伝達、課題の提出管理や採点を行います。
学習者は学生のことで、教師からの指示に基づいて、課題などの受講・提出・確認を行います。
ざっくりこんな流れ
このMCPサーバーの処理の流れをざっくり説明します。
① 認証
ユーザーがIdPで認証します。このIdPはKeyCloakやOktaなどのOpenID Connectに対応したものの利用を想定しています。
② 質問
LLMアプリ(DifyやClaudeなど)で質問します。このとき①で取得したIDトークンをHTTPヘッダーAuthorizationにセットして、LLMアプリに渡します。
③ ツール取得
MCPのプロトコルに則り、MCPクライアントからMCPサーバーに対して、利用可能なツールの一覧を取得します。
④ ツール選定
③で取得したツール一覧をAzure OpenAI ServiceなどのLLMに渡し、質問に対して適切なツールを選定します。
⑤ ツール返却
LLMから返却されたツールをLLMアプリが受け取ります。
⑥ ツール呼び出し
LLMアプリがMCPクライアントを通じて、MCPサーバーに対してツールを呼び出します。このとき、②で取得したIDトークンをもとに、MCPサーバーに対して、ユーザー情報を渡します。
⑦ API実行
MCPサーバーはユーザー情報やコース情報などをもとに、MoodleのREST APIを呼び出して、必要な情報を取得します。
MCPサーバーの基本機能
今回作成したMCPサーバーは、MoodleのREST APIを利用して、Moodleの情報を取得する機能を持っております。具体的には以下の機能を提供しています。
■ 成績が低い課題のコースに関するファイル一覧を取得
ユーザーの履修コースのうち、成績の回答率が 一定値を下回るコースに添付されたファイルを列挙します。
■ 未提出の課題の取得
ユーザーの未提出と判定される課題を検索して返します。
■ ユーザーが履修しているコース一覧の取得
ユーザーが履修しているコースの一覧を取得します。
■ コース検索
全コースからキーワード検索を行います。表示名(displayname)や概要(summary)も出力に含めます。
■ 締め切り間近の課題取得
ユーザーの未提出と判定される課題のうち、締め切りが近いものを検索して返します。
MCPサーバーの詳細機能
MCPサーバーのより詳細な機能について説明します。
- get_resources_under_specific_grade
- ユーザーの履修コースのうち、成績の回答率が環境変数 COMPLETION_THRESHOLD を下回るコースに添付されたファイルを列挙します。
- ユーザー名は REMOTE_USER 環境変数、または X-Remote-User ヘッダー、あるいは ID トークンで指定します。
- get_my_unsubmitted_assignments
- ユーザーの未提出と判定される課題を検索して返します。課題の提出判定には 環境変数mod_assign_get_submission_status を優先利用し、
- ユーザー名は REMOTE_USER 環境変数、または X-Remote-User ヘッダー、あるいは ID トークンで指定します。
- get_my_enrolled_courses
- ユーザーが履修しているコース一覧を返します。
- ユーザー名は REMOTE_USER 環境変数、または X-Remote-User ヘッダー、あるいは ID トークンで指定します。
- find_my_courses_by_keyword(keyword)
- core_course_search_courses を利用して全コースからキーワード検索を行います。表示名(displayname)や概要(summary)も出力に含めます。
- get_upcoming_deadlines
- 環境変数UPCOMING_DEADLINES_DAYS 日以内に締切が来る課題を集約して返します。mod_assign_get_assignments を優先して使い、
- フォールバックで gradereport から締切を探します。
- ユーザー名は REMOTE_USER 環境変数、または X-Remote-User ヘッダー、あるいは ID トークンで指定します。
システム構成
このMCPサーバーで構成することの出来るシステム構成の例をいくつか紹介します。
信頼できるネットワーク内にMCPクライントとMCPサーバーがある場合
後述するIDトークンを使う場合と比べて、LLMアプリの実装が簡単になります。
信頼できるネットワーク外にMCPクライントとMCPサーバーがある場合
この場合、MCPクライアントとMCPサーバーは同じ信頼できるネットワーク内にはないため、つまり、このMCPサーバーにアクセスできるのは、不特定多数のユーザーが利用する可能性があるため、HTTPヘッダーX-Remote-Userを使ってユーザー名を渡すことはできません。
事前準備
以下のコマンドを実行して、必要なライブラリをインストールしてください。
$ git clone https://github.com/ntakei-sti/moodle-mcp-server.git
$ cd moodle-mcp-server
$ ppip install -r requirements.txt
環境変数の設定
このプロジェクトは環境変数で各種挙動を制御します。.env ファイルを作成するか、シェルの環境変数として設定してください。通信方式(MCP_TRANSPORT)により必要な環境変数が異なります。
- 共通
- MOODLE_API_URL (必須): Moodle サイトのベース URL(例: <https://moodle.example.com>)
- MOODLE_WSTOKEN (必須): Moodle の Webservice トークン
- COMPLETION_THRESHOLD (任意): 成績の「回答率」閾値(デフォルト 80)
- UPCOMING_DEADLINES_DAYS (任意): get_upcoming_deadlines が参照する日数ウィンドウ(デフォルト 7)
- MCP_TRANSPORT (任意): 通信方式。sse(既定)または stdio
- SSE モード(MCP_TRANSPORT=sse)もしくはStreamable HTTPモード(MCP_TRANSPORT=streamable-http)で主に使用する変数
- MCP_SERVER_HOST (任意): サーバのバインドアドレス(デフォルト 0.0.0.0)
- MCP_SERVER_PORT (任意): サーバのポート(デフォルト 8000)
- ACCEPT_REMOTE_USER_HEADERS (任意): true で X-Remote-User ヘッダを優先(デフォルト true)
- Bearer トークン検証を使う場合に必要:
- OIDC_METADATA_URL: OpenID Provider のメタデータ URL(必須)
- OIDC_AUDIENCE (任意): 期待する audience
- OIDC_USERNAME_CLAIM (任意): ユーザー識別子に使うクレーム名(デフォルト sub)
- STDIO モード(MCP_TRANSPORT=stdio)で使用する変数
- REMOTE_USER (必須): 実行中プロセスで扱う Moodle の username
注意: ACCEPT_REMOTE_USER_HEADERS を有効にする場合、リモートヘッダは信頼できるプロキシからのみ渡す ようにしてください。
例: `.env` ファイルテンプレート(SSEもしくはStreamable HTTP)
MOODLE_API_URL=https://moodle.example.com
MOODLE_WSTOKEN=your_moodle_ws_token
COMPLETION_THRESHOLD=80
OIDC_METADATA_URL=https://idp.example.com/.well-known/openid-configuration
OIDC_AUDIENCE=your-client-id
OIDC_USERNAME_CLAIM=preferred_username
ACCEPT_REMOTE_USER_HEADERS=true
UPCOMING_DEADLINES_DAYS=7
MCP_TRANSPORT=sse
MCP_SERVER_HOST=0.0.0.0
MCP_SERVER_PORT=8000
例: `.env` ファイルテンプレート(STDIO)
MOODLE_API_URL=https://moodle.example.com
MOODLE_WSTOKEN=your_moodle_ws_token
REMOTE_USER=your-username
MCP_TRANSPORT=stdio
起動
以下のコマンドを実行して、MCPサーバーを起動します。
$ python moodle_mcpserver.py
動作確認
MCP Inspectorというツールを使うと簡単に動作確認ができます。MCP InspectorはMCPサーバーに対して、MCPのプロトコルに則った通信を行うことができるツールです。動作するためにはNode.jsの環境が必要です。
以下のコマンドでMCP Inspectorを起動します。
$ npx @modelcontextprotocol/inspector
STDIOで動作確認する場合
まず基本的な設定を行います。
① Transportで`stdio`を選択します。
② Commandに`python `を選択します。
③ Commandにmoodle_mcp_server.pyのパスを入力します。
④ `MOODLE_API_URL`、`MOODLE_WSTOKEN`、`REMOTE_USER`の環境変数を設定します。
ツールの一覧を取得して実行します。`Tools`タブを選択し(①)、`List Tools`ボタンを押します(②)。ツールの一覧が取得されます(③)。ツールを選んで、`Run Tool`ボタンを押すと、ツールが実行されます(④)。
ツールの実行に成功すると、以下のように結果が表示されます。
Stremable HTTPもしくはSSEで動作確認する場合
まず基本的な設定を行います。
① Transport Typeは、`Streamable HTTP`もしくは`sse`を選択します。
② Transport Typeが`Streamable HTTP`の場合、`http://[MCPサーバーのホスト名]/mcp`を入力します。Transport Typeが`sse`の場合、`http://[MCPサーバーのホスト名]/sse`を入力します。
③ HTTPヘッダを追加します。`ACCEPT_REMOTE_USER_HEADERS`を`true`に設定している場合、`X-Remote-User`ヘッダーを追加します。IDトークンを使う場合、`Authorization`ヘッダーを追加します。両方とも値はMoodleのユーザー名です。
⑤ `Connect`ボタンを押します。
LLM アプリとして Dify を利用します。Dify は MCP に対応しているため、MCP クライアントとして動作します。
Dify 自体には認証機能がないため、別途チャット UI を提供するアプリケーションサーバーを用意します。ここでは Streamlit を利用します。Streamlit は OpenID Connect に対応しているため、KeyCloak でユーザーを認証した後に ID トークンを取得できます。
ユーザーが認証を終えると、取得した ID トークンが Streamlit に渡され、Streamlit 側でトークンの検証を行い、Moodle のユーザー名を取得します。そのうえで Streamlit から Dify に対して API を呼び出します。Dify は公開 API を提供しており、そこに質問を送ることで回答を得ることができます。この際、ID トークンから取得したユーザー名を API のパラメーターとして渡すことで、Dify がユーザー名を認識できるようにします。
Dify は MCP クライアントとして、MCP サーバーに対して Moodle の情報を取得するためのツール一覧の取得やツール実行を行います。ここから先は MCP のプロトコルに従って通信が進みます。
では構築手順を説明していきます。
`Client authentication`を`ON`(①)、`Authorization`を`ON`(②)にしてクライアントシークレットによる認証を有効にします。認可コードフローに対応するために、`Standrd flow`にチェックを入れます(③)。最後に`Next`ボタンを押します(④)。
Valid redirect URIsに`http[s]://[Streamlitのホスト名]/oauth2callback`を入力し(①)、`Save`ボタンを押します(②)。
クライアントシークレットを確認するために、`Credentials`タブを選択します(①)。`Secret`の値を控えておきます(②)。後でStreamlitの設定で利用します。
Streamlitの設定
Streamlitでは、KeyCloakなどのOpenID Connectに対応したIdPを利用して、ユーザー認証を行い、DifyのAPIを呼び出す必要があります。このアプリケーションは、以下のGitHubリポジトリで公開していますので、Cloneして利用してください。
Cloneしたら、まず、`.env`を以下のように設定します。`DIFY_API_URL`はDifyのAPIエンドポイントを指定します。`DIFY_API_KEY`はDifyのAPIキーを指定します。`OIDC_USERNAME_CLAIM`はKeyCloakのユーザー名に対応するクレーム名を指定します。KeyCloakのデフォルトでは`preferred_username`ですが、環境によって異なる場合がありますので、適宜変更してください。
DIFY_API_URL=http[s]://<Difyのホスト名>/v1/chat-messages
DIFY_API_KEY=<DifyのAPIキー>
OIDC_USERNAME_CLAIM=<KeyCloakのユーザー名に対応するクレーム名>
次に、`secrets.toml`を以下のように設定します。`redirect_uri`はKeyCloakのクライアント設定で指定した`Valid redirect URIs`を指定します。`cookie_secret`は任意の文字列を指定します。`client_id`はKeyCloakのクライアントIDを指定します。`client_secret`はKeyCloakのクライアントシークレットを指定します。`server_metadata_url`はKeyCloakのメタデータURLを指定します。
[auth]
redirect_uri = "http[s]://<Streamlitのホスト名>/oauth2callback"
cookie_secret = "<任意の文字列>"
client_id = "<KeyCloakのクライアントID>"
client_secret = "<KeyCloakのクライアントシークレット>"
server_metadata_url = "http[s]://<KeyCloakのホスト名>/realms/<レルム名>/.well-known/openid-configuration"
Streamlitアプリケーションを起動します。
$ streamlit run app.py
Dify設定
まず、DifyのマーケットプレイスからMCPプラグインをインストールします。Difyの管理コンソールにログインし、`Plugins`メニューから、`Agent Strategies`のプラグインをインストールします。
そして、ワークフロー内でMCPプラグインを利用するように設定します。`Agent`というブロックが追加出来るようになっているので、これをワークフローに追加します。
`Agentic Strategy`は`Function Calling (Support MCP Tools)`を選択します(①)。`MCP SERVERS CONFIG`は、以下のように設定します(②)。
{
"server_name1": {
"transport": "sse",
"headers": {
"X-Remote-User": 🏡Start/{x}sys.user_id
},
"url": "http[s]://<MCPサーバーのホスト名>/sse"
}
}
`INSTRUCTIONS`は以下のように設定します(③)。このシステムメッセージを定義することにより、ユーザーの弱気な発言を拾って、Moodleの情報を取得して、優しく励ますことができます。
あなたはmoodleからいろんな情報を取得する賢いエージェントです。
ユーザーが、「成績が出なくて困った」などの類の弱気な趣旨の質問をした場合には、ツールget_resources_under_specific_gradeを呼んでください。その回答は、成績が思わしく無いコースの資料ですので、それらの資料を優しく提案してあげてください。
MCPサーバーの設定
MCPサーバーを起動します。起動するための環境変数の説明は説明済みですので割愛します。今回の構成では、通信方式をSSE、MCPサーバーはDifyからのみアクセスされることを前提として、`ACCEPT_REMOTE_USER_HEADERS`を`true`に設定します。以下は`.env`ファイルの例です。
MOODLE_API_URL=<MoodleのURL>
MOODLE_WSTOKEN=<MoodleのWebサービスのトークン>
COMPLETION_THRESHOLD=80
MCP_TRANSPORT=sse
ACCEPT_REMOTE_USER_HEADERS=true
MCPサーバーを起動します。
$ python moodle_mcp_server.py