POML 与 MCP(Model Context Protocol)集成
本文基于官方页面(microsoft.github.io/poml/stable... MCP 的工作原理、安装方式、基于 POML 模板的动态工具集成方案、完整示例、与直接使用 MCP 的对比,以及未来原生支持计划。
导语/背景介绍
- MCP 是一种开放协议,为 LLM 应用与外部数据源和工具之间提供标准化的上下文访问与工具调用能力。
- 警告:当前 POML 尚未内置支持 MCP。官方建议通过"工具调用 + 模板"的方式模拟 MCP 行为;未来版本计划提供原生支持。
- 说明:部分提供方支持远程调用 MCP 服务器(如 OpenAI Response API),这类行为不在本文档范围内,因为开发者无需直接处理 MCP 调用细节。
安装与使用方法(Installation)
bash
pip install mcp
工作原理(How MCP Works)
- 客户端---服务端架构:
- 工具发现:MCP 服务器公开可调用的工具/函数
- 工具调用:客户端(LLM 应用)按参数调用工具
- 响应处理:服务器执行并返回结果给客户端
- 典型流程:
- 初始化:建立与 MCP 服务器的连接
- 列出工具:查询可用工具及其 schema
- 工具执行:当 LLM 需要调用时向 MCP 服务器发送请求
- 结果集成:将工具结果整合回对话
动态工具(Dynamic Tools with MCP)
- 通过 POML 的模板能力,可在运行时动态加载 MCP 服务器提供的工具定义,并在上下文中跟踪交互。
- 该方案适用于 MCP 的原因(官方表述):
- MCP 服务器在运行时动态提供工具
- 不同 MCP 服务器的工具 schema 各不相同
- 同一个 POML 模板可适配任意 MCP 服务器
- 需要跨轮次跟踪工具交互
动态工具模板(POML Template for Dynamic Tools)
xml
<poml> <system-msg>{{ system }}</system-msg> <human-msg>{{ input }}</human-msg> <!-- Dynamic Tool Loading: Iterates through tools discovered from the MCP server --> <div for="tool in tools"> <!-- Each tool's name, description, and JSON schema are inserted dynamically --> <tool-definition name="{{ tool.name }}" description="{{ tool.description }}"> {{ tool.schema }} </tool-definition> </div> <!-- Interaction History: Maintains conversation history with tool calls and responses --> <div for="i in interactions"> <!-- All dynamic content is provided through the context parameter --> <tool-request for="res in i" id="{{ res.id }}" name="{{ res.name }}" parameters="{{ res.input }}" /> <tool-response for="res in i" id="{{ res.id }}" name="{{ res.name }}"> <!-- Embeds the tool output directly via POML's Object componenet. --> <object data="{{ res.output }}"/> </tool-response> </div> <runtime model="gpt-4.1"/> </poml>
完整示例(Complete Example with MCP Server)
官方示例基于一个"掷骰子"的公共 MCP 演示服务器,演示工具发现、工具调用处理与主对话循环。
1) 工具发现与转换(Tool Discovery and Conversion)
python
async def discover_mcp_tools(mcp_session): """Discover available tools from MCP server and convert to POML format""" mcp_tools = (await mcp_session.list_tools()).tools print(f"Available MCP tools: {mcp_tools}") # Convert MCP tools to POML context format. # The format must be compatible with the POML template above. poml_tools = [] for tool in mcp_tools: poml_tools.append({ "name": tool.name, "description": tool.description, "schema": tool.inputSchema }) return poml_tools
2) 处理工具调用(Process Tool Calls)
python
async def process_tool_calls(mcp_session, tool_calls): """Execute MCP tool calls and format responses""" responses = [] for tool_call in tool_calls: function = tool_call.function args = json.loads(function.arguments or "{}") # Call MCP server tool result = await mcp_session.call_tool(function.name, args) print(f"Tool {function.name} result: {result}") # Format for POML context. # This format must be compatible with the POML template above. responses.append({ "id": tool_call.id, "name": function.name, "input": args, "output": result.model_dump() }) return responses
3) 主对话循环(Main Conversation Loop)
python
async def run_mcp_conversation(mcp_session, context): """Run the conversation loop with MCP tools""" # Discover and add tools to context context["tools"] = await discover_mcp_tools(mcp_session) client = OpenAI() # Conversation loop while True: # Generate OpenAI parameters from POML params = poml.poml("dynamic_tools.poml", context=context, format="openai_chat") response = client.chat.completions.create(**params) message = response.choices[0].message if message.tool_calls: # Process and add tool responses to context responses = await process_tool_calls(mcp_session, message.tool_calls) context["interactions"].append(responses) else: # Final response - conversation complete print(f"Assistant: {message.content}") return message.content
4) 完整集成(Complete Integration)
python
import json import asyncio from openai import OpenAI import poml from mcp import ClientSession from mcp.client.sse import sse_client async def main(): # Initialize context for POML context = { "system": "You are a helpful DM assistant. Use the dice-rolling tool when needed.", "input": "Roll 2d4+1", "tools": [], "interactions": [] } # Connect to MCP server (using public demo server) server_url = "https://dmcp-server.deno.dev/sse" async with sse_client(server_url) as (read, write): async with ClientSession(read, write) as mcp_session: await mcp_session.initialize() result = await run_mcp_conversation(mcp_session, context) print(f"Conversation completed: {result}") if __name__ == "__main__": asyncio.run(main())
与直接使用 MCP 的对比(Comparison with Direct MCP Usage)
- 主要差异(官方表述):
- 消息管理:直接方式需要手动区分
"role": "assistant"
与"role": "tool"
;POML 通过请求/响应成对管理,自动处理 - 内容渲染:直接方式需手动字符串转换与格式化;POML 使用
<object>
组件统一处理各类内容 - 工具格式:直接方式需手动按 OpenAI 等提供方要求格式化;POML 使用声明式模板完成转换
- 消息管理:直接方式需要手动区分
不使用 POML(Direct Approach)
python
# Manually manage message roles and format
messages.append({"role": "assistant", "content": msg.content or "", "tool_calls": msg.tool_calls})
messages.append({"role": "tool", "tool_call_id": tc.id, "name": fn.name, "content": text_result})
# Manually format tool results from MCP
if result.structuredContent is not None:
text_result = json.dumps(result.structuredContent)
else:
text_result = "\n".join([c.text for c in result.content if isinstance(c, types.TextContent)])
# Convert MCP tools to OpenAI format
oa_tools.append({
"type": "function",
"function": {"name": t.name, "description": t.description, "parameters": t.inputSchema}
})
使用 POML(Structured Approach)
python
# Simply track tool request/response pairs
context["interactions"].append([
{"id": tc.id, "name": fn.name, "input": args, "output": result.model_dump()}
])
未来原生支持(Future Native Support)
- POML 计划提供 MCP 的原生支持,届时会为 MCP 操作提供简化语法。
- 在此之前,可采用本文基于模板的方式作为过渡性解决方案。
使用场景
- 官方页面未提供具体使用场景示例。
总结/展望
- 文中基于官方页面介绍了 MCP 的工作原理、安装方法、POML 模板的动态工具集成方案、完整示例,以及与直接使用 MCP 的对比与未来计划。
- 获取完整细节与最新进展,请参考官方文档: