MCP中的资源

这里我们说下MCP中,什么是资源

在MCP之中,资源是其中的核心原语,用于将服务器端的某些数据暴露给客户端,供客户端上的大模型进行使用,添加到上下文之中。

而这个数据,可以是任意类型的文本或者二进制数据,包括文件,数据库记录,日志,音频,视频等。

对于资源,在主机上是由着唯一的URI确定的,遵循着通用化的协议

例如

file:///home/user/docs/report.pdf

postgres://db.example.com/customers/schema

screen://localhost/display

根据不同的协议,确定URI格式,内容

但是需要注意一点,就是不同的MCP的客户端处理资源的方式不同

比如常见的Claude Tare,需要让用户明确选择资源

其他的客户端会自主的选择资源

因此需要注意,在利用MCP协议暴露资源的时候,需要意识到MCP Host类型不同,使用资源的方式不同,如果需要明确由LLM来控制资源使用,应该将资源封装为工具而不是资源。

其次是在MCP中资源的具体使用方式

服务器首先要声明自己支持Resource暴露功能

{

“capabilities”: {

“resources”: {

“subscribe”: true,

“listChanged”: true

}

}

}

其中subscribe 是指的客户端是否支持订阅单个资源

listChanged指的是资源列表变化的时候,服务器端是否发送通知。

服务器端需要一个统一的数据结构来描述每个资源对象,

包含 URI 资源标识符,name,description,mineType,size

并且在服务器上,分为了静态资源和动态资源

{

“uri”: “file:///logs/app.log”,

“name”: “Application Logs”,

“description”: “实时日志文件”,

“mimeType”: “text/plain”

}

{

“uriTemplate”: “logs://recent?timeframe={duration}”,

“name”: “最近日志”,

“description”: “按时长获取日志”,

“mimeType”: “text/plain”

}

对于动态数据,根据其中的标准填入,让客户端进行构建对应的URI,从而获取到最近的资源。

对于服务器端的代码实现,则是可以通过装饰器和注册函数来实现资源定义接口

# 使用装饰器注册资源列表处理函数

@app.list_resources()

async def list_resources() -> list[types.Resource]:

“””

实现资源列表API,返回服务器提供的资源列表

返回:

list[types.Resource]: 资源对象列表

“””

return [

# 创建一个资源对象

types.Resource(

uri=”file:///logs/app.log”, # 资源的唯一标识符

name=”Application Logs” # 资源的显示名称

mimeType=”text/plain”

)

]

通过app.list_resources来让服务器端注册暴露资源的函数

对于客户端,则是通过发起不同的查询请求,获取到可用资源

比如常见的 resources/list 请求来获取到可用资源

{

“jsonrpc”: “2.0”,

“id”: 1,

“method”: “resources/list”,

“params”: {

“cursor”: “optional-cursor-value”

}

}

对应的响应为:

{

“jsonrpc”: “2.0”,

“id”: 1,

“result”: {

“resources”: [

{

“uri”: “file:///project/src/main.rs”,

“name”: “main.rs”,

“description”: “Primary application entry point”,

“mimeType”: “text/x-rust”

}

],

“nextCursor”: “next-page-cursor”

}

}

通过resources/read 来指定URI,从而读取到文件内容。

客户端的请求格式如下

{

“jsonrpc”: “2.0”,

“id”: 2,

“method”: “resources/read”,

“params”: {

“uri”: “file:///logs/app.log”,

}

}

服务器的响应为

{

“jsonrpc”: “2.0”,

“id”: 2,

“method”: “resources/read”,

“params”: {

“uri”: “file:///logs/app.log”,

}

}

除此外还有这实时通知机制,让客户端保持对资源状态的感知

列表变更通知,当服务器资源进行增删改的时候,发出对应的通知

或者文件内容发生更新订阅,服务器端会通知对应的订阅者,方便获取到最新内容。

那么最终的交互流程为

IMG_256

同时我们可以结合代码来看,假设我们在项目目录下有一系列的文档

我们可以在服务器端通过函数进行资源暴露,代码如下

# 导入必要的库

import asyncio

import os

import mcp.types as types

from mcp.server import Server

from mcp.server.stdio import stdio_server

# 创建一个MCP服务器实例,名称为”example-server”

app = Server(“example-server”)

DOC_DIR = “你的路径/mcp-in-action/05-resource-资源发现/server/medical_docs”

# 注册资源列表

@app.list_resources()

async def list_resources() -> list[types.Resource]:

files = [f for f in os.listdir(DOC_DIR) if f.endswith(“.txt”)]

return [

types.Resource(

uri=f”file://{os.path.join(DOC_DIR, fname)}”,

name=fname,

description=”文档”,

mimeType=”text/plain”

)

for fname in files

]

# 读取资源内容

@app.read_resource()

async def read_resource(uri: str) -> str:

path = uri.replace(“file://”, “”)

with open(path, encoding=”utf-8″) as f:

return f.read()

async def main():

async with stdio_server() as streams:

await app.run(

streams[0],

streams[1],

app.create_initialization_options()

)

if __name__ == “__main__”:

asyncio.run(main())

之后客户端可以通过诸如stdio的协议进行读取。

import asyncio

import sys

from mcp import ClientSession, StdioServerParameters

from mcp.types import Notification, Resource

from mcp.client.stdio import stdio_client

async def main():

# 检查命令行参数 – 必须通过两个参数启动此程序,一个当前客户端,一个服务器程序

if len(sys.argv) < 2:

print(“Usage: python simple_client.py <path_to_server_script>”)

sys.exit(1)

server_script = sys.argv[1]

# 使用服务器的 Python 解释器来启动 Server,而不是当前客户端的Python环境

params = StdioServerParameters(

command=”你的环境路径/bin/python3″,

args=[server_script],

env=None

)

# 建立 stdio 传输并创建 Session

async with stdio_client(params) as (reader, writer):

async with ClientSession(reader, writer) as session:

# 初始化握手

await session.initialize()

# 发送初始化完成通知

notification = Notification(

method=”notifications/initialized”,

params={}

)

await session.send_notification(notification)

# 列出资源

response = await session.list_resources()

print(“资源列表:”)

# 打印返回格式

print(f”返回类型: {type(response)}”)

print(f”返回内容: {response}”)

# 简化处理资源列表

resources = getattr(response, ‘resources’, response)

for res in resources:

print(f”- URI: {res.uri}, Name: {res.name}”)

if __name__ == “__main__”:

asyncio.run(main())

通过发送通知并利用list_resources函数获取到了相关的资源。

最后我们总结下,

MCP支持其中服务端的资源向着客户端暴露数据和内容,作为LLM交互的上下文

并通过特定函数,诸如resources/list接口来提供URI使用。从而建立了合格的资源管理机制,来对外部数据进行引用