当前位置: 首页 > news >正文

[MCP] Changes

   n a web application, the buttons, links, and data that you see change based on the context. Whether the user is currently logged in, whether there is any data available, what role the user has, etc. MCP servers are no different. The information and tools they provide can change at any time.
MCP provides a robust system for notifying clients about changes so that UIs and agents can stay in sync with the server's current capabilities and data.
This exercise will teach you how to implement and respond to change notifications in MCP, including:
  • list_changed notifications for tools, prompts, and resources
  • Special handling for resource template list changes
  • Resource update notifications and subscriptions
  • Recommended practices for keeping clients and users up-to-date
Change notifications are essential for building responsive, real-time AI apps. They let clients update menus, toolbars, and resource lists automatically.
 

1. List Change (Tools, Prompts, and Resources)

Whenever the set of available tools, prompts, or resources changes, the server should send a list_changed notification. This tells the client to refresh its list and fetch the latest definitions.
  • Tools: If a tool is added, removed, or updated, send a notifications/tools/list_changed request.
  • Prompts: If a prompt is enabled, disabled, or updated, send a notifications/prompts/list_changed request.
  • Resources: If a static resource or resource template is registered, unregistered, or its metadata changes, send a notifications/resources/list_changed request.
Clients that support list_changed can provide a seamless, always-up-to-date experience for users, no manual refresh required!
 

Example: Enabling/Disabling a Tool in Mission Control

Suppose your space station operations console has a "Dock Module" tool that should only be available when a docking port is free. When a new port becomes available, enable the tool and send a tools/list_changed notification. If all ports are occupied, disable the tool and notify again.
// Pseudocode
if (freeDockingPorts.length > 0 && !dockModuleTool.enabled) {dockModuleTool.enable()
} else if (freeDockingPorts.length === 0 && dockModuleTool.enabled) {dockModuleTool.disable()
}
The TypeScript SDK automatically sends list_changed notifications when tools are enabled or disabled. To avoid over-sending notifications, you should check whether the tool is already enabled or disabled before enabling or disabling it.

Example: Resource List Change Notification

When new experiment logs become available in the space station's research database:
 
{"jsonrpc": "2.0","method": "notifications/resources/list_changed"
}
This tells the client that the available resources have changed, prompting it to refresh its resource list and discover new experiment logs.
 
Code: 
查看代码
export async function initializePrompts(agent: EpicMeMCP) {const suggestTagsPrompt = agent.server.registerPrompt('suggest_tags',{title: 'Suggest Tags',description: 'Suggest tags for a journal entry',argsSchema: {entryId: completable(z.string().describe('The ID of the journal entry to suggest tags for'),async (value) => {const entries = await agent.db.getEntries()return entries.map((entry) => entry.id.toString()).filter((id) => id.includes(value))},),},},async ({ entryId }) => {invariant(entryId, 'entryId is required')const entryIdNum = Number(entryId)invariant(!Number.isNaN(entryIdNum), 'entryId must be a valid number')const entry = await agent.db.getEntry(entryIdNum)invariant(entry, `entry with the ID "${entryId}" not found`)const tags = await agent.db.listTags()return {messages: [{role: 'user',content: {type: 'text',text: `
Below is my EpicMe journal entry with ID "${entryId}" and the tags I have available.Please suggest some tags to add to it. Feel free to suggest new tags I don't have yet.For each tag I approve, if it does not yet exist, create it with the EpicMe "create_tag" tool. Then add approved tags to the entry with the EpicMe "add_tag_to_entry" tool.`.trim(),},},{role: 'user',content: {type: 'resource',resource: {uri: 'epicme://tags',mimeType: 'application/json',text: JSON.stringify(tags),},},},{role: 'user',content: {type: 'resource',resource: {uri: `epicme://entries/${entryId}`,mimeType: 'application/json',text: JSON.stringify(entry),},},},],}},)async function updatePrompts() {const entries = await agent.db.getEntries()if (entries.length > 0) {if (!suggestTagsPrompt.enabled) suggestTagsPrompt.enable()} else {if (suggestTagsPrompt.enabled) suggestTagsPrompt.disable()}}agent.db.subscribe(updatePrompts)await updatePrompts()
}
 
 

