Function Calling vs. MCP──LLMを“真のエージェント”化する仕組み比較

に公開

目次

  1. はじめに
  2. この記事のターゲットとゴール
  3. Function Callingの概要
  4. Model Context Protocol(MCP)の概要
  5. 両者の比較ポイント
  6. 今すぐ試せる実装サンプル
  7. まとめ:次世代AIエージェント開発の選択肢
  8. 採用情報 ―― “AIを部下にする”エンジニア募集

1. はじめに

最近のLLM活用では「単にプロンプトを書く」だけでなく、AI自身に外部ツール呼び出しやデータ取得を任せる流れが加速しています。本稿では、OpenAIのFunction CallingとAnthropic主導のModel Context Protocol(MCP)という二大アプローチを整理し、エンジニア観点でどちらを選ぶべきかを解説します。


2. この記事のターゲットとゴール

誰に向けた記事なのか

  • AIアプリケーション開発を手掛けるソフトウェアエンジニア
  • LLMを使ったエージェント・ツール連携に興味があるSRE/インフラエンジニア

この記事を読むとなにが理解できるのか

  • Function CallingとMCPの仕組み・アーキテクチャ
  • それぞれのメリット・制約
  • 手を動かせるPython/TypeScriptのサンプル実装
  • プロダクション導入の判断基準

3. Function Callingの概要

OpenAIが2023年6月に導入したFunction Callingは、LLMが「関数(tools)を呼び出せる」仕組みです。

  1. アプリケーション側で関数定義(get_current_weatherなど)を用意
  2. LLMにプロンプトと関数仕様を渡す
  3. LLMが必要と判断したらJSONで「関数名+引数」を返却
  4. アプリ側が関数を実行し、結果をLLMに再投入
  5. LLMが最終回答を生成
  • 利点: すぐに既存コードやサードパーティAPIと接続できる
  • 制約: 関数実行はクライアント(あなたのアプリ)が担うため、各アプリで実装が必要

4. Model Context Protocol(MCP)の概要

Anthropic提唱のMCPは「Client(LLM)⇆Server」のクライアント・サーバープロトコル。ツール呼び出しロジックをServer側に集約し、クライアント実装をシンプルに保ちます。

  • Host: ユーザーとLLMの対話を管理
  • Client (LLM): MCP Serverへリクエスト
  • Server: ツール・データ取得の実装を一元化

Transport: JSON-RPC over StdIO or HTTP+SSE
メッセージ型: request / response / notification


4. 今すぐ試せる実装サンプル

Function Calling (Python + LangChain)

from typing import  Any
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain_core.prompts import ChatPromptTemplate
from langchain.agents import create_tool_calling_agent, AgentExecutor
import requests
from dotenv import load_dotenv
import os

load_dotenv()

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

@tool
def get_current_weather(location: str) -> dict[str, Any]:
    """
    指定された場所の現在の天気を取得する
    注意: このAPIはアメリカの場所のみ対応している

    Args:
        location (str): 天気を知りたい場所の緯度と経度(例: "39.7456,-97.0892")

    Returns:
        dict[str, Any]: 天気情報を含む辞書なのだ。以下のキーを含:
            - 場所 (str): 最も近い都市の名前
            - 天気 (str): 現在の天気の簡単な説明
            - 気温 (str): 摂氏での現在の気温(例: "20.5°C")
            - 風速 (str): 現在の風速(例: "10 mph")
            - 詳細 (str): 天気の詳細な説明

        エラーの場合は以下の形式なのだ:
            - error (str): エラーメッセージ
    """
    # Weather.gov APIのエンドポイントを設定する
    base_url = "https://api.weather.gov/points/"
    
    # ユーザーエージェントを設定する(Weather.gov APIの要件)
    headers = {
        "User-Agent": "WeatherBot/1.0 (weather-bot@example.com)",
        "Accept": "application/json"
    }
    
    try:
        # まず座標から気象観測地点の情報を取得する
        response = requests.get(f"{base_url}{location}", headers=headers)
        response.raise_for_status()
        points_data = response.json()
        
        # 気象観測地点から現在の天気を取得する
        forecast_url = points_data["properties"]["forecast"]
        response = requests.get(forecast_url, headers=headers)
        response.raise_for_status()
        weather_data = response.json()
        
        # 現在の天気情報を取得する
        current_period = weather_data["properties"]["periods"][0]
        
        return {
            "場所": points_data["properties"]["relativeLocation"]["properties"]["city"],
            "天気": current_period["shortForecast"],
            "気温": f"{fahrenheit_to_celsius(current_period['temperature'])}°C",
            "風速": current_period["windSpeed"],
            "詳細": current_period["detailedForecast"]
        }
        
    except requests.exceptions.RequestException as e:
        return {"error": f"天気情報の取得に失敗しました: {str(e)}"}

def fahrenheit_to_celsius(fahrenheit: float) -> float:
    """華氏を摂氏に変換する"""
    return round((fahrenheit - 32) * 5/9, 1)

tools = [get_current_weather]

