Serverless Operations, inc

>_cd /blog/id_b-rab5q77fd

title

Remote MCP (HTTP Streaming) Server のサンプルを VSCode + GitHub Copilot でテストする

MCPにおいて、クライアントとサーバ間の通信はJSON-RPCを用いていますが、その通信におけるトランスポートとしては以下3つをサポートしています。

  • STDIO
  • Streamable HTTP
  • SSE(Server Side Events)

通常いろいろなところから配布されているMCP Serverを試す場合、STDIO方式、つまりjsやpyファイルをそのままconfigファイルから呼び出しMCP Clientが起動時にMCP Serverを起動させる形態をとることが主流です。

{
  "mcpServers": {
    "openai": {
      "command": "node",
      "args": [
        "C:\\Users\\h.kameda\\postmanopenaimcp\\mcpServer.js"
      ],
      "env": {
        "OPENAI_API_KEY": "<api key>"
      }
    }
  }
}

この方式は配布がしやすくテストも容易ですが、MCP Serverを開発して配布する側に立つと少し困ったことがあります。それは利用しているユーザーがバージョンアップを行ってくれるかどうかが保証でいきない、という点です。せっかく中身のアルゴリズムを進化させメンテナンスしていても、ユーザーが古いものを使い続けてしまえば無駄になってしまいますし、何より商用ベースでの配布を考えれば、万が一、そこに脆弱性が混入した際にも責任の所在があいまいになりがちです。

Remote MCP と Streamable HTTP

このため最近Remote MCPというキーワードをよく耳にするようになってきました。MCP Server をStreamable HTTP 形式でサービスとして提供しておけば、提供者がその中身をメンテナンスすることが可能となり、すべてのユーザーに対して画一的なユーザー体験を提供することが可能になります。

まだStreamable HTTPトランスポートのMCP Serverに対応しているMCP Clientはそこまで多くなく、技術的な課題もあるため少しテストが大変です。この記事では最も簡単なテスト環境を構築することを目的としています。

Remote MCP と OAuth

HTTP経由でMCP Serverを読み込むのであれば、そのエンドポイントはインターネット上に露出することとなるため、認証認可の実装が重要になってきます。そうしなければ皆が等しく同じデータにアクセスできることとなってしまいます。例えばGmailの内容をサマリーしてくれるRemote MCP Serverを想像するとその課題がわかります。

MCP ServerではオプションとしてClientとServerの通信にOAuthを用いることが可能となっています。

https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#2-4-dynamic-client-registration

オプションとはなっていますが、例えばClaudeDesktopではなく、ブラウザから利用する形態のClaudeではRemote MCP Serverを組み込む場合必須となっているため、少しテスト環境の構築が困難です。

VSCode Clineでも以下の通りRemote MCP Serverの組み込みが可能となっているようですが、OAuthを実装しないと動作市内のかうまく起動しませんでした。ただしエラーメッセージが認証とは異なる設定エラーと出ていたので以下のIssueが関連していそうです。

https://github.com/cline/cline/issues/3315

VSCode + GitHub Copilot

こちらの組み合わせがOAuth未実装のRemote MCP Serverの起動に対応していました。

さっそくやってみる

環境構築

https://code.visualstudio.com/docs/copilot/chat/mcp-servers

に従ってセットアップを行います。英語なのでハードル高そうですが、普段VSCode使っている方であれば、普通にGitHub Copilotプラグラインをインストールするだけです。

ExtensionからGitHubと検索したら出てきます。

インストールが完了したらGitHubにプラグインからログインすれば完了です。

Remote MCP Server の起動

以下のファイルを作成します。名前はmcp-server.jsとします。

const http = require('http');
const url = require('url');

// MCPメッセージのIDカウンター
let messageId = 1;

// レスポンスヘルパー関数
function sendJsonResponse(res, data, statusCode = 200) {
  res.writeHead(statusCode, {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type'
  });
  res.end(JSON.stringify(data));
}

// MCPプロトコルメッセージハンドラー
function handleMCPMessage(message) {
  const { method, params } = message;

  switch (method) {
    case 'initialize':
      return {
        jsonrpc: '2.0',
        id: message.id,
        result: {
          protocolVersion: '2024-11-05',
          capabilities: {
            tools: {},
            prompts: {},
            resources: {}
          },
          serverInfo: {
            name: 'hello-world-mcp-server',
            version: '1.0.0'
          }
        }
      };

    case 'notifications/initialized':
      // initialized通知には応答不要
      return null;

    case 'tools/list':
      return {
        jsonrpc: '2.0',
        id: message.id,
        result: {
          tools: [
            {
              name: 'hello_world',
              description: 'Returns a simple hello world message',
              inputSchema: {
                type: 'object',
                properties: {
                  name: {
                    type: 'string',
                    description: 'Name to greet (optional)'
                  }
                }
              }
            }
          ]
        }
      };

    case 'tools/call':
      const { name: toolName, arguments: args } = params;
      
      if (toolName === 'hello_world') {
        const name = args?.name || 'World';
        return {
          jsonrpc: '2.0',
          id: message.id,
          result: {
            content: [
              {
                type: 'text',
                text: `Hello, ${name}! This is a simple MCP server response.`
              }
            ]
          }
        };
      }
      
      // 未知のツール
      return {
        jsonrpc: '2.0',
        id: message.id,
        error: {
          code: -32601,
          message: `Unknown tool: ${toolName}`
        }
      };

    default:
      return {
        jsonrpc: '2.0',
        id: message.id,
        error: {
          code: -32601,
          message: `Method not found: ${method}`
        }
      };
  }
}

