Skip to content

Commit 69a3d45

Browse files
chore: publish resource updated notification only when there are subscriptions
1 parent 4ca1565 commit 69a3d45

File tree

4 files changed

+48
-14
lines changed

4 files changed

+48
-14
lines changed

src/resources/common/exportedData.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,12 @@ export class ExportedData {
1515
private server?: Server;
1616

1717
constructor(private readonly session: Session) {
18-
this.session.exportsManager.on("export-available", (uri) => {
19-
this.server?.mcpServer.sendResourceListChanged();
20-
void this.server?.mcpServer.server.sendResourceUpdated({
21-
uri,
22-
});
23-
});
24-
this.session.exportsManager.on("export-expired", () => {
25-
this.server?.mcpServer.sendResourceListChanged();
26-
});
18+
const onExportChanged = (uri: string): void => {
19+
this.server?.sendResourceListChanged();
20+
this.server?.sendResourceUpdated(uri);
21+
};
22+
this.session.exportsManager.on("export-available", onExportChanged);
23+
this.session.exportsManager.on("export-expired", onExportChanged);
2724
}
2825

2926
public register(server: Server): void {

src/resources/resource.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ export abstract class ReactiveResource<Value, RelevantEvents extends readonly (k
8383
],
8484
});
8585

86-
private async triggerUpdate(): Promise<void> {
86+
private triggerUpdate(): void {
8787
try {
88-
await this.server?.mcpServer.server.sendResourceUpdated({ uri: this.uri });
89-
this.server?.mcpServer.sendResourceListChanged();
88+
this.server?.sendResourceListChanged();
89+
this.server?.sendResourceUpdated(this.uri);
9090
} catch (error: unknown) {
9191
this.session.logger.warning({
9292
id: LogId.resourceUpdateFailure,

src/server.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ import { Telemetry } from "./telemetry/telemetry.js";
99
import { UserConfig } from "./common/config.js";
1010
import { type ServerEvent } from "./telemetry/types.js";
1111
import { type ServerCommand } from "./telemetry/types.js";
12-
import { CallToolRequestSchema, CallToolResult } from "@modelcontextprotocol/sdk/types.js";
12+
import {
13+
CallToolRequestSchema,
14+
CallToolResult,
15+
SubscribeRequestSchema,
16+
UnsubscribeRequestSchema,
17+
} from "@modelcontextprotocol/sdk/types.js";
1318
import assert from "assert";
1419
import { ToolBase } from "./tools/tool.js";
1520

@@ -27,6 +32,7 @@ export class Server {
2732
public readonly userConfig: UserConfig;
2833
public readonly tools: ToolBase[] = [];
2934
private readonly startTime: number;
35+
private readonly subscriptions = new Set<string>();
3036

3137
constructor({ session, mcpServer, userConfig, telemetry }: ServerOptions) {
3238
this.startTime = Date.now();
@@ -42,7 +48,7 @@ export class Server {
4248
this.registerResources();
4349
await this.validateConfig();
4450

45-
this.mcpServer.server.registerCapabilities({ logging: {}, resources: { listChanged: true } });
51+
this.mcpServer.server.registerCapabilities({ logging: {}, resources: { listChanged: true, subscribe: true } });
4652

4753
// TODO: Eventually we might want to make tools reactive too instead of relying on custom logic.
4854
this.registerTools();
@@ -70,6 +76,26 @@ export class Server {
7076
return existingHandler(request, extra);
7177
});
7278

79+
this.mcpServer.server.setRequestHandler(SubscribeRequestSchema, ({ params }) => {
80+
this.subscriptions.add(params.uri);
81+
this.session.logger.debug({
82+
id: LogId.serverInitialized,
83+
context: "resources",
84+
message: `Client subscribed to resource: ${params.uri}`,
85+
});
86+
return {};
87+
});
88+
89+
this.mcpServer.server.setRequestHandler(UnsubscribeRequestSchema, ({ params }) => {
90+
this.subscriptions.delete(params.uri);
91+
this.session.logger.debug({
92+
id: LogId.serverInitialized,
93+
context: "resources",
94+
message: `Client unsubscribed from resource: ${params.uri}`,
95+
});
96+
return {};
97+
});
98+
7399
this.mcpServer.server.oninitialized = (): void => {
74100
this.session.setAgentRunner(this.mcpServer.server.getClientVersion());
75101

@@ -101,6 +127,16 @@ export class Server {
101127
await this.mcpServer.close();
102128
}
103129

130+
public sendResourceListChanged(): void {
131+
this.mcpServer.sendResourceListChanged();
132+
}
133+
134+
public sendResourceUpdated(uri: string): void {
135+
if (this.subscriptions.has(uri)) {
136+
void this.mcpServer.server.sendResourceUpdated({ uri });
137+
}
138+
}
139+
104140
/**
105141
* Emits a server event
106142
* @param command - The server command (e.g., "start", "stop", "register", "deregister")

tests/integration/helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ export function timeout(ms: number): Promise<void> {
286286
*/
287287
export function resourceChangedNotification(client: Client, uri: string): Promise<void> {
288288
return new Promise<void>((resolve) => {
289+
void client.subscribeResource({ uri });
289290
client.setNotificationHandler(ResourceUpdatedNotificationSchema, (notification) => {
290291
if (notification.params.uri === uri) {
291292
resolve();

0 commit comments

Comments
 (0)