Skip to content

Add support for message field in progress notifications #502

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions src/server/mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,99 @@ describe("McpServer", () => {
}
])
});

/***
* Test: Progress Notification with Message Field
*/
test("should send progress notifications with message field", async () => {
const mcpServer = new McpServer(
{
name: "test server",
version: "1.0",
}
);

// Create a tool that sends progress updates
mcpServer.tool(
"long-operation",
"A long running operation with progress updates",
{
steps: z.number().min(1).describe("Number of steps to perform"),
},
async ({ steps }, { sendNotification, _meta }) => {
const progressToken = _meta?.progressToken;

if (progressToken) {
// Send progress notification for each step
for (let i = 1; i <= steps; i++) {
await sendNotification({
method: "notifications/progress",
params: {
progressToken,
progress: i,
total: steps,
message: `Completed step ${i} of ${steps}`,
},
});
}
}

return { content: [{ type: "text" as const, text: `Operation completed with ${steps} steps` }] };
}
);

const progressUpdates: Array<{ progress: number, total?: number, message?: string }> = [];

const client = new Client({
name: "test client",
version: "1.0",
});

const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

await Promise.all([
client.connect(clientTransport),
mcpServer.server.connect(serverTransport),
]);

// Call the tool with progress tracking
await client.request(
{
method: "tools/call",
params: {
name: "long-operation",
arguments: { steps: 3 },
_meta: {
progressToken: "progress-test-1"
}
}
},
CallToolResultSchema,
{
onprogress: (progress) => {
progressUpdates.push(progress);
}
}
);

// Verify progress notifications were received with message field
expect(progressUpdates).toHaveLength(3);
expect(progressUpdates[0]).toMatchObject({
progress: 1,
total: 3,
message: "Completed step 1 of 3",
});
expect(progressUpdates[1]).toMatchObject({
progress: 2,
total: 3,
message: "Completed step 2 of 3",
});
expect(progressUpdates[2]).toMatchObject({
progress: 3,
total: 3,
message: "Completed step 3 of 3",
});
});
});

describe("ResourceTemplate", () => {
Expand Down
68 changes: 68 additions & 0 deletions src/shared/protocol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,74 @@ describe("protocol tests", () => {
await Promise.resolve();
await expect(requestPromise).resolves.toEqual({ result: "success" });
});

test("should handle progress notifications with message field", async () => {
await protocol.connect(transport);
const request = { method: "example", params: {} };
const mockSchema: ZodType<{ result: string }> = z.object({
result: z.string(),
});
const onProgressMock = jest.fn();

const requestPromise = protocol.request(request, mockSchema, {
timeout: 1000,
onprogress: onProgressMock,
});

jest.advanceTimersByTime(200);

if (transport.onmessage) {
transport.onmessage({
jsonrpc: "2.0",
method: "notifications/progress",
params: {
progressToken: 0,
progress: 25,
total: 100,
message: "Initializing process...",
},
});
}
await Promise.resolve();

expect(onProgressMock).toHaveBeenCalledWith({
progress: 25,
total: 100,
message: "Initializing process...",
});

jest.advanceTimersByTime(200);

if (transport.onmessage) {
transport.onmessage({
jsonrpc: "2.0",
method: "notifications/progress",
params: {
progressToken: 0,
progress: 75,
total: 100,
message: "Processing data...",
},
});
}
await Promise.resolve();

expect(onProgressMock).toHaveBeenCalledWith({
progress: 75,
total: 100,
message: "Processing data...",
});

if (transport.onmessage) {
transport.onmessage({
jsonrpc: "2.0",
id: 0,
result: { result: "success" },
});
}
await Promise.resolve();
await expect(requestPromise).resolves.toEqual({ result: "success" });
});
});
});

Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,10 @@ export const ProgressSchema = z
* Total number of items to process (or total progress required), if known.
*/
total: z.optional(z.number()),
/**
* An optional message describing the current progress.
*/
message: z.optional(z.string()),
})
.passthrough();

Expand Down