mcp: add resource subscriptions (#138)

This CL adds the ability for clients to subscribe and receive updates
for resources as described in
https://modelcontextprotocol.io/specification/2025-06-18/server/resources#subscriptions

Fixes: #23
This commit is contained in:
Sam Thanawalla 2025-07-23 15:23:50 -04:00 • committed by GitHub
commit bcbb31f072
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -60,7 +60,7 @@ func TestEndToEnd(t *testing.T) {
// Channels to check if notification callbacks happened.
notificationChans := map[string]chan int{}
for _, name := range []string{"initialized", "roots", "tools", "prompts", "resources", "progress_server", "progress_client"} {
for _, name := range []string{"initialized", "roots", "tools", "prompts", "resources", "progress_server", "progress_client", "resource_updated", "subscribe", "unsubscribe"} {
notificationChans[name] = make(chan int, 1)
}
waitForNotification := func(t *testing.T, name string) {
@ -78,6 +78,14 @@ func TestEndToEnd(t *testing.T) {
ProgressNotificationHandler: func(context.Context, *ServerSession, *ProgressNotificationParams) {
notificationChans["progress_server"] <- 0
},
SubscribeHandler: func(context.Context, *SubscribeParams) error {
notificationChans["subscribe"] <- 0
return nil
},
UnsubscribeHandler: func(context.Context, *UnsubscribeParams) error {
notificationChans["unsubscribe"] <- 0
return nil
},
}
s := NewServer(testImpl, sopts)
AddTool(s, &Tool{
@ -128,6 +136,9 @@ func TestEndToEnd(t *testing.T) {
ProgressNotificationHandler: func(context.Context, *ClientSession, *ProgressNotificationParams) {
notificationChans["progress_client"] <- 0
},
ResourceUpdatedHandler: func(context.Context, *ClientSession, *ResourceUpdatedNotificationParams) {
notificationChans["resource_updated"] <- 0
},
}
c := NewClient(testImpl, opts)
rootAbs, err := filepath.Abs(filepath.FromSlash("testdata/files"))
@ -421,6 +432,37 @@ func TestEndToEnd(t *testing.T) {
waitForNotification(t, "progress_server")
})
t.Run("resource_subscriptions", func(t *testing.T) {
err := cs.Subscribe(ctx, &SubscribeParams{
URI: "test",
})
if err != nil {
t.Fatal(err)
}
waitForNotification(t, "subscribe")
s.ResourceUpdated(ctx, &ResourceUpdatedNotificationParams{
URI: "test",
})
waitForNotification(t, "resource_updated")
err = cs.Unsubscribe(ctx, &UnsubscribeParams{
URI: "test",
})
if err != nil {
t.Fatal(err)
}
waitForNotification(t, "unsubscribe")
// Verify the client does not receive the update after unsubscribing.
s.ResourceUpdated(ctx, &ResourceUpdatedNotificationParams{
URI: "test",
})
select {
case <-notificationChans["resource_updated"]:
t.Fatalf("resource updated after unsubscription")
case <-time.After(time.Second):
}
})
// Disconnect.
cs.Close()
clientWG.Wait()