2. Resource List Change (Templates and Expansions)

Resources are special because there's a difference between what the ResourceTemplate "expands" into and whether that resource template is available in the first place:
const ingredientsResource = agent.server.registerResource('ingredient',new ResourceTemplate('sandwich://ingredients/{id}', {list: async () => {// what this returns can change as a result of database changes etc.},}),{title: 'Ingredient',description: 'A single sandwich ingredient by ID',},async (uri, { id }) => {// ...},
)// this can also change as a result of user's access
ingredientsResource.enable() // or disable()
Resources in MCP can be either static (a single file, a database record) or dynamic via resource templates (e.g., a directory of files, a database table, or even a set of modules on a space station 🚀). Resource templates allow the server to expose a pattern or collection of resources that can change over time.
notifications/resources/list_changed notification (like the one in the example above) is sent when:
  • A new resource template is enabled/disabled
  • The set of expansions for a template changes (e.g., a new module docks, a new experiment log appears, or a file is added to a directory)
  • The metadata for a resource or template changes (e.g., the title of a module changes)

Let's look at an example of how this is done:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'const server = new McpServer({ name: 'DuckCollector', version: '1.0.0' },{capabilities: {resources: { listChanged: true },// ...other capabilities},},
)// Whenever the ducky or tag list changes, notify the client
function updateResourceTemplates() {// ...logic to check for changes...server.sendResourceListChanged()
}

The MCP SDK makes it easy to keep resource lists in sync. Just call sendResourceListChanged() whenever your templates expand (items are added), contract (items are removed), or change metadata.

To make this work in our app, you'll want to subscribe to changes in your database and any other dynamic sources (like video uploads). When something changes, call sendResourceListChanged() so the client can refresh its resource lists and show users the latest and greatest.

 

Code:

查看代码
export async function initializeResources(agent: EpicMeMCP) {agent.db.subscribe(() => agent.server.sendResourceListChanged())subscribeToVideoChanges(() => agent.server.sendResourceListChanged())const tagListResource = agent.server.registerResource('tags','epicme://tags',{title: 'Tags',description: 'All tags currently in the database',},async (uri) => {const tags = await agent.db.getTags()return {contents: [{mimeType: 'application/json',text: JSON.stringify(tags),uri: uri.toString(),},],}},)const tagsResource = agent.server.registerResource('tag',new ResourceTemplate('epicme://tags/{id}', {complete: {async id(value) {const tags = await agent.db.getTags()return tags.map((tag) => tag.id.toString()).filter((id) => id.includes(value))},},list: async () => {const tags = await agent.db.getTags()return {resources: tags.map((tag) => ({name: tag.name,uri: `epicme://tags/${tag.id}`,mimeType: 'application/json',})),}},}),{title: 'Tag',description: 'A single tag with the given ID',},async (uri, { id }) => {const tag = await agent.db.getTag(Number(id))invariant(tag, `Tag with ID "${id}" not found`)return {contents: [{mimeType: 'application/json',text: JSON.stringify(tag),uri: uri.toString(),},],}},)const entryResource = agent.server.registerResource('entry',new ResourceTemplate('epicme://entries/{id}', {list: undefined,complete: {async id(value) {const entries = await agent.db.getEntries()return entries.map((entry) => entry.id.toString()).filter((id) => id.includes(value))},},}),{title: 'Journal Entry',description: 'A single journal entry with the given ID',},async (uri, { id }) => {const entry = await agent.db.getEntry(Number(id))invariant(entry, `Entry with ID "${id}" not found`)return {contents: [{mimeType: 'application/json',text: JSON.stringify(entry),uri: uri.toString(),},],}},)const videoResource = agent.server.registerResource('video',new ResourceTemplate('epicme://videos/{videoId}', {complete: {async videoId(value) {const videos = await listVideos()return videos.filter((video) => video.includes(value))},},list: async () => {const videos = await listVideos()return {resources: videos.map((video) => ({name: video,uri: `epicme://videos/${video}`,mimeType: 'application/json',})),}},}),{title: 'EpicMe Videos',description: 'A single video with the given ID',},async (uri, { videoId }) => {invariant(typeof videoId === 'string', 'Video ID is required')const videoBase64 = await getVideoBase64(videoId)invariant(videoBase64, `Video with ID "${videoId}" not found`)return {contents: [{mimeType: 'video/mp4',text: videoBase64,uri: uri.toString(),},],}},)async function updateResources() {const entries = await agent.db.getEntries()const tags = await agent.db.getTags()const videos = await listVideos()if (tags.length > 0) {if (!tagListResource.enabled) tagListResource.enable()if (!tagsResource.enabled) tagsResource.enable()} else {if (tagListResource.enabled) tagListResource.disable()if (tagsResource.enabled) tagsResource.disable()}if (entries.length > 0) {if (!entryResource.enabled) entryResource.enable()} else {if (entryResource.enabled) entryResource.disable()}if (videos.length > 0) {if (!videoResource.enabled) videoResource.enable()} else {if (videoResource.enabled) videoResource.disable()}}agent.db.subscribe(updateResources)await updateResources()
}

 

3. Resource Subscriptions (Updates to Specific URIs)

While list_changed tells clients about changes in what resources are available, resource subscriptions are about changes to the content of a specific resource URI. Clients can subscribe to updates for a particular resource (e.g., a specific module or experiment log) and receive a notifications/resources/updated notification when its content changes.
  • Clients subscribe to resource URIs using the subscribe capability
  • The server tracks subscriptions and notifies only interested clients

Example: Subscribing to Module Status Updates

A client wants to stay updated on a specific space station module:
{"jsonrpc": "2.0","id": 1,"method": "resources/subscribe","params": { "uri": "spacestation://modules/habitat-alpha" }
}

When the module's status changes (e.g., a new experiment starts, or the module is undocked), the server sends:

{"jsonrpc": "2.0","method": "notifications/resources/updated","params": {"uri": "spacestation://modules/habitat-alpha","title": "Habitat Alpha Module"}
}

Subscriptions are for updates to the content of a specific resource URI, not for changes in the set of available resources. Use list_changed for the latter.

 

Here's an example:

import {SubscribeRequestSchema,UnsubscribeRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'const petSubscriptions = new Set<string>()// NOTE: the server.server is how the MCP SDK exposes the underlying server
// instance for more advanced APIs like this one.// Allow clients to subscribe to updates for a specific pet
server.server.setRequestHandler(SubscribeRequestSchema, async ({ params }) => {petSubscriptions.add(params.uri)return {} // no specific response data is needed
})// Allow clients to unsubscribe from updates
server.server.setRequestHandler(UnsubscribeRequestSchema,async ({ params }) => {petSubscriptions.delete(params.uri)return {} // no specific response data is needed},
)// When a pet's status changes, notify subscribed clients
function onPetStatusChange(petId: string, newStatus: string) {const uri = `pethotel://pets/${petId}`if (petSubscriptions.has(uri)) {server.server.notification({method: 'notifications/resources/updated',params: { uri, title: `Pet ${petId} status updated to ${newStatus}` },})}
}

 

Code:

查看代码
export async function initializeSubscriptions(agent: EpicMeMCP) {agent.server.server.setRequestHandler(SubscribeRequestSchema,async ({ params }) => {uriSubscriptions.add(params.uri)return {}},)agent.server.server.setRequestHandler(UnsubscribeRequestSchema,async ({ params }) => {uriSubscriptions.delete(params.uri)return {}},)agent.db.subscribe(async (changes) => {for (const entryId of changes.entries ?? []) {const uri = `epicme://entries/${entryId}`if (uriSubscriptions.has(uri)) {await agent.server.server.notification({method: 'notifications/resources/updated',params: { uri, title: `Entry ${entryId}` },})}}for (const tagId of changes.tags ?? []) {const uri = `epicme://tags/${tagId}`if (uriSubscriptions.has(uri)) {await agent.server.server.notification({method: 'notifications/resources/updated',params: { uri, title: `Tag ${tagId}` },})}}})subscribeToVideoChanges(async () => {const videos = await listVideos()for (const video of videos) {const uri = `epicme://videos/${video}`if (uriSubscriptions.has(uri)) {await agent.server.server.notification({method: 'notifications/resources/updated',params: { uri, title: `Video ${video}` },})}}})
}

http://www.jsqmd.com/news/308271/

相关文章:

  • 2026最新飞书推荐!数字化转型工具权威榜单发布,高效协同与智能管理双引擎驱动企业升级 深圳/广州飞书服务公司推荐
  • 2026年哪家红外压片机售后服务好?品牌推荐
  • 2026年GEO优化服务商怎么选?中小企业“可验证交付”决策指南(含对比表+合同验收清单)
  • 全国雅思网课一对一培训机构排行推荐,2026权威出国雅思课程中心学校口碑排行榜
  • 2026年企业需要建设网站哪家公司靠谱?
  • 全国雅思网课一对一培训机构排行推荐、2026权威出国雅思课程中心学校口碑排行榜
  • 革新性视频下载工具:3步搞定高清视频保存与离线观看
  • 全国雅思网课一对一培训机构排行推荐;2026权威出国雅思课程中心学校口碑排行榜
  • 2026全国最新家装品牌top10推荐!南昌等地优质家装公司权威榜单发布,资质服务双优助力打造理想家居
  • 革新鸿蒙调试体验:无缝远程真机工具破解跨地域开发难题
  • 2026健身教练培训哪里学比较好?就业保障择校关键参考
  • oii一键生成动漫,oiioii一键生成动漫,oii邀请码,oiioii邀请码2026年1月27日最新
  • 深度解析 GB/T31455.3-2025:BRT 车载智能设备开发与适配技术指南
  • 渗透测试怎么学?从零基础入门到精通,看完这一篇就够了
  • 【课程6.6】代码编写:供水管网漏损监测模块编码(压力数据解析、漏损预警)
  • STM32 CubeIDE 控制OLED显示屏
  • 颠覆认知的社保新规:开发者不可不知的权益壁垒
  • AbMole小讲堂丨UK-5099在肿瘤和线粒体研究中的实验指南
  • OpCore-Simplify:智能配置引擎驱动的系统部署自动化技术突破方案
  • 5步解锁零代码数据分析工具:从实时分析到可视化仪表板的完整指南
  • 论文写作中怎样正确插入引文文献
  • 数据工作流革命:Mage如何重塑现代数据管道自动化
  • 2026复杂地质非开挖管道坍陷修复服务商推荐榜:非开挖厂家/非开挖铺设/非开挖顶管/河道清淤泥非开挖/管道堵塞非开挖疏通/选择指南
  • 2026年继承律师推荐:基于多类资产实证,解决跨境继承与股权分割核心难题
  • 2026年智能咖啡机如何选择?靠谱品牌推荐及值得信赖产品合集
  • 2026年四川丧葬一条龙服务推荐?绵阳孝爱,专业全面丧葬用品首选
  • 2026年最新广东AIGC 培训、AI 生图培训、AIGC 网课、AI 漫剧培训及漫剧制作与培训诚意推荐:5家优质机构盘点,真人漫剧培训这样选不踩坑
  • 2026大型企业办公室商用咖啡机推荐及品牌选购指南
  • 2026全自动咖啡机选哪个牌子好 靠谱值得信赖口碑好的品牌推荐
  • 人机异质:2026年AI与人类的本质区隔与表象趋同分析