
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 ãµãŒãã®ããããåŽé¢ã«ã»ãã·ã§ã³å¯Ÿå¿æ©èœãç°¡åã«è¿œå ã§ããããã«ãªããŸããã