// HTTPサーバー作成
const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(req.url, true);
  
  // CORS preflight
  if (req.method === 'OPTIONS') {
    sendJsonResponse(res, {});
    return;
  }

  // GET - サーバー情報
  if (req.method === 'GET' && parsedUrl.pathname === '/') {
    sendJsonResponse(res, {
      name: 'Hello World MCP Server',
      version: '1.0.0',
      description: 'A simple MCP server that responds with hello world messages',
      endpoints: {
        mcp: '/mcp'
      }
    });
    return;
  }

  // POST - MCPメッセージ処理
  if (req.method === 'POST' && parsedUrl.pathname === '/mcp') {
    let body = '';
    
    req.on('data', chunk => {
      body += chunk.toString();
    });
    
    req.on('end', () => {
      try {
        const message = JSON.parse(body);
        console.log('Received MCP message:', JSON.stringify(message, null, 2));
        
        const response = handleMCPMessage(message);
        
        if (response) {
          console.log('Sending response:', JSON.stringify(response, null, 2));
          sendJsonResponse(res, response);
        } else {
          // notifications/initializedなど、応答不要の場合
          res.writeHead(204);
          res.end();
        }
      } catch (error) {
        console.error('Error processing message:', error);
        sendJsonResponse(res, {
          jsonrpc: '2.0',
          id: null,
          error: {
            code: -32700,
            message: 'Parse error'
          }
        }, 400);
      }
    });
    return;
  }

  // 404
  sendJsonResponse(res, { error: 'Not found' }, 404);
});

const PORT = process.env.PORT || 3000;

server.listen(PORT, () => {
  console.log(`MCP HTTP Server running on port ${PORT}`);
  console.log(`Server info: http://localhost:${PORT}/`);
  console.log(`MCP endpoint: http://localhost:${PORT}/mcp`);
});

// グレースフルシャットダウン
process.on('SIGINT', () => {
  console.log('\nShutting down server...');
  server.close(() => {
    console.log('Server closed');
    process.exit(0);
  });
});

node mcp-server.js で起動します。

MCP HTTP Server running on port 3000
Server info: http://localhost:3000/
MCP endpoint: http://localhost:3000/mcp

と表示されれば起動完了です。

GitHub Copilotへの組み込み

作業用ディレクトリに.vscode/mcp.jsonというファイルを作成します。中身は空欄で問題ありません。そうすると自動で認識されAdd Serverというアイコンが出てきます。

HTTPを選択します。

http://localhost:3000/mcp と入力すると自動ServerIDが生成されconfigファイルが出来上がります。

{
    "servers": {
        "my-mcp-server-18a109c9": {
            "url": "http://localhost:3000/mcp"
        }
    }
}

テスト

いよいよテストです。CopilotのモードをAgentに切り替えます。

プロンプトでhello world mcp と入力すると以下の通りレスポンスがRemote MCP Serverから戻ります。

node側のコンソールでは以下の内容が出力されています。

Received MCP message: {
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "hello_world",
    "arguments": {
      "name": "world"
    },
    "_meta": {
      "progressToken": "75e01fc5-5d1d-4f86-bf09-17af99154fac"
    }
  }
}
Sending response: {
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Hello, world! This is a simple MCP server response."        
      }
    ]
  }
}

mcp-server.jsの少しだけ解説

function handleMCPMessage(message) {
  const { method, params } = message;

  switch (method) {
    // ... 各メソッドの処理
  }
}

この部分がMCP Serverの心臓部分です。MCPのプロトコル定義に従って4つのメソッドが定義されています。

  1. initialize

    クライアントとのコネクション初期化を担っておりプロトコルバージョンなどが指定されます。

  2. notifications/initialized

    初期化完了の通知を受信します

  3. tools/list

    MCP Clientに利用可能なツールの一覧を返します

  4. tools/call

    プロンプトに応じで実行される部分です。このサンプルでは単純にHello, ${name}! This is a simple MCP server response.のみを戻します。

1.と2.はMCP Client起動時の初期化フェーズで行われ、3.と4.は適宜プロンプトに応じて呼び出されます。

Written by
編集部

亀田 治伸

Kameda Harunobu

  • Facebook->
  • X->
  • GitHub->

Share

Facebook->X->
Back
to list
<-