feat(mcp): add progress bar, throttling, and input validation for MCP tool progress (#19772)

This commit is contained in:
Jasmeet Bhatia
2026-02-24 09:13:51 -08:00
committed by GitHub
parent 4efdbe9089
commit c0b76af442
16 changed files with 647 additions and 46 deletions

View File

@@ -1,16 +1,22 @@
/**
* @license
* Copyright 2025 Google LLC
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
CoreEventEmitter,
CoreEvent,
coreEvents,
type UserFeedbackPayload,
type McpProgressPayload,
} from './events.js';
vi.mock('./debugLogger.js', () => ({
debugLogger: { log: vi.fn() },
}));
describe('CoreEventEmitter', () => {
let events: CoreEventEmitter;
@@ -360,4 +366,63 @@ describe('CoreEventEmitter', () => {
expect(listener.mock.calls[0][0]).toMatchObject({ prompt: 'Consent 10' });
});
});
describe('emitMcpProgress validation', () => {
const basePayload: McpProgressPayload = {
serverName: 'test-server',
callId: 'call-1',
progressToken: 'token-1',
progress: 0,
};
let listener: ReturnType<typeof vi.fn>;
afterEach(() => {
if (listener) {
coreEvents.off(CoreEvent.McpProgress, listener);
}
});
it('rejects NaN progress', () => {
listener = vi.fn();
coreEvents.on(CoreEvent.McpProgress, listener);
coreEvents.emitMcpProgress({ ...basePayload, progress: NaN });
expect(listener).not.toHaveBeenCalled();
});
it('rejects negative progress', () => {
listener = vi.fn();
coreEvents.on(CoreEvent.McpProgress, listener);
coreEvents.emitMcpProgress({ ...basePayload, progress: -1 });
expect(listener).not.toHaveBeenCalled();
});
it('rejects Infinity progress', () => {
listener = vi.fn();
coreEvents.on(CoreEvent.McpProgress, listener);
coreEvents.emitMcpProgress({ ...basePayload, progress: Infinity });
expect(listener).not.toHaveBeenCalled();
});
it('emits valid progress payload', () => {
listener = vi.fn();
coreEvents.on(CoreEvent.McpProgress, listener);
const payload: McpProgressPayload = {
...basePayload,
progress: 5,
total: 10,
message: 'test',
};
coreEvents.emitMcpProgress(payload);
expect(listener).toHaveBeenCalledExactlyOnceWith(payload);
});
});
});

View File

@@ -13,6 +13,7 @@ import type {
TokenStorageInitializationEvent,
KeychainAvailabilityEvent,
} from '../telemetry/types.js';
import { debugLogger } from './debugLogger.js';
/**
* Defines the severity level for user-facing feedback.
@@ -353,6 +354,10 @@ export class CoreEventEmitter extends EventEmitter<CoreEvents> {
* Notifies subscribers that progress has been made on an MCP tool call.
*/
emitMcpProgress(payload: McpProgressPayload): void {
if (!Number.isFinite(payload.progress) || payload.progress < 0) {
debugLogger.log(`Invalid progress value: ${payload.progress}`);
return;
}
this.emit(CoreEvent.McpProgress, payload);
}