prompt = ChatPromptTemplate.from_messages([
    ("system", "you're a helpful assistant"),
    ("user", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

llm = ChatOpenAI(model="gpt-4o")

agent = create_tool_calling_agent(llm, tools, prompt)

agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

if __name__ == "__main__":
    result = agent_executor.invoke(
        {"input": "ロサンゼルスの天気を教えて"}
    )
    print("結果:", result)

MCP (TypeScript or Python)

import httpx
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"

async def make_nws_request(url: str) -> dict[str, str] | None:
    headers = {
        "User-Agent": USER_AGENT,
        "Accept": "application/geo+json",
    }
    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(url, headers=headers, timeout=30.0)
            response.raise_for_status()
            return response.json()
        except Exception:
            return None

@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> dict[str, str]:
    """アメリカの場所の天気予報を取得してください

    Args:
        latitude: 場所の緯度
        longitude: 場所の経度

    Returns:
        dict[str, Any]: 天気情報を含む辞書です。以下のキーを含みます:
            - 場所 (str): 最も近い都市の名前
            - 天気 (str): 現在の天気の簡単な説明
            - 気温 (str): 摂氏での現在の気温(例: "20.5°C")
            - 風速 (str): 現在の風速(例: "10 mph")
            - 詳細 (str): 天気の詳細な説明
    """
    # First get the forecast grid endpoint
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)

    if not points_data:
        return {"error": "天気情報の取得に失敗しました"}

    # Get the forecast URL from the points response
    forecast_url = points_data["properties"]["forecast"]
    forecast_data = await make_nws_request(forecast_url)

    if not forecast_data:
        return {"error": "詳細な天気情報の取得に失敗しました"}

    # Get current period data
    current_period = forecast_data["properties"]["periods"][0]
    
    def fahrenheit_to_celsius(fahrenheit: float) -> float:
        """華氏を摂氏に変換しました"""
        return round((fahrenheit - 32) * 5/9, 1)

    # Format the response to match function_calling.py
    return {
        "場所": points_data["properties"]["relativeLocation"]["properties"]["city"],
        "天気": current_period["shortForecast"],
        "気温": f"{fahrenheit_to_celsius(current_period['temperature'])}°C",
        "風速": current_period["windSpeed"],
        "詳細": current_period["detailedForecast"]
    }

@mcp.prompt()
async def weather_prompt(area: str) -> dict[str, str]:
    """
    指定されたアメリカの都市の天気情報からお出かけ情報を分析するプロンプト

    Args:
        area (str): 天気を知りたい場所の名前
    
    Returns:
        dict[str, str]: プロンプトを含む辞書
    """
    prompt = f"""アメリカの都市{area}についてmcp tool get_forecastを実行して天気情報を取得してください。
その後、その天気情報からお出かけ情報を分析してください。以下の情報を提供してください。
- 服装のアドバイス
- 外出時の注意点
- おすすめの活動
- おすすめの観光地"""

    return prompt

if __name__ == "__main__":
    # Initialize and run the server
    mcp.run(transport='stdio')

サーバー起動後、echo '{"jsonrpc":"2.0","method":"initialize",...}' | python mcp_app.py などで対話可能です。


7. まとめ:次世代AIエージェント開発の選択肢

Function Callingではエージェント側が直接 tools(関数)を定義し、それぞれの実装詳細を把握した上で呼び出す必要がありました。

これに対して、MCPではLLM(Client)がMCP Serverに対してJSON-RPCでデータ要求を行い、Serverが外部ツールとの連携を一手に引き受けます。

結果として、エージェントはツールの具体的な実装を意識せずとも、その抽象化されたインターフェースを通じて機能を利用できるようになり、共通の連携レイヤーとしてプロジェクト全体の保守性と再利用性が大幅に向上します。


8. 採用情報 ―― “AIを部下にする”エンジニア募集

Malme では「土木×IT×生成AI」でインフラ産業を進化させる仲間を大募集中!
Zenn読者のあなたも、Function CallingやMCPを駆使する次世代エンジニアの一員に。

ポジション 年収レンジ リモート 求める人材
AIエンジニア 700–1,100万 フルリモート LLM・画像認識・クラウドAI基盤を自走し「Devinを部下に」開発したい方
Tech Lead / BE・FEエンジニア 500–1,000万 フルリモート Go/Python/TSでスケーラブルなWebプロダクトをガンガン作りたい方
インフラエンジニア 450–900万 フルリモート AWS/GCP/IaCで土木DX基盤の信頼性を高めたい方
土木ブリッジエンジニア(BIM/CIM) 400–1,000万 フルリモート 3Dモデルと現場知見で開発チームと現場をつなぎたい方
PdM/PM 500–1,000万 フルリモート Dev×Biz×現場ハブとして建設DXをリードしたい方

応募方法:
HERP Careersで職種を選んでエントリー → カジュアル面談 でまずは話を聞いてください!

https://herp.careers/v1/malme/Wgtqw4uWAF-a

https://herp.careers/v1/malme/1lUV21bzuS_7

https://herp.careers/v1/malme/ImSG_luBe-80

https://herp.careers/v1/malme/PMv1aWEnXB3u

https://herp.careers/v1/malme/iI5m7wL7B-Ql

AIツールを“チームメンバー”に変える開発文化で、一緒にインフラDXを加速させましょう!
HERP Careers – Malme求人一覧

Discussion