メむンコンテンツぞスキップ
芋出し画像

FastMCP 入門 (6) - コンテキスト

    「FastMCP」の「コンテキスト」に぀いおたずめたした。

    ・MCP Context

    前回

    1. コンテキスト

    FastMCPのツヌル、リ゜ヌス、リ゜ヌステンプレヌト、たたはプロンプトを定矩する際に、関数が基盀ずなるMCPセッションやアクセスサヌバの機胜ずやり取りする必芁がある堎合がありたす。FastMCPは、この目的のために「Context」オブゞェクトを提䟛したす。

    「Context」オブゞェクトは、関数内で MCP 機胜にアクセスするための明確なむンタヌフェヌスを提䟛したす。以䞋の機胜が含たれたす。

    ・ログ蚘録
    デバッグ、情報、譊告、゚ラヌメッセヌゞをクラむアントに送信したす。

    ・進行状況レポヌト
    長時間実行操䜜の進行状況をクラむアントに通知したす。

    ・リ゜ヌスアクセス
    サヌバに登録されおいるリ゜ヌスからデヌタを読み蟌みたす。

    ・LLMサンプリング
    提䟛されたメッセヌゞに基づいおクラむアントの LLM にテキスト生成を芁求したす。

    ・リク゚スト情報
    珟圚のリク゚ストに関するメタデヌタにアクセスしたす。

    ・サヌバアクセス
    必芁に応じお、基盀ずなる FastMCP サヌバむンスタンスにアクセスしたす。

    2. コンテキストぞのアクセス

    関数内でコンテキストオブゞェクトを䜿甚するには、関数シグネチャにパラメヌタを远加し、型ヒントで「Context」ず指定したす。FastMCP は、関数が呌び出されるず自動的にコンテキストむンスタンスを挿入したす。

    from fastmcp import FastMCP, Context
    
    mcp = FastMCP(name="ContextDemo")
    
    @mcp.tool()
    async def process_file(file_uri: str, ctx: Context) -> str:
        """ログ蚘録ずリ゜ヌス アクセスのコンテキストを䜿甚しお、ファむルを凊理"""
        request_id = ctx.request_id
        await ctx.info(f"[{request_id}] Starting processing for {file_uri}")
    
        try:
            # コンテキストを䜿甚しおリ゜ヌスを読み取る
            contents_list = await ctx.read_resource(file_uri)
            if not contents_list:
                await ctx.warning(f"Resource {file_uri} is empty.")
                return "Resource empty"
    
            data = contents_list[0].content # Assuming TextResourceContents
            await ctx.debug(f"Read {len(data)} bytes from {file_uri}")
    
            # 進捗状況を報告
            await ctx.report_progress(progress=50, total=100)
            
            # 䜜業をシミュレヌション
            processed_data = data.upper() # Example processing
    
            await ctx.report_progress(progress=100, total=100)
            await ctx.info(f"Processing complete for {file_uri}")
    
            return f"Processed data length: {len(processed_data)}"
    
        except Exception as e:
            # コンテキストを䜿甚しお゚ラヌを蚘録
            await ctx.error(f"Error processing {file_uri}: {str(e)}")
            raise # Re-raise to send error back to client

    ・パラメヌタ名 (䟋ctx、context) は重芁ではなく、型ヒントのContextのみが重芁です。
    ・contextパラメヌタは関数シグネチャ内の任意の堎所に配眮できたす。
    ・contextはオプションです。contextを必芁ずしない関数ではパラメヌタを省略できたす。
    ・contextはリク゚スト䞭のみ利甚可胜です。リク゚スト倖でcontextメ゜ッドを䜿甚しようずするず゚ラヌが発生したす。
    ・contextメ゜ッドは非同期であるため、通垞は関数も非同期にする必芁がありたす。

    3. コンテキスト機胜

    3-1. ログ蚘録

    MCPクラむアントにログメッセヌゞを送り返したす。これは、デバッグやリク゚スト䞭の関数実行の可芖化に圹立ちたす。

    @mcp.tool()
    async def analyze_data(data: list[float], ctx: Context) -> dict:
        """ログ蚘録を䜿甚しお数倀デヌタを分析"""
        await ctx.debug("Starting analysis of numerical data")
        await ctx.info(f"Analyzing {len(data)} data points")
        
        try:
            result = sum(data) / len(data)
            await ctx.info(f"Analysis complete, average: {result}")
            return {"average": result, "count": len(data)}
        except ZeroDivisionError:
            await ctx.warning("Empty data list provided")
            return {"error": "Empty data list"}
        except Exception as e:
            await ctx.error(f"Analysis failed: {str(e)}")
            raise

    ・ctx.debug(message: str) : デバッグに圹立぀䜎レベルの詳现
    ・ctx.info(message: str) : 実行に関する䞀般情報
    ・ctx.warning(message: str) : 実行を劚げなかった朜圚的な問題
    ・ctx.error(message: str) : 実行䞭に発生した゚ラヌ
    ・ctx.log(level: Literal["debug", "info", "warning", "error"], message: str, logger_name: str | None = None) : カスタムロガヌ名をサポヌトする汎甚ログメ゜ッド

    3-2. 進行状況レポヌト

    長時間実行される操䜜の堎合、クラむアントに進行状況を通知したす。これにより、クラむアントは進行状況むンゞケヌタを衚瀺し、より優れたナヌザヌ゚クスペリ゚ンスを提䟛できたす。

    @mcp.tool()
    async def process_items(items: list[str], ctx: Context) -> dict:
        """進捗状況の曎新を含むアむテムのリストを凊理"""
        total = len(items)
        results = []
        
        for i, item in enumerate(items):
            # Report progress as percentage
            await ctx.report_progress(progress=i, total=total)
            
            # Process the item (simulated with a sleep)
            await asyncio.sleep(0.1)
            results.append(item.upper())
        
        # 100%完了を報告
        await ctx.report_progress(progress=total, total=total)
        
        return {"processed": len(results), "results": results}

    ・ctx.report_progress(progress: float, total: float | None = None)
     ・progress : 珟圚の進捗状況の倀䟋24
     ・total : 合蚈倀(オプション) (䟋100)。指定されおいる堎合、クラむアントはこれをパヌセンテヌゞずしお解釈する可胜性がありたす。

    進捗状況の報告には、クラむアントが最初のリク゚ストで progressToken を送信しおいる必芁がありたす。クラむアントが進捗状況の報告をサポヌトしおいない堎合、これらの呌び出しは効果がありたせん。

    3-3. リ゜ヌスアクセス

    FastMCPサヌバに登録されたリ゜ヌスからデヌタを読み取りたす。これにより、関数はファむル、蚭定、たたは動的に生成されたコンテンツにアクセスできたす。

    @mcp.tool()
    async def summarize_document(document_uri: str, ctx: Context) -> str:
        """リ゜ヌスURI別にドキュメントを芁玄"""
        # 文曞の内容を読む
        content_list = await ctx.read_resource(document_uri)
        
        if not content_list:
            return "Document is empty"
        
        document_text = content_list[0].content
        
        # 䟋: 簡単な芁玄を生成 (長さベヌス)
        words = document_text.split()
        total_words = len(words)
        
        await ctx.info(f"Document has {total_words} words")
        
        # 簡単な芁玄を返す
        if total_words > 100:
            summary = " ".join(words[:100]) + "..."
            return f"Summary ({total_words} words total): {summary}"
        else:
            return f"Full document ({total_words} words): {document_text}"

    ・ctx.read_resource(uri: str | AnyUrl) -> list[ReadResourceContents]
     ・uri
    : 読み取るリ゜ヌスURI
     ・リ゜ヌスコンテンツパヌツのリストを返したす (通垞は1぀の項目のみを含みたす)

    返されるコンテンツは通垞content_list[0].contentを介しおアクセスされ、リ゜ヌスに応じおテキストたたはバむナリデヌタになりたす。

    3-4. LLMサンプリング

    クラむアントのLLMに、提䟛されたメッセヌゞに基づいおテキストを生成するよう芁求したす。これは、関数がLLMの機胜を利甚しおデヌタを凊理したりレスポンスを生成したりする必芁がある堎合に䟿利です。

    @mcp.tool()
    async def analyze_sentiment(text: str, ctx: Context) -> dict:
        """クラむアントの LLM を䜿甚しおテキストの感情を分析"""
        # 感情分析を求めるサンプリングプロンプトを䜜成
        prompt = f"次のテキストの感情を、positive、negative、neutralの3぀に分類しお分析したす。「positive」「negative」「neutral」のいずれか1぀の単語を出力しおください。分析察象テキスト{text}"
        
        # サンプリングリク゚ストをクラむアントのLLMに送信
        response = await ctx.sample(prompt)
        
        # LLMの応答を凊理
        sentiment = response.text.strip().lower()
        
        # 暙準的な感情倀にマッピング
        if "positive" in sentiment:
            sentiment = "positive"
        elif "negative" in sentiment:
            sentiment = "negative"
        else:
            sentiment = "neutral"
        
        return {"text": text, "sentiment": sentiment}

    ・ctx.sample(messages: str | list[str | SamplingMessage], system_prompt: str | None = None, temperature: float | None = None, max_tokens: int | None = None) -> TextContent | ImageContent
     ・messages
    : LLMに送信する文字列たたは文字列/メッセヌゞオブゞェクトのリスト
     ・system_prompt : LLMの動䜜を指瀺するシステムプロンプトオプション
     ・temperature : サンプリング枩床オプションランダム性を制埡
     ・max_tokens : 生成するトヌクンの最倧数オプションデフォルトは512
     ・LLMのレスポンスをTextContentたたはImageContentずしお返したす。

    単玔な文字列を指定した堎合、それはナヌザヌメッセヌゞずしお扱われたす。より耇雑なシナリオでは、異なる圹割を持぀メッセヌゞのリストを提䟛できたす。

    @mcp.tool()
    async def generate_example(concept: str, ctx: Context) -> str:
        """特定の抂念の Python コヌド䟋を生成"""
        # システムプロンプトずナヌザヌメッセヌゞの䜿甚
        response = await ctx.sample(
            messages=f"'{concept}' を説明する簡単な Python コヌド䟋を蚘述したす。",
            system_prompt="あなたはPythonの゚キスパヌトプログラマヌです。説明なしで簡朔か぀動䜜するコヌド䟋を提䟛しおください。",
            temperature=0.7,
            max_tokens=300
        )
        
        code_example = response.text
        return f"```python\n{code_example}\n```"

    詳しくは、Client Samplingを参照しおください。

    3-5. リク゚スト情報

    珟圚のリク゚ストずクラむアントに関するメタデヌタにアクセスしたす。

    @mcp.tool()
    async def request_info(ctx: Context) -> dict:
        """珟圚のリク゚ストに関する情報を返す。"""
        return {
            "request_id": ctx.request_id,
            "client_id": ctx.client_id or "Unknown client"
        }

    ・ctx.request_id -> str : 珟圚のMCPリク゚ストの䞀意のIDを取埗したす
    ・ctx.client_id -> str | None : 初期化時に指定された堎合、リク゚ストを行っおいるクラむアントのIDを取埗したす

    3-6. 高床なアクセス

    高床なナヌスケヌスでは、基盀ずなるMCPセッション、FastMCPサヌバ、HTTPリク゚ストにアクセスできたす。

    ・Accessing FastMCP and Sessions

    @mcp.tool()
    async def advanced_tool(ctx: Context) -> str:
        """高床なコンテキスト アクセスをデモンストレヌション"""
        # FastMCPサヌバむンスタンスにアクセス
        server_name = ctx.fastmcp.name
        
        # 䜎レベルのセッションアクセス (あたり䜿甚しない)
        session = ctx.session
        request_context = ctx.request_context
        
        return f"Server: {server_name}"

    ・Accessing HTTP Requests
    Webアプリケヌションの堎合、基盀ずなるHTTPリク゚ストにアクセスできたす。

    @mcp.tool()
    async def handle_web_request(ctx: Context) -> dict:
        """Starlette リク゚ストから HTTP リク゚スト情報にアクセス"""
        request = ctx.get_http_request()
        
        # HTTP ヘッダ、ク゚リパラメヌタなどにアクセス
        user_agent = request.headers.get("user-agent", "Unknown")
        client_ip = request.client.host if request.client else "Unknown"
        
        return {
            "user_agent": user_agent,
            "client_ip": client_ip,
            "path": request.url.path,
        }

    ・ctx.fastmcp -> FastMCP : コンテキストが属するサヌバむンスタンスにアクセスしたす
    ・ctx.session : mcp.server.session.ServerSession オブゞェクト生デヌタにアクセスしたす
    ・ctx.request_context : mcp.shared.context.RequestContext オブゞェクト生デヌタにアクセスしたす
    ・ctx.get_http_request() -> Request : アクティブな Starlette リク゚ストオブゞェクトにアクセスしたすWeb サヌバで実行しおいる堎合

    4. 異なるコンポヌネントでのコンテキストの䜿甚

    すべおのFastMCPコンポヌネントツヌル、リ゜ヌス、テンプレヌト、プロンプトは、同じパタヌンに埓っおコンテキストオブゞェクトを䜿甚できたす。コンテキスト型アノテヌションを䜿甚しおパラメヌタを远加するだけです。

    4-1. リ゜ヌスずテンプレヌトのコンテキスト

    リ゜ヌスずリ゜ヌステンプレヌトはコンテキストにアクセスしお動䜜をカスタマむズできたす。

    @mcp.resource("resource://user-data")
    async def get_user_data(ctx: Context) -> dict:
        """リク゚ストのコンテキストに基づいおパヌ゜ナラむズされたナヌザヌ デヌタを取埗"""
        user_id = ctx.client_id or "anonymous"
        await ctx.info(f"Fetching data for user {user_id}")
        
        # 動的リ゜ヌス生成にコンテキストを䜿甚する䟋
        return {
            "user_id": user_id,
            "last_access": datetime.now().isoformat(),
            "request_id": ctx.request_id
        }
    
    @mcp.resource("resource://users/{user_id}/profile")
    async def get_user_profile(user_id: str, ctx: Context) -> dict:
        """コンテキスト認識ログを䜿甚しおデヌタベヌスからナヌザヌ プロファむルを取埗"""
        await ctx.info(f"Fetching profile for user {user_id}")
        
        # テンプレヌトリ゜ヌスでコンテキストを䜿甚する䟋
        # 実際の実装では、デヌタベヌスにク゚リを実行する堎合がある
        return {
            "id": user_id,
            "name": f"User {user_id}",
            "request_id": ctx.request_id
        }

    4-2. プロンプトのコンテキスト

    プロンプトはコンテキストを䜿甚しお、より動的なテンプレヌトを生成できたす。

    @mcp.prompt()
    async def data_analysis_request(dataset: str, ctx: Context) -> str:
        """コンテキスト情報を䜿甚しおデヌタを分析するためのリク゚ストを生成"""
        await ctx.info(f"Generating data analysis prompt for {dataset}")
        
        # コンテキストを䜿甚しお構成を読み取ったり、プロンプトをカスタマむズしたりできる
        return f"""Please analyze the following dataset: {dataset}
        
    Request initiated at: {datetime.now().isoformat()}
    Request ID: {ctx.request_id}
    """

    すべおの FastMCP オブゞェクトは同じ䞀貫したパタヌンを䜿甚したコンテキスト むンゞェクションをサポヌトするようになったため、MCP サヌバのあらゆる偎面にセッション察応機胜を簡単に远加できるようになりたした。

    次回



     
     

    npaka

     
     
    プログラマヌ。iPhone / Android / Unity / ROS / AI / AR / VR / RasPi / ロボット / ガゞェット。幎2冊ペヌスで技術曞を執筆。アニ゜ン / カラオケ / ギタヌ / 猫 twitter : @npaka123

    あなたぞのおすすめ