はじめに
Databricks on AWSの東京リージョンにてDatabricks Appsが利用できるようになり、以下のようなサンプルアプリを作る記事を書きました。
しかし、Databricks AppsはWebアプリケーションを作るだけではなく、MCPサーバも作れるのです。
以下のように海外記事の邦訳紹介もあります。
Unity CatalogのマネージドMCPサーバもかなり便利で、ほとんどのユースケースはこちらで十分だと思いますが、レイテンシが大きかったり複雑な処理を行う場合などは独自にMCPサーバを立てた方がよい場合があります。
この希望を叶えてくれるのがDatabricksのカスタムMCPサーバであり、しかもDatabricks Appsの機能を使うことで認証やリソース管理、基本的なロギングなどを全部やってくれます。
MCPサーバは認証関係が複雑だという印象があるのですが、このあたりDatabricksの仕組としてやってもらえるのはとても助かります。
というわけで実際にカスタムMCPサーバを構築してみるという趣旨の内容になります。
環境はDatabricks on AWS(東京リージョン)で実施しました。
記事中にMarkitdownを使って特定サイトの情報を取得している部分があります。
利用の仕方によってはスクレイピングになりますので、試される場合はサイトが許可しているかなど確認の上行ってください。
作ってみるMCPサーバの機能・技術概要
こちらの記事で、Unity Catalog Functionsとして実装した、マークダウン変換ツールconvert_to_markdown
をカスタムMCPサーバに実装します。
実はUnity Catalog FunctionsでMarkitdownを使うのは相性が悪く(30秒のタイムアウトと出力データサイズの制限があるため)、制約の少ないカスタムMCPサーバで利用してみます。
実装方式については通常のMCPサーバ実装と同じはずですが、今回は以下の公式exampleに則ってFastMCPを利用して実装しました。
実装方法も上記サンプルを参考にさせてもらっています。
では、実装してみましょう。
Step1. MCPサーバの実装
ワークスペースの任意の場所にmcp-custom-serverというフォルダを作成し、以下のように4つのファイルを配備します。
※ staticフォルダにはアプリを開いたときに表示されるindex.htmlを置いています。今回は説明を割愛します。
各ファイルの内容を順番に記載します。
app.py
MCPサーバのコア実装です。
ここにMCPサーバが提供するツールやリソースの内容を記載しています。
今回はconvert_to_markdown
というツール1種のみを提供しています。
また、Streamable HTTPで通信するMCPサーバ設定となっています。
from pathlib import Path
from mcp.server.fastmcp import FastMCP
from fastapi import FastAPI
from fastapi.responses import FileResponse
from markitdown import MarkItDown
STATIC_DIR = Path(__file__).parent / "static"
# Create an MCP server
mcp = FastMCP("Custom MCP Server on Databricks Apps")
@mcp.tool()
def convert_to_markdown(url: str) -> dict[str, str]:
"""
Converts the content of a given URL to markdown format.
Args:
url (str): The URL of the content to be converted.
Returns:
dict[str, str]: A dictionary containing the title and text content of the converted markdown.
"""
md = MarkItDown(enable_plugins=False)
result = md.convert(url)
return {"title": result.title, "text": result.text_content}
mcp_app = mcp.streamable_http_app()
app = FastAPI(
lifespan=lambda _: mcp.session_manager.run(),
)
@app.get("/", include_in_schema=False)
async def serve_index():
return FileResponse(STATIC_DIR / "index.html")
app.mount("/", mcp_app)
main.py
Uvicornを使ってAPIサーバを起動する処理です。
app.pyと1個に統合も可能ですが、今回は処理を分離しました。
import uvicorn
def main():
uvicorn.run(
"app:app",
host="0.0.0.0",
port=8000,
reload=True, # optional
)
if __name__ == "__main__":
main()
app.yaml
Databricks Appsの設定ファイルです。
単純にmain.py
をPythonで実行するコマンドのみを記載しています。
command: [
"python",
"main.py"
]
requirements.txt
必要なパッケージ群。Appsへのデプロイ時にインストールされます。
fastapi>=0.115.12
mcp[cli]>=1.10.0
uvicorn>=0.34.2
markitdown[all]>=0.1.2
全体的にファイル数も少なく、シンプルです。
ツール等を追加したい場合はapp.py
に処理を記載することで実現できます。
では、作成したコード類をAppsにデプロイします。
Step2. Databricks Appsへデプロイ
アプリの画面から、新しいアプリを作ります。テンプレートを使わず、カスタムアプリとして作成します。
(もしくはAssetBundleを使って作成してもOK)
名前は任意ですが、mcp-custom-server-bundlesにします。
構成は追加設定不要ですので、このままアプリを作成してください。
数分後にコンピュートが作成され、コードセットのデプロイが可能になります。
デプロイが可能になったら、Step1で作成した場所を指定してデプロイしてください。
デプロイ後、サーバが起動します。
Step3. Playgroundから使ってみる
作ったMCPサーバを試験利用してみましょう。
Playgroundを開き、ツールが利用可能なモデルを選択して、ツールを追加します。
ツール追加画面から「MCP Servers」を選択。
DatabricksアプリのMCPサーバから、先ほど作成したMCPサーバを選択します。
では、使ってみます。
問題なく動いてそうです。
Step4. ノートブックから使ってみる
Databricksのノートブック上からもMCPサーバにアクセスしてツール情報を引き出してみます。
まずはノートブックを作成し、サーバレスクラスタをアタッチします。
その上で、関連パッケージをインストール。
%pip install -U databricks-mcp "mcp==1.11.0" databricks-sdk
%restart_python
作成したMCPサーバのURLを指定し、ツール一覧を取得してみます。
from databricks.sdk import WorkspaceClient
from databricks_mcp import DatabricksOAuthClientProvider
from mcp.client.streamable_http import streamablehttp_client as connect
from mcp import ClientSession
client = WorkspaceClient()
async def main():
# MCPサーバのURL。/mcp/のパスを最後につけること
app_url = "https://mcp-custom-server-bundles-xxxxxxxxxx.databricksapps.com/mcp/"
async with connect(app_url, auth=DatabricksOAuthClientProvider(client)) as (
read_stream,
write_stream,
_,
):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
tools = await session.list_tools()
print(
f"Discovered tools {[t.name for t in tools.tools]} "
f"from MCP server {app_url}"
)
await main()
実行すると、httpx.HTTPStatusError: Client error '401 Unauthorized'
というエラーが返ってきます。ここが今回の個人的ハマりポイントでした。
エラー内容の通り、MCPサーバへのリクエストに必要な認可情報が含まれていないことが原因です。
ただ、認可情報はWorkspaceClient()
で内包管理されているはずで、ノートブック上の実行は実行ユーザの認可情報が使われるはず・・・。
というわけでいろいろ試した結果、Databricks Appsへの認可についてはOAuth方式である必要がありそうでした。(全ての認証認可方式を試したわけでは無いのですが、PAT方式もダメで、U2M方式およびM2M方式のOAuthで成功しました)
いくつかの実験結果でそう判断しただけなので、ここは誤っているかもしれません。
詳しい方、教えてください。
ノートブックでDatabricks Appsへリクエストを実行する際は、例えばM2M方式のOAuthだとサービスプリンシパルを準備した上で以下のようにCLIENT SECRET等を環境変数に設定する必要があります。
import os
os.environ["DATABRICKS_HOST"] = "https://xxxxxxx.databricks.com"
os.environ["DATABRICKS_CLIENT_ID"] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
os.environ["DATABRICKS_CLIENT_SECRET"] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
*M2M方式の接続については以下の公式ドキュメントを参照ください。
適切に環境変数を設定すれば、上記コードの実行で以下のようにツール一覧を取得できます。
Discovered tools ['convert_to_markdown'] from MCP server https:....
応用すれば、前記事のようにエージェント処理に組み込んで、アプリから利用できます。
まとめ
Databricks Appsの機能を使ってMCPサーバを作成しました。
今回のような変換処理やブラウザ・コンピュート操作といったユースケースにはカスタムMCPサーバを立ててツール化する方が制約少なく安全に利用できると思います。
希望を言えば、こういった使い方だとモデルサービングのようなAuto Scale(Zero Scale)もあればいいなと思いました。数秒でスピンアップするようなサーバレス環境が実現できるとさらにコスト最適が進むのですが。
Appsが東京リージョンで利用可能になったことで、いろいろ実行できることが広がりそうです。
もっといろんなユースケースを探って実装してみたいと思います。