前言
在MCP TypeScript-SDK的初级篇中,我们介绍了资源开发的基础知识,包括静态资源与动态资源的创建、资源模板设计与参数提取,以及基本的资源列表与发现机制。随着应用规模的扩大和复杂性的提高,我们需要更加高级的资源设计方案来应对各种挑战。
本文作为中级篇的第三篇,将深入探讨MCP高级资源设计,包括复杂资源结构设计、资源分页与过滤、资源缓存策略以及大规模资源管理方案。通过学习这些高级技术,你将能够构建更加高效、灵活且可扩展的MCP资源系统,满足复杂企业级应用的需求。
一、复杂资源结构设计
在实际应用场景中,资源往往具有复杂的结构和关系,简单的扁平资源结构无法满足复杂业务需求。本节将探讨如何设计和实现复杂的资源结构。
1.1 嵌套资源设计
嵌套资源是指资源之间存在层级关系,子资源依附于父资源存在。嵌套资源设计可以更好地表达业务实体之间的从属关系。
import {
McpServer } from '@modelcontextprotocol/sdk';
import {
z } from 'zod';
const server = new McpServer({
name: 'nested-resource-server',
description: '嵌套资源示例服务器',
version: '1.0.0',
});
// 创建公司资源
server.registerResource({
name: 'companies',
description: '公司列表资源',
parameters: z.object({
filter: z.string().optional().describe('过滤条件'),
}),
resolve: async ({
filter }) => {
const companies = await fetchCompanies(filter);
return {
content: JSON.stringify(companies),
metadata: {
count: companies.length,
filter: filter || 'none',
}
};
}
});
// 创建部门资源(作为公司的子资源)
server.registerResource({
name: 'companies/:companyId/departments',
description: '部门列表资源',
parameters: z.object({
companyId: z.string().describe('公司ID'),
filter: z.string().optional().describe('过滤条件'),
}),
resolve: async ({
companyId, filter }) => {
const departments = await fetchDepartments(companyId, filter);
return {
content: JSON.stringify(departments),
metadata: {
companyId,
count: departments.length,
filter: filter || 'none',
}
};
}
});
// 创建员工资源(作为部门的子资源)
server.registerResource({
name: 'companies/:companyId/departments/:departmentId/employees',
description: '员工列表资源',
parameters: z.object({
companyId: z.string().describe('公司ID'),
departmentId: z.string().describe('部门ID'),
filter: z.string().optional().describe('过滤条件'),
}),
resolve: async ({
companyId, departmentId, filter }) => {
const employees = await fetchEmployees(companyId, departmentId, filter);
return {
content: JSON.stringify(employees),
metadata: {
companyId,
departmentId,
count: employees.length,
filter: filter || 'none',
}
};
}
});
1.2 资源关系与引用
在复杂系统中,资源之间往往存在各种关系,如一对一、一对多、多对多等。通过资源引用可以表达这些复杂关系,并允许客户端遍历相关资源。
// 使用URI引用表达资源之间的关系
server.registerResource({
name: 'projects/:projectId',
description: '项目详情',
parameters: z.object({
projectId: z.string().describe('项目ID'),
}),
resolve: async ({
projectId }) => {
const project = await fetchProject(projectId);
// 构建关联资源的URI引用
const teamUri = `teams://${
project.teamId}`;
const tasksUri = `projects://${
projectId}/tasks`;
const documentsUri = `projects://${
projectId}/documents`;
return {
content: JSON.stringify({
...project,
// 添加资源引用
_links: {
team: teamUri,
tasks: tasksUri,
documents: documentsUri,
}
}),
metadata: {
relatedResources: [teamUri, tasksUri, documentsUri],
}
};
}
});
1.3 资源版本控制
对于频繁变化的资源,版本控制是确保一致性和兼容性的重要机制。
// 实现资源版本控制
server.registerResource({
name: 'api-specs/:version',
description: 'API规范文档',
parameters: z.object({
version: z.string().describe('API版本,格式为v1, v2等,或latest表示最新版本'),
}),
resolve: async ({
version }) => {
// 处理特殊版本标识符
const actualVersion = version === 'latest'
? await getLatestApiVersion()
: version;
const apiSpec = await fetchApiSpec(actualVersion);
if (!apiSpec) {
throw new Error(`API规范版本 ${
actualVersion} 不存在`);
}
return {
content: apiSpec.content,
metadata: {
version: actualVersion,
publishedAt: apiSpec.publishedAt,
isLatest: await isLatestVersion(actualVersion),
previousVersion: apiSpec.previousVersion,
nextVersion: apiSpec.nextVersion,
}
};
}
});
1.4 多语言资源支持
全球化应用需要支持多种语言的资源内容,MCP可以轻松实现多语言资源。
// 实现多语言资源支持
server.registerResource({
name: 'localized-content/:contentId',
description: '多语言内容资源',
parameters: z.object({
contentId: z.string().describe('内容ID'),
language: z.string().default('en').describe('语言代码,如en, zh-CN, ja等'),
}),
resolve: async ({
contentId, language }) => {
const content = await fetchLocalizedContent(contentId, language);
// 如果请求的语言版本不存在,返回默认语言版本
const actualContent = content || await fetchLocalizedContent(contentId, 'en');
if (!actualContent) {
throw new Error(`内容 ${
contentId} 不存在`);
}
return {
content: actualContent.content,
metadata: {
contentId,
language: actualContent.language, // 返回实际使用的语言
availableLanguages: await getAvailableLanguages(contentId),
defaultLanguage: 'en',
translatedAt: actualContent.translatedAt,
}
};
}
});
二、资源分页与过滤
当资源数据量很大时,一次性加载所有资源会导致性能问题。资源分页和过滤机制可以帮助客户端高效地访问大型数据集。
2.1 基于游标的分页实现
MCP推荐使用基于游标的分页机制,而不是传统的基于页码的分页。基于游标的分页更加高效,特别是对于大型数据集,且能够有效处理数据集动态变化的情况。
import {
McpServer } from '@modelcontextprotocol/sdk';
import {
z } from 'zod';
const server = new McpServer({
name: 'pagination-server',
description: '分页资源示例服务器',
version: '1.0.0',
});
// 基于游标的分页实现
server.registerResource({
name: 'articles',
description: '文章列表资源',
parameters: z.object({
limit: z.number().int().min(1).max(100).default(20).describe('每页条数'),
cursor: z.string().optional().describe('分页游标'),
sortBy: z.enum(['date', 'title', 'views']).default('date').describe('排序字段'),
sortOrder: z.enum(['asc', 'desc']).default('desc').describe('排序方向'),
}),
resolve: async ({
limit, cursor, sortBy, sortOrder }) => {
// 解析游标(如果有)
let cursorData = {
offset: 0 };
if (cursor) {
try {
cursorData = JSON.parse(Buffer.from(cursor, 'base64').toString());
} catch (error) {
throw new Error('无效的分页游标');
}
}
// 查询数据
const {
articles, totalCount } = await fetchArticles({
offset: cursorData.offset,
limit,
sortBy,
sortOrder,
});
// 计算下一页游标
const nextOffset = cursorData.offset + articles.length;
const hasNextPage = nextOffset < totalCount;
const nextCursor = hasNextPage
? Buffer.from(JSON.stringify({
offset: nextOffset })).toString('base64')
: null;
return {
content: JSON.stringify(articles),
metadata: {
pagination: {
totalCount,
returnedCount: articles.length,
hasNextPage,
nextCursor,
},
sortBy,
sortOrder,
}
};
}
});
2.2 高级过滤与搜索
过滤和搜索机制允许客户端精确获取所需的资源子集,减少不必要的数据传输。
// 实现高级过滤与搜索
server.registerResource({
name: 'products',
description: '产品列表资源',
parameters: z.object({
cursor: z.string().optional().describe('分页游标'),
limit: z.number().int().min(1).max(100).default(20).describe('每页条数'),
// 高级过滤参数
category: z.string().optional().describe('产品类别'),
minPrice: z.number().optional().describe('最低价格'),
maxPrice: z.number().optional().describe('最高价格'),
inStock: z.boolean().optional().describe('是否有库存'),
tags: z.array(z.string()).optional().describe('标签列表'),
// 搜索参数
query: z.string().optional().describe('搜索关键词'),
searchFields: z.array(z.enum(['name', 'description', 'sku'])).optional().describe('搜索字段'),
}),
resolve: async (params) => {
// 构建过滤条件
const filters = [