
Model Context Protocol (MCP) は、Claude Desktop、VS Code、カスタムチャット UI などの AI クライアントが外部データやサービスにアクセスする方法を定義するオープンスタンダードです。これにより、大規模言語モデルがツールの発見・呼び出し、リソースの読み取り、プロンプトテンプレートの使用を、明確に定義されたトランスポートに依存しないプロトコルを通じておこなえます。
この記事では、JavaScript、Python、.NET を用いた実際の参照実装で使用されているパターンを参考に、Autodesk Platform Services (APS) と統合する MCP サーバーを構築するためのベストプラクティスを解説します。
セットアップ
公式 MCP SDK
公式 MCP SDK は、すべての言語において推奨される出発点です。:
| 言語 | パッケージ | 例 |
|---|---|---|
| JavaScript/TypeScript | @modelcontextprotocol/sdk | aps-mcp-app-example |
| C# / .NET | ModelContextProtocol | aps-aecdm-mcp-dotnet |
| Python | fastmcp(公式 SDK をベースに構築)) | aps-mcp-server-python |
公式 SDK は、メッセージフレーミング、トランスポート抽象化、ツール/リソース登録フック、セッション管理といったコアプロトコル実装を提供します。プロトコルの基盤となる処理は SDK が担うため、開発者はドメインロジックに集中できます。
JavaScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";const server = new McpServer({ name: "My APS MCP Server", version: "1.0.0",});
.NET
var builder = Host.CreateEmptyApplicationBuilder(settings: null);builder.Services.AddMcpServer() .WithStdioServerTransport() .WithToolsFromAssembly();var app = builder.Build();await app.RunAsync();
Python
Python の場合、FastMCP が公式 SDK 上に高レベルの便利なレイヤーを提供します。これにより、定型コードが大幅に削減されます。デコレータベースの APIを 使用すると、わずか数行でツール、リソース、プロンプトを登録できます。:
from fastmcp import FastMCPmcp = FastMCP( "My APS MCP Server",)
FastMCP は、サーバーの初期化、型ヒントからのツール スキーマ生成、トランスポートの設定などを処理するため、迅速な反復開発が求められる場合に有力な選択肢となります。
ステートレス サーバー vs. ステートフル サーバー
MCP サーバーはステートレス(stateless)、またはステートフル(stateful)のいずれかであり、その選択はアーキテクチャの設計方法に影響を与えます。
ステートレス
ステートレスサーバーは、受信リクエストごとに新しい McpServer インスタンスを作成します。リクエスト間のセッションの継続性はなく、共有メモリも蓄積されたコンテキストもありません。これは最もシンプルなモデルであり、次のような場合に適しています。:
- 各リクエストが独立しているリモート環境におけるマルチテナント デプロイ
- ユーザーセッションを追跡する必要のないサーバー
- ロードバランサーの背後で水平方向に拡張可能なサービス
aps-mcp-app-example (JavaScript)はこのパターンを示しています。受信したHTTP リクエストごとに新しい McpServer インスタンスが作成され、トランスポートに接続され、リクエストが処理され、その後すべてが破棄されます。:
app.all("/mcp", async (req, res) => { const server = createMcpServer(options); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, // No sessions }); res.on("close", () => { transport.close(); server.close(); }); await server.connect(transport); await transport.handleRequest(req, res, req.body);});
sessionIdGenerator: undefinedを設定すると、サーバーがステートレスであることを明示的に示します。
ステートフル
ステートフル サーバーは、リクエスト間でセッション状態を維持します。これは、次のような場合に必要となります。 :
- ユーザーごとの認証トークン(例:3-legged OAuth)を追跡する必要がある。
- サーバーは会話を通じてコンテキストを蓄積する。
- 単一のクライアントに接続されたローカル STDIO サーバーを実行する。
aps-mcp-server-python 3-legged サンプルは、ステートフルなセッション管理を示しています。このサンプルは、MCP セッションコンテキスト (ctx) を使用して、ツール呼び出し間で OAuth トークンを保存および取得します。 :
@mcp.tool()async def list_hubs(ctx: Context) -> list[dict] | dict: token = await _get_valid_token(ctx) if not token: return { "auth_required": True, "auth_url": _build_auth_url(ctx.session_id), "message": "Open auth_url in a browser to authenticate, then call list_hubs again." } return await _list_hubs(token)
aps-aecdm-mcp-dotnet (.NET) のサンプルもステートフルです。トークンをグローバル ステートに保存し、WebSocket を介してローカルで起動された Viewer と通信します。
トランスポート
MCP は、トランスポート層をプロトコル自体とは別に定義します。主なトランスポート方式は、STDIO と Streamable HTTP の2つです。
STDIO
STDIO トランスポートは、標準入出力ストリームを介して通信します。MCP クライアントは、サーバーを子プロセスとして起動し、stdin と stdout を介して JSON-RPC メッセージを送受信します。
// .NET example: STDIO transportbuilder.Services.AddMcpServer() .WithStdioServerTransport() .WithToolsFromAssembly();
STDIO は次のような用途に最適です。:
- ローカル開発およびテスト – ネットワーク接続不要
- デスクトップ クライアント – Claude Desktop などのデスクトップ クライアントがサーバーをサブプロセスとして起動
- シングル ユーザー シナリオ – サーバープロセス毎に 1 クライアント
Claude Desktop の設定は通常、次のようになります。:
{ "mcpServers": { "my-server": { "command": "dotnet", "args": ["run", "--project", "/path/to/mcp-server.csproj"] } }}
Streamable HTTP
その他のすべての利用例、特にリモート デプロイ、マルチユーザー サーバー、Web ベースのクライアントでは、Streamable HTTP の利用が推奨されます。Streamable HTTP は、MCP サーバーを HTTP エンドポイントとして実行し、通常は /mcp パスで動作します。
// JavaScript example: Streamable HTTP with Expressimport { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined,});await server.connect(transport);await transport.handleRequest(req, res, req.body);
# Python (FastMCP): Streamable HTTPmcp.run(transport="streamable-http", host="0.0.0.0", port=5000)# Or via CLI:# uv run fastmcp run server.py --transport streamable-http --port 5000
Streamable HTTP の利点:
- ネットワーク境界を越えて動作(異なるマシン上のクライアントとサーバーに対応)。
- 複数クライアントの同時接続をサポート
- 標準的な Web インフラストラクチャ(ロードバランサー、リバースプロキシ、CORS)と互換性があり
- HTTP ヘッダーによるセッション管理が可能
認証
APS と連携する MCP サーバーの認証には、2 つの異なるレイヤーが関わっています。それぞれのレイヤーをいつ、どのように使用するかを理解することが重要です。
レイヤー1:MCP クライアントと MCP サーバー間の認証
このレイヤーは、MCP サーバーに接続できるユーザーを制御します。STDIO ベースのローカルサーバーの場合、これは暗黙的です。プロセスを実行しているローカルユーザーのみが接続できます。HTTP ベースのサーバーの場合は、次の実装が必要になる場合があります。:
- 受信リクエストにおける API キーまたはトークンの検証
- OAuth ベースのクライアント認証
レイヤー2:MCP サーバーと APS 間の認証
ここでは、サーバーが Autodesk Platform Services で認証を行い、アプリまたはユーザーに代わって APS API を呼び出します。APS は主に 3 つの認証方式をサポートしており、それぞれ異なるシナリオに適しています。:
2-Legged OAuth
最もシンプルな方法です。サーバーは自身の Client ID と Client Secret を使用して、アプリ(識別子)に紐づいたトークンを取得します。ユーザー コンテキストは一切関与しません。
async with httpx.AsyncClient() as client: response = await client.post( APS_TOKEN_URL, data={ "grant_type": "client_credentials", "scope": "bucket:read data:read", }, auth=(APS_CLIENT_ID, APS_CLIENT_SECRET), ) data = response.json()
使用場面:アプリが所有するリソース(ユーザーコンテキストは不要)。例えば、アプリンが所有する OSS Bucket へのアクセスなど。
Secure Service Accounts
SSA(Secure Service Accounts) は、ユーザーのサインイン操作なしに 3-legged トークンを取得できる Autodesk ID(オートデスク アカウント)であり、ユーザーコンテキスト API(Data Management API など)を必要とする自動化されたワークフローに最適です。サーバーは 署名付き JWT アサーションを作成してアクセス トークンと交換します。:
payload = { "iss": APS_CLIENT_ID, "sub": APS_SSA_ID, "aud": APS_TOKEN_URL, "exp": now + 300, "scope": ["data:read"],}assertion = jwt.encode(payload, SSA_PRIVATE_KEY, algorithm="RS256", headers={"kid": SSA_KEY_ID})
// JavaScript equivalentconst assertion = jwt.sign(payload, serviceAccountPrivateKey, { algorithm: "RS256", header: { alg: "RS256", kid: serviceAccountKeyId },});
使用場面:自動化されたサーバー間ワークフロー。サービス アカウントは、対象リソースへのアクセス権限を事前に取得しておく必要があります。
3-Legged OAuth
OAuth 2.0 の認証コードフロー全体。ユーザーは明示的にサインイン、アクセスに同意します。サーバーはコールバックを介してコードを受け取り、アクセストークンとリフレッシュトークンと交換します。
@mcp.custom_route("/callback", methods=["GET"])async def oauth_callback(request: Request) -> HTMLResponse: code = request.query_params.get("code") session_id = request.query_params.get("state") tokens = await _exchange_code(code) _pending_tokens[session_id] = { "access_token": tokens["access_token"], "refresh_token": tokens.get("refresh_token"), "expires_at": time.time() + tokens.get("expires_in", 3600), } return HTMLResponse("Authentication successful! You can close this window.")
使用場面:明示的な同意を得た上で実際のユーザーに代わって操作する場合。ユーザー毎のトークンを追跡するには、セッションはステートフルである必要があります。
どれを使うべきか
| アプローチ | ユーザー操作 | トークン タイプ | 最適な用途 |
|---|---|---|---|
| 2-Legged (Client Credentials) | なし | アプリケーション トークン | アプリ所有のリソース、ユーザーコンテキストなし |
| Secure Service Accounts | なし | ユーザーコンテキスト トークン | ユーザーコンテキスト API を必要とする自動化ワークフロー |
| 3-Legged (Authorization Code) | サインイン操作 | ユーザー トークン | 同意を得た実際のユーザーに代わってアクション |
ベスト プラクティス
- トークンを常にキャッシュし、有効期限が切れるまで再利用してください。3 つのリファレンス実装はすべて、有効期限チェック付きのインメモリキャッシュを使用しています。:
if _token_cache["access_token"] and time.time() < _token_cache["expires_at"] - 60: return _token_cache["access_token"]
- トークンは積極的に更新する。API 呼び出しを行う前に、有効期限が切れていないか(例えば 60 秒の猶予期間を設けて)確認してください。
- 認証情報はコードから分離する。Client ID、Client Secret、SSA キーは環境変数(.envファイル)を使用してください。
- 要求するスコープを最小限に抑える。ツールが実際に必要とする OAuthス コープ(例:data:read、bucket:read)のみを要求してください。
ツール
MCP ツールとは何か?
ツールとは、LLM(Large Language Model、大規模言語モデル)がアクションを実行したりデータを取得したりするために呼び出すことができる機能です。これは、MCP サーバーが機能を公開する主要な方法です。各ツールには次の機能があります。:
- ツールの名前と説明(LLM が呼び出しタイミングを決定するために使用)
- パラメータを定義する入力スキーマ
- ロジックを実行するコールバック
- オプションの注釈(例:
readOnlyHintはツールが状態を変更しないことを示す)
ツールの実装
Python (FastMCP) — デコレータベース
FastMCP は、関数シグネチャとドキュメント文字列からツールスキーマを推論します。:
@mcp.tool()async def list_buckets() -> list[dict]: """List all OSS buckets owned by the configured APS application.""" token = await _get_access_token() return await _list_oss_buckets(token)@mcp.tool()async def list_objects(bucket_key: str) -> list[dict]: """List objects stored in a specific OSS bucket. Args: bucket_key: The unique key identifying the OSS bucket. """ token = await _get_access_token() return await _list_oss_objects(token, bucket_key)
JavaScript — Zod スキーマを使用したファクトリー パターン
aps-mcp-app-example はファクトリーパターンを使用しており、各ツールはファクトリー関数をエクスポートするモジュールです。入力スキーマは Zod を使用して定義されています。:
import z from "zod";export const getProjectContentsToolFactory = ({}) => ({ name: "get-project-contents", config: { title: "Get project contents", description: "Retrieves top-level folders in a project or contents of a specified folder.", inputSchema: { accountId: z.string().nonempty().describe("The ID of the account."), projectId: z.string().nonempty().describe("The ID of the project."), folderId: z.string().optional().describe("The ID of the folder."), }, annotations: { readOnlyHint: true }, }, callback: async ({ accountId, projectId, folderId }) => { // ... tool implementation },});
ツールはサーバー設定時に動的に登録されます。:
for (const toolFactory of Object.values(tools)) { const { name, config, callback } = toolFactory(options); registerAppTool(server, name, config, callback);}
.NET — 属性ベースの登録
.NET SDK は属性と自動検出を使用します。静的クラスに [McpServerToolType] を、各ツールメソッドに [McpServerTool] を注釈として付加します。:
[McpServerToolType]public static class AECDMTools{ [McpServerTool, Description("Get the ACC hubs from the user")] public static async Task<string> GetHubs() { // ... tool implementation } [McpServerTool, Description("Get the ACC projects from one hub")] public static async Task<string> GetProjects( [Description("Hub id to query the projects from")] string hubId) { // ... tool implementation }}
サーバーは WithToolsFromAssembly() を使用して、アセンブリ内のすべてのツールを自動的に検出します。
content vs. structuredContent の返却
MCP ツールは、content(人間が可読なテキスト、または LLM 用のイメージ)とstructuredContent(プログラム処理用のマシンが機械可読 JSON)の両方を返すことができます。aps-mcp-app-example は、一貫して両方を使用しています。:
callback: async ({ projectId, designId, region }) => { // ... fetch data return { structuredContent: props.data, // Full JSON for MCP Apps / UI rendering content: [{ type: "text", text: `Found properties containing ${props.data.collection.length} elements`, }], };}
content: LLM が読み込み、推論するコンテンツ ブロック(テキスト、画像など)の配列。簡潔かつ分かりやすく記述してください。structuredContent: プログラムから利用できる JSONオ ブジェクト。例えば、3D Viewerやデータ テーブルを表示する MCP アプリの UI などで使用出来ます。
ツールがリッチデータを UI コンポーネントに渡す必要がある場合、またはクライアントがレスポンスをプログラムで処理する必要がある場合は、structuredContentを使用してください。レスポンスが主に LLM によって解釈され、ユーザーに伝達される場合は、contentを使用してください。
追加リソース
- 参考実装例:
- aps-mcp-app-example – HTTP、SSA 認証、MCP アプリのサポート、および APS Viewer 統合を持つ JavaScript MCP サーバー
- aps-aecdm-mcp-dotnet – STDIO トランスポート、PKCE 認証、および AEC Data Model API を利用する .NET MCP サーバー
- aps-mcp-server-python – 2-legged OAuth、SSA、および 3-legged OAuth パターンを示す Python MCP サーバー
- 仕様とガイド:
- MCP Specification(MCP 仕様)、MCP公式ドキュメント 日本語版
- MCP Auth Patterns (APS wiki) – APS MCP サーバーの認証方式の詳細な比較
- Autodesk Platform Services ドキュメント
- Secure Service Accounts (SSA) ガイド
※ 本記事は Building Custom MCP Servers with Autodesk Platform Services | Autodesk Platform Services から転写・意訳・補足したものです。

You must be logged in to post a comment.