python-sdk icon indicating copy to clipboard operation
python-sdk copied to clipboard

how to trigger a resources_changed or listChanged

Open skunkwerk opened this issue 9 months ago • 3 comments

Hi,

The protocol specifies that there's a notification sent to clients when the list of tools or resources changes on the server-side, but I'm not sure how to trigger this. Does it happen automatically when add_resource gets called, or do I need to trigger something manually?

thanks

skunkwerk avatar May 14 '25 00:05 skunkwerk

so, (i) this capability is not mandatory - servers may or may not declare resources: {listChanged: true} when announcing their capabilities to the client in response to initialize, a la:

{
  "capabilities": {
    "experimental": {},
    "prompts": {},
    "resources": {
      "listChanged": true
    },
    "tools": {}
  },
  "serverInfo": {
    ...
  },
  ...
}

(ii) even if the server does announce this as a capability, it seems like it is a SHOULD (but not a MUST):

When the list of available resources changes, servers that declared the listChanged capability SHOULD send a notification:

source: https://modelcontextprotocol.io/specification/2025-03-26/server/resources#list-changed-notification

WRONG - I was able to wire up the notification, but I had to do it manually. [ From my current experience in Inspector, it seems like you can get resource update notifications from the server for a subscribed resource (notifications/resources/updated), but i have not seen notifications/resources/list_changed notifications appear if i add a resource.

The resources/updated notifications started coming in without me adding any custom code, once I got the subscription working. So I would expect the updated notifications to come in without any custom code, as long as the server announces that it has the capability. But, from what I can tell, (i) it's not mandatory for the server to send these notifications even if they advertise the capability; (ii) it's totally optional what the client does (if anything) if these notifications are sent.

My guess is that if the server was actually sending the notification, it would show up in Inspector, since Inspector seems at least to be showing the server-notifications that it receives. Whether or not anything is supposed to happen after that, I can't say. But it would appear to me that the server is probably not sending the notification, which is a shame, but not strictly speaking a violation of the protocol, which suggests that sending the notifications is a SHOULD and not a MUST.

That being said, I would hope the official mcp python SDK server does what it "SHOULD" do even if it's not a MUST. ]

hesreallyhim avatar May 24 '25 12:05 hesreallyhim

CORRECTION

My mistake, I was reading my code again, and I am actually sending the resource/updated manually when a subscribed resource is changed, via session.send_notification, so my reply above is not fully accurate.

hesreallyhim avatar May 24 '25 12:05 hesreallyhim

Yeah so I just added some code to send the list_updated notification to all sessions when resource list changes, and it's coming through, but I had to do it manually, and I'm keeping track of all the session objects.

In relation to #741, I am manually keeping a list of all the sessions in my server code, so that's how i achieved this, see: "Implementing a custom session registry in user code, but this is error-prone and would be better handled by the framework."

hesreallyhim avatar May 24 '25 12:05 hesreallyhim

Can anyone tell how to declare resources: {listChanged: true}while running mcp server. default i see something like this.

  "capabilities": {
    "experimental": {},
    "prompts": {
      "listChanged": false
    },
    "resources": {
      "subscribe": false,
      "listChanged": false
    },
    "tools": {
      "listChanged": false
    }
  },
  "serverInfo": {
    "name": "mcp-search",
    "version": "1.9.3"
  }
}```

salman-khandu avatar Jun 11 '25 14:06 salman-khandu

@salman-khandu i think you need to pass it in to InitializationOptions, see this snippet on python-sdk readme

async def run():
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="example",
                server_version="0.1.0",
                capabilities=server.get_capabilities(
                    notification_options=NotificationOptions(),
                    experimental_capabilities={},
                ),
            ),
        )

I'm not at my computer so double-check, i think i had to use pydantic validate base model function or some such.

hesreallyhim avatar Jun 11 '25 14:06 hesreallyhim

@hesreallyhim

@salman-khandu i think you need to pass it in to InitializationOptions, see this snippet on python-sdk readme

async def run():
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="example",
                server_version="0.1.0",
                capabilities=server.get_capabilities(
                    notification_options=NotificationOptions(),
                    experimental_capabilities={},
                ),
            ),
        )

I'm not at my computer so double-check, i think i had to use pydantic validate base model function or some such.

I use

mcp = FastMCP(
    "mcp-search",
    description="MCP server for search",
    host=os.getenv("HOST", "0.0.0.0"),
    port=os.getenv("PORT", "8050"),
)   

and then

async def main():
    
    transport = os.getenv("TRANSPORT", "sse")
    if transport == 'sse':
        # Run the MCP server with sse transport
        await mcp.run_sse_async()
    else:
        # Run the MCP server with stdio transport
        await mcp.run_stdio_async()

So i don't see options

Thank you

salman-khandu avatar Jun 11 '25 14:06 salman-khandu

you might have to create the server using a different method, in the end I had to use lowlevel server :(

hesreallyhim avatar Jun 11 '25 14:06 hesreallyhim

@hesreallyhim if i send listChanged: true then client will automatically sync tool? like let say i have add new tool and restart server

salman-khandu avatar Jun 12 '25 08:06 salman-khandu

@salman-khandu this would depend on the client, I don't think the spec says what the client is supposed to do if it gets a resource-update or a list-changed notification. E.g. here it discusses the requirements on the server for these notifications: https://modelcontextprotocol.io/specification/2025-03-26/server/tools#list-changed-notification

When the list of available tools changes, servers that declared the listChanged capability SHOULD send a notification:

but I don't know of anything saying that a client has to actually take some action in response to that notification.

If you restart the server then it'll know about the new tool because of "tools/list" but that's different than responding to a list-changed notification. You can search the spec if you want, but AFAIK the client is free to just ignore those notifications if it doesn't care.

EDIT: Sorry somehow got mixed up with tools and resources, but in either case, I don't think there are statements about what a client must or should do.

hesreallyhim avatar Jun 12 '25 21:06 hesreallyhim

If you're asking about how to do this on FastMCP you currently need to do this manually by modifying lowlevel - see https://github.com/modelcontextprotocol/python-sdk/issues/1126

This may also be addressed by https://github.com/modelcontextprotocol/python-sdk/pull/1043

felixweinberger avatar Oct 07 '25 14:10 felixweinberger