使用 Ollama 和 LangChain 快速入门 MCP
PolarSPARC
Model Context Protocol (MCP) 快速入门 Bhaskar S | 2025/04/13 ---|--- 概述 当 LLM 首次出现时,使用 LLM 构建的企业应用仅限于 LLM 训练所用的知识。 这些应用程序对于诸如文本生成、文本情感分析、文本摘要等任务非常有用。
LLM 应用的下一个演变是使用 RAG 通过 Vector Stores 与企业数据资产集成,以实现上下文知识检索。
随着像 LangChain 这样的 Agentic 框架的出现,并支持工具集成以实现手动任务自动化,LLM 应用不断发展,从而推动了企业环境中的自动化。
然而,挑战在于没有工具集成的行业标准,每个框架都有自己的工具集成方法。
Model Context Protocol (或 MCP) 的出现改变了工具集成的格局。
可以将 MCP 视为其他企业服务之上的行业标准层,它允许任何 Agentic 框架(例如 LangChain、LlamaIndex 等)一致地与企业工具集成。
换句话说,MCP 是一种开放协议,可实现 LLM 应用与外部数据源(数据库、文件等)和工具(GitHub、ServiceNow 等)之间的无缝集成。
MCP 规范包含以下核心组件:
- MCP Server:连接到各种外部和内部数据源和工具,以向 Agentic LLM 应用公开特定功能。 可以将其视为服务提供商。
- MCP Client:以标准化方式连接 MCP Server 并与之交互。
- MCP Host:使用 MCP Client 访问 MCP Server 的 LLM 应用。
安装与设置 安装和设置将在基于 Ubuntu 24.04 LTS 的 Linux 桌面系统上进行。 确保在桌面上安装并设置了 Python 3.x 编程语言。
此外,确保在 Linux 桌面上安装并设置了 Ollama(请参考 相关说明)。
假设 Linux 桌面上的 IP 地址是 192.168.1.25,通过在终端窗口中执行以下命令来启动 Ollama 平台:
$ docker run --rm --name ollama --network=host -p 192.168.1.25:11434:11434 -v $HOME/.ollama:/root/.ollama ollama/ollama:0.6.2
对于 LLM 模型,我们将使用最近发布的 IBM Granite 3.1 模型。
打开一个新的终端窗口并执行以下 Docker 命令来下载 LLM 模型:
$ docker exec -it ollama ollama run granite3.1-moe:1b
要为此入门安装必要的 Python 模块,请执行以下命令:
$ pip install dotenv langchain lanchain-core langchain-ollama langgraph mcp langchain-mcp-adapters starlette sse-starlette uvicorn
这完成了使用 Python 进行 MCP 实践演示的所有安装和设置。
MCP 实践 (使用 Python) 在以下章节中,我们将使用 Ollama 和 LangChain 动手实践 MCP。 事不宜迟,让我们开始吧!
创建一个名为 .env 的文件,并定义以下环境变量:
.env
LLM_TEMPERATURE=0.2
OLLAMA_MODEL='granite3.1-moe:1b'
OLLAMA_BASE_URL='http://192.168.1.25:11434'
PY_PROJECT_DIR='/projects/python/MCP/'
SSE_BASE_URL='http://192.168.1.25:8000/sse'
以下是一个简单的基于 LangChain 的 ReACT 应用:
interest_client.py
#
# @Author: Bhaskar S
# @Blog: https://www.polarsparc.com
# @Date: 06 April 2025
#
import asyncio
import logging
import os
from dotenv import load_dotenv, find_dotenv
from langchain_core.tools import tool
from langchain_ollama import ChatOllama
from langgraph.prebuilt import create_react_agent
logging.basicConfig(format='%(levelname)s %(asctime)s - %(message)s', level=logging.INFO)
logger = logging.getLogger('interest_client')
load_dotenv(find_dotenv())
home_dir = os.getenv('HOME')
llm_temperature = float(os.getenv('LLM_TEMPERATURE'))
ollama_model = os.getenv('OLLAMA_MODEL')
ollama_base_url = os.getenv('OLLAMA_BASE_URL')
py_project_dir = os.getenv('PY_PROJECT_DIR')
ollama_chat_llm = ChatOllama(base_url=ollama_base_url, model=ollama_model, temperature=llm_temperature)
@tool
def dummy():
"""This is a dummy tool"""
return None
async def main():
tools = [dummy]
# Initialize a ReACT agent
agent = create_react_agent(ollama_chat_llm, tools)
# Case - 1 : Simple interest definition
agent_response_1 = await agent.ainvoke(
{'messages': 'what is the simple interest ?'})
logger.info(agent_response_1['messages'][::-1])
# Case - 2 : Simple interest calculation
agent_response_2 = await agent.ainvoke(
{'messages': 'compute the simple interest for a principal of 1000 at rate 3.75 ?'})
logger.info(agent_response_2['messages'][::-1])
# Case - 3 : Compound interest calculation
agent_response_3 = await agent.ainvoke(
{'messages': 'compute the compound interest for a principal of 1000 at rate 4.25 ?'})
logger.info(agent_response_3['messages'][::-1])
if __name__ == '__main__':
asyncio.run(main())
要执行上面的 Python 代码,请在终端窗口中执行以下命令:
$ python interest_client.py
以下是典型输出:
Output.1
INFO 2025-04-13 09:54:41,426 - HTTP Request: POST http://192.168.1.25:11434/api/chat "HTTP/1.1 200 OK"
INFO 2025-04-13 09:54:41,428 - [AIMessage(content='The simple interest (SI) is calculated using the formula:\n\nSI = P * R * T / 100\n\nWhere:\n- P is the principal amount (the initial sum of money)\n- R is the rate of interest per annum\n- T is the time in years\n\nFor example, if you have Rs. 1000 as a principal amount with an annual interest rate of 5% for 2 years, then:\n\nSI = 1000 * 5/100 * 2 / 100\nSI = 1000 * 0.05 * 2 / 100\nSI = 1000 * 0.10 / 100\nSI = Rs. 10', additional_kwargs={}, response_metadata={'model': 'granite3.1-moe:1b', 'created_at': '2025-04-13T13:54:41.425392119Z', 'done': True, 'done_reason': 'stop', 'total_duration': 1338038780, 'load_duration': 6784726, 'prompt_eval_count': 84, 'prompt_eval_duration': 31132026, 'eval_count': 172, 'eval_duration': 1298733422, 'message': Message(role='assistant', content='The simple interest (SI) is calculated using the formula:\n\nSI = P * R * T / 100\n\nWhere:\n- P is the principal amount (the initial sum of money)\n- R is the rate of interest per annum\n- T is the time in years\n\nFor example, if you have Rs. 1000 as a principal amount with an annual interest rate of 5% for 2 years, then:\n\nSI = 1000 * 5/100 * 2 / 100\nSI = 1000 * 0.05 * 2 / 100\nSI = 1000 * 0.10 / 100\nSI = Rs. 10', images=None, tool_calls=None)}, id='run-d5a4f021-1a87-4bac-b6a4-5dc3239e6f7b-0', usage_metadata={'input_tokens': 84, 'output_tokens': 172, 'total_tokens': 256}), HumanMessage(content='what is the simple interest ?', additional_kwargs={}, response_metadata={}, id='aee2669b-36d3-42aa-bdb6-53fd2454ea12')]
INFO 2025-04-13 09:54:41,604 - HTTP Request: POST http://192.168.1.25:11434/api/chat "HTTP/1.1 200 OK"
INFO 2025-04-13 09:54:41,605 - [AIMessage(content='{"code":200,"message":"Interest Calculation Completed"}', additional_kwargs={}, response_metadata={'model': 'granite3.1-moe:1b', 'created_at': '2025-04-13T13:54:41.603723234Z', 'done': True, 'done_reason': 'stop', 'total_duration': 173677918, 'load_duration': 6716296, 'prompt_eval_count': 99, 'prompt_eval_duration': 36381866, 'eval_count': 15, 'eval_duration': 129621404, 'message': Message(role='assistant', content='{"code":200,"message":"Interest Calculation Completed"}', images=None, tool_calls=None)}, id='run-a73da0a1-6862-4aba-ba1d-095df8a85e0c-0', usage_metadata={'input_tokens': 99, 'output_tokens': 15, 'total_tokens': 114}), HumanMessage(content='compute the simple interest for a principal of 1000 at rate 3.75 ?', additional_kwargs={}, response_metadata={}, id='2d929265-e096-4275-aaa1-001f15d34047')]
INFO 2025-04-13 09:54:41,771 - HTTP Request: POST http://192.168.1.25:11434/api/chat "HTTP/1.1 200 OK"
INFO 2025-04-13 09:54:41,773 - [AIMessage(content='{"code":301,"message":"Compound Interest Formula Calculation"}', additional_kwargs={}, response_metadata={'model': 'granite3.1-moe:1b', 'created_at': '2025-04-13T13:54:41.771498307Z', 'done': True, 'done_reason': 'stop', 'total_duration': 164399085, 'load_duration': 6787425, 'prompt_eval_count': 99, 'prompt_eval_duration': 43029403, 'eval_count': 17, 'eval_duration': 113471897, 'message': Message(role='assistant', content='{"code":301,"message":"Compound Interest Formula Calculation"}', images=None, tool_calls=None)}, id='run-7dfd2c78-0847-461c-a822-eb4b4c4f54a3-0', usage_metadata={'input_tokens': 99, 'output_tokens': 17, 'total_tokens': 116}), HumanMessage(content='compute the compound interest for a principal of 1000 at rate 4.25 ?', additional_kwargs={}, response_metadata={}, id='284397b4-2575-46d6-bebc-83bff0c6f64f')]
从上面的 Output.1 可以明显看出,LLM 应用能够定义什么是单利,但是无法计算单利或复利。
现在让我们构建我们的第一个 MCP Server,用于计算单利(一年)和复利(一年)。
以下是我们的第一个用 Python 编写的 MCP Server 代码:
interest_mcp_server.py
#
# @Author: Bhaskar S
# @Blog: https://www.polarsparc.com
# @Date: 06 April 2025
#
from mcp.server.fastmcp import FastMCP
import logging
logging.basicConfig(format='%(levelname)s %(asctime)s - %(message)s', level=logging.INFO)
logger = logging.getLogger('interest_mcp_server')
mcp = FastMCP('InterestCalculator')
@mcp.tool()
def yearly_simple_interest(principal: float, rate:float) -> float:
"""Tool to compute simple interest rate for a year."""
logger.info(f'Simple interest -> Principal: {principal}, Rate: {rate}')
return principal * rate / 100.00
@mcp.tool()
def yearly_compound_interest(principal: float, rate:float) -> float:
"""Tool to compute compound interest rate for a year."""
logger.info(f'Compound interest -> Principal: {principal}, Rate: {rate}')
return principal * (1 + rate / 100.0)
if __name__ == '__main__':
logger.info(f'Starting the interest MCP server...')
mcp.run(transport='stdio')
MCP 规范支持两种类型的传输方式,如下所示:
- 标准 IO (stdio):通过标准输入和输出流实现通信,这对于与命令行工具集成非常有用。
- Server Sent Events (sse):通过 HTTP POST 请求实现服务器到客户端的流式传输,这对于与启用网络的服务集成非常有用。
在我们的示例中,我们选择 stdio 传输。
下一步是构建一个 MCP Host (LLM 应用),它使用 MCP Client 访问上述 MCP Server,以计算单利和复利。
以下是我们的第一个用 Python 编写的 MCP Host LLM 应用代码:
interest_mcp_client.py
#
# @Author: Bhaskar S
# @Blog: https://www.polarsparc.com
# @Date: 06 April 2025
#
from dotenv import load_dotenv, find_dotenv
from langchain_ollama import ChatOllama
from langchain_mcp_adapters.tools import load_mcp_tools
from langgraph.prebuilt import create_react_agent
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio
import logging
import os
logging.basicConfig(format='%(levelname)s %(asctime)s - %(message)s', level=logging.INFO)
logger = logging.getLogger('interest_mcp_client')
load_dotenv(find_dotenv())
home_dir = os.getenv('HOME')
llm_temperature = float(os.getenv('LLM_TEMPERATURE'))
ollama_model = os.getenv('OLLAMA_MODEL')
ollama_base_url = os.getenv('OLLAMA_BASE_URL')
py_project_dir = os.getenv('PY_PROJECT_DIR')
server_params = StdioServerParameters(
command='python',
# Full absolute path to mcp server
args=[home_dir + py_project_dir + 'interest_mcp_server.py'],
)
ollama_chat_llm = ChatOllama(base_url=ollama_base_url, model=ollama_model, temperature=llm_temperature)
async def main():
# Will launch the MCP server and communicate via stdio/stdout
async with stdio_client(server_params) as (read, write):
# Create a MCP client session
async with ClientSession(read, write) as session:
# Connect to the MCP server
await session.initialize()
# Get the list of all the registered tools
tools = await load_mcp_tools(session)
logger.info(f'Loaded MCP Tools -> {tools}')
# Initialize a ReACT agent
agent = create_react_agent(ollama_chat_llm, tools)
# Case - 1 : Simple interest definition
agent_response_1 = await agent.ainvoke(
{'messages': 'explain the definition of simple interest ?'})
logger.info(agent_response_1['messages'][::-1])
# Case - 2 : Simple interest calculation
agent_response_2 = await agent.ainvoke(
{'messages': 'compute the simple interest for a principal of 1000 at rate 3.75 ?'})
logger.info(agent_response_2['messages'][::-1])
# Case - 3 : Compound interest calculation
agent_response_3 = await agent.ainvoke(
{'messages': 'compute the compound interest for a principal of 1000 at rate 4.25 ?'})
logger.info(agent_response_3['messages'][::-1])
if __name__ == '__main__':
asyncio.run(main())
要执行上面的 Python 代码,请在终端窗口中执行以下命令:
$ python interest_mcp_client.py
以下是典型输出:
Output.2
INFO 2025-04-13 10:24:36,353 - Starting the interest MCP server...
INFO 2025-04-13 10:24:36,358 - Processing request of type ListToolsRequest
INFO 2025-04-13 10:24:36,359 - Loaded MCP Tools -> [StructuredTool(name='yearly_simple_interest', description='Tool to compute simple interest rate for a year.', args_schema={'properties': {'principal': {'title': 'Principal', 'type': 'number'}, 'rate': {'title': 'Rate', 'type': 'number'}}, 'required': ['principal', 'rate'], 'title': 'yearly_simple_interestArguments', 'type': 'object'}, response_format='content_and_artifact', coroutine=<function ClientSession.load_mcp_tools.<locals>.<lambda>.<locals>.call_tool at 0x76293a568720>), StructuredTool(name='yearly_compound_interest', description='Tool to compute compound interest rate for a year.', args_schema={'properties': {'principal': {'title': 'Principal', 'type': 'number'}, 'rate': {'title': 'Rate', 'type': 'number'}}, 'required': ['principal', 'rate'], 'title': 'yearly_compound_interestArguments', 'type': 'object'}, response_format='content_and_artifact', coroutine=<function ClientSession.load_mcp_tools.<locals>.<lambda>.<locals>.call_tool at 0x76293a568900>)]
INFO 2025-04-13 10:24:38,406 - HTTP Request: POST http://192.168.1.25:11434/api/chat "HTTP/1.1 200 OK"
INFO 2025-04-13 10:24:38,408 - [AIMessage(content='Simple Interest (SI) is a method of calculating interest where only the principal amount (the initial sum of money) and not the accumulated interest, is used to calculate the total interest paid or earned over a specific period. The formula for Simple Interest is:\n\nSI = P * R * T / 100\n\nWhere:\n- P is the principal amount (the initial sum of money)\n- R is the rate of interest per annum\n- T is the time in years\n\nThe SI is calculated by multiplying the principal by the rate and then dividing it by 100. The result gives you the total interest for that period, expressed as a percentage of the principal amount. For example, if you have $1000 as your principal, an annual interest rate of 5%, and you want to know how much you would earn in one year, you would calculate:\n\nSI = 1000 * 5/100 * 1 = $50\n\nThis means that for every $1000 you have, you would earn $50 in interest over the course of a year.', additional_kwargs={}, response_metadata={'model': 'granite3.1-moe:1b', 'created_at': '2025-04-13T14:24:38.405888113Z', 'done': True, 'done_reason': 'stop', 'total_duration': 2038184289, 'load_duration': 4645995, 'prompt_eval_count': 179, 'prompt_eval_duration': 34893791, 'eval_count': 248, 'eval_duration': 1997458315, 'message': Message(role='assistant', content='Simple Interest (SI) is a method of calculating interest where only the principal amount (the initial sum of money) and not the accumulated interest, is used to calculate the total interest paid or earned over a specific period. The formula for Simple Interest is:\n\nSI = P * R * T / 100\n\nWhere:\n- P is the principal amount (the initial sum of money)\n- R is the rate of interest per annum\n- T is the time in years\n\nThe SI is calculated by multiplying the principal by the rate and then dividing it by 100. The result gives you the total interest for that period, expressed as a percentage of the principal amount. For example, if you have $1000 as your principal, an annual interest rate of 5%, and you want to know how much you would earn in one year, you would calculate:\n\nSI = 1000 * 5/100 * 1 = $50\n\nThis means that for every $1000 you have, you would earn $50 in interest over the course of a year.', images=None, tool_calls=None)}, id='run-4877f653-8e87-44b9-9f23-3ead28ba5441-0', usage_metadata={'input_tokens': 179, 'output_tokens': 248, 'total_tokens': 427}), HumanMessage(content='explain the definition of simple interest ?', additional_kwargs={}, response_metadata={}, id='046aef94-62e2-4b38-a398-84d3a57fba4d')]
INFO 2025-04-13 10:24:38,872 - HTTP Request: POST http://192.168.1.25:11434/api/chat "HTTP/1.1 200 OK"
INFO 2025-04-13 10:24:38,882 - Processing request of type CallToolRequest
INFO 2025-04-13 10:24:38,882 - Simple interest -> Principal: 1000.0, Rate: 3.75
INFO 2025-04-13 10:24:39,170 - HTTP Request: POST http://192.168.1.25:11434/api/chat "HTTP/1.1 200 OK"
INFO 2025-04-13 10:24:39,171 - [AIMessage(content='The simple interest for a principal of $1000 at an annual rate of 3.75% is $37.50.', additional_kwargs={}, response_metadata={'model': 'granite3.1-moe:1b', 'created_at': '2025-04-13T14:24:39.170273321Z', 'done': True, 'done_reason': 'stop', 'total_duration': 285605746, 'load_duration': 6872903, 'prompt_eval_count': 239, 'prompt_eval_duration': 46254681, 'eval_count': 32, 'eval_duration': 227943217, 'message': Message(role='assistant', content='The simple interest for a principal of $1000 at an annual rate of 3.75% is $37.50.', images=None, tool_calls=None)}, id='run-8bd6ce67-d04e-4320-a1e1-2fcc94811fac-0', usage_metadata={'input_tokens': 239, 'output_tokens': 32, 'total_tokens': 271}), ToolMessage(content='37.5', name='yearly_simple_interest', id='a7e69290-1162-41a7-bd05-08e4738e7a51', tool_call_id='eea9e5ab-d0a5-4092-9b80-9a7f7bfa9e11'), AIMessage(content='', additional_kwargs={}, response_metadata={'model': 'granite3.1-moe:1b', 'created_at': '2025-04-13T14:24:38.879515025Z', 'done': True, 'done_reason': 'stop', 'total_duration': 469244684, 'load_duration': 6761794, 'prompt_eval_count': 193, 'prompt_eval_duration': 38995191, 'eval_count': 60, 'eval_duration': 422119871, 'message': Message(role='assistant', content='', images=None, tool_calls=None)}, id='run-f32cbb8e-2f7b-41b6-a144-f403fb30f956-0', tool_calls=[{'name': 'yearly_simple_interest', 'args': {'principal': 1000, 'rate': 3.75}, 'id': 'eea9e5ab-d0a5-4092-9b80-9a7f7bfa9e11', 'type': 'tool_call'}], usage_metadata={'input_tokens': 193, 'output_tokens': 60, 'total_tokens': 253}), HumanMessage(content='compute the simple interest for a principal of 1000 at rate 3.75 ?', additional_kwargs={}, response_metadata={}, id='2bf88431-a08e-4679-9d31-0c62bfbac9a5')]
INFO 2025-04-13 10:24:39,478 - HTTP Request: POST http://192.168.1.25:11434/api/chat "HTTP/1.1 200 OK"
INFO 2025-04-13 10:24:39,494 - Processing request of type CallToolRequest
INFO 2025-04-13 10:24:39,494 - Compound interest -> Principal: 1000.0, Rate: 4.25
INFO 2025-04-13 10:24:39,858 - HTTP Request: POST http://192.168.1.25:11434/api/chat "HTTP/1.1 200 OK"
INFO 2025-04-13 10:24:39,860 - [AIMessage(content='The compound interest for a principal of $1000 at a rate of 4.25% per year is approximately $1,042.50.', additional_kwargs={}, response_metadata={'model': 'granite3.1-moe:1b', 'created_at': '2025-04-13T14:24:39.858215452Z', 'done': True, 'done_reason': 'stop', 'total_duration': 362158794, 'load_duration': 6921843, 'prompt_eval_count': 241, 'prompt_eval_duration': 39011381, 'eval_count': 37, 'eval_duration': 312756614, 'message': Message(role='assistant', content='The compound interest for a principal of $1000 at a rate of 4.25% per year is approximately $1,042.50.', images=None, tool_calls=None)}, id='run-13a4f3b1-603e-44f1-963b-585b2b8fc0e5-0', usage_metadata={'input_tokens': 241, 'output_tokens': 37, 'total_tokens': 278}), ToolMessage(content='1042.5', name='yearly_compound_interest', id='6914a123-c9e0-4ea6-8070-7aa72b830c00', tool_call_id='597e2076-eb5c-4767-ad73-33b5d7810dbf'), AIMessage(content='', additional_kwargs={}, response_metadata={'model': 'granite3.1-moe:1b', 'created_at': '2025-04-13T14:24:39.49171724Z', 'done': True, 'done_reason': 'stop', 'total_duration': 317826794, 'load_duration': 6408858, 'prompt_eval_count': 193, 'prompt_eval_duration': 37113360, 'eval_count': 39, 'eval_duration': 272645313, 'message': Message(role='assistant', content='', images=None, tool_calls=None)}, id='run-087af799-5faa-4b2d-9d99-480026fb63bc-0', tool_calls=[{'name': 'yearly_compound_interest', 'args': {'principal': 1000, 'rate': 4.25}, 'id': '597e2076-eb5c-4767-ad73-33b5d7810dbf', 'type': 'tool_call'}], usage_metadata={'input_tokens': 193, 'output_tokens': 39, 'total_tokens': 232}), HumanMessage(content='compute the compound interest for a principal of 1000 at rate 4.25 ?', additional_kwargs={}, response_metadata={}, id='81328b6c-5277-49e5-b9e6-da08da988a85')]
太棒了!从上面的 Output.2 可以明显看出,LLM 应用不仅能够定义什么是单利,还能够使用 MCP Server 公开的工具计算单利和复利。
一个典型的企业 LLM Agentic 应用会调用多个 MCP Server 来执行特定任务。 在我们的下一个示例中,LLM Host 应用将演示如何设置和使用多个工具。
以下是我们的第二个用 Python 编写的 MCP Server 代码,它将调用 shell 命令:
shell_mcp_server.py
#
# @Author: Bhaskar S
# @Blog: https://www.polarsparc.com
# @Date: 12 April 2025
#
import subprocess
from mcp.server.fastmcp import FastMCP
import logging
logging.basicConfig(format='%(levelname)s %(asctime)s - %(message)s', level=logging.INFO)
logger = logging.getLogger('shell_mcp_server')
mcp = FastMCP('ShellCommandExecutor')
# DISCLAIMER: This is purely for demonstration purposes and NOT to be used in production environment
@mcp.tool()
def execute_shell_command(command: str) -> str:
"""Tool to execute shell commands"""
logger.info(f'Executing shell command: {command}')
try:
result = subprocess.run(command, shell=True, check=True, text=True, capture_output=True)
if result.returncode != 0:
return f'Error executing shell command - {command}'
return result.stdout
except subprocess.CalledProcessError as e:
logger.error(e)
if __name__ == '__main__':
logger.info(f'Starting the shell executor MCP server...')
mcp.run(transport='stdio')
以下是我们的第二个使用多个工具的 MCP Host LL