生成AIの学術利用を加速する!!勉学に励む学生を優しくサポートするMoodle対応のMCPサーバーを作りました!!

はじめに

昨今、生成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がある世界」を比較してみましょう。

MCPがない世界

MCPがない世界では、他のAIエージェントAでとても役立つ機能を使っていたとして、それをいざAIエージェントBで使おうとした場合、AIエージェントB向けにその機能を実装し直す必要があります。
というのは、今までは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サーバーがある場合

この場合、MCPクライアントとMCPサーバーは同じ信頼できるネットワーク内にあるため、つまり、このMCPサーバーにアクセスできるのは、LLMアプリのみのため、HTTPヘッダーX-Remote-Userを使ってユーザー名を渡すことができます。
後述するIDトークンを使う場合と比べて、LLMアプリの実装が簡単になります。

信頼できるネットワーク外にMCPクライントとMCPサーバーがある場合

この場合、MCPクライアントとMCPサーバーは同じ信頼できるネットワーク内にはないため、つまり、このMCPサーバーにアクセスできるのは、不特定多数のユーザーが利用する可能性があるため、HTTPヘッダーX-Remote-Userを使ってユーザー名を渡すことはできません。
この場合、IDトークンを使ってユーザー名を渡す必要があります。MCPサーバーはIDトークンを検証し、ユーザー名を取得します。

MCPクライアントとMCPサーバーが同じアプリケーションサーバー上にある場合

この場合、MCPクライアントとMCPサーバーは同じアプリケーションサーバー上にあるため、通信方式としてSTDIOを用います。環境変数REMOTE_USERを使ってユーザー名を渡すことができます。

起動方法

このMCPサーバーはPythonで作成されており、pipコマンドでインストールして利用します。

事前準備

以下のコマンドを実行して、必要なライブラリをインストールしてください。
$ 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 (任意): trueX-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`の環境変数を設定します。
MCPサーバーに接続します。`Connect`ボタンを押します。

 
ツールの一覧を取得して実行します。`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`ボタンを押します。
後の手順は、STDIOで動作確認する場合と同様です。

より実践的なシステムを構築

簡単な動作確認が出来たところで、このMCPサーバーを動かすための、より実践的なシステムを構築してみましょう。以下の図がそのシステム構成です。
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 のプロトコルに従って通信が進みます。

では構築手順を説明していきます。

KeyCloakの設定

クライアントを作成するために、KeyCloakの管理コンソールにログインし、`Clients`メニューから、`Create client`ボタンを押します。

 

`Client ID`に任意の名称を入力し、`Next`ボタンを押します。
 
`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

動作確認

Streamlitのアプリケーションにアクセスします。KeyCloakのログイン画面が表示されるので、ユーザー名とパスワードを入力してログインします。

 
KeyCloakのログイン画面が表示されるので、ユーザー名とパスワードを入力してログインします。

 
ログインに成功すると、チャット画面が表示されます。チャット画面で質問を入力して、送信ボタンのアイコンを押します。すると回答が返ってきます。

まとめ

いかがでしたでしょうか?勉学に励む学生を優しく包むAIって素敵ですね。ときに厳しく、そしてときに優しく、学生を支えるMoodle対応のMCPサーバーをぜひご活用ください。
ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です