Skip to content

Progress Notifications

Jeremy edited this page Oct 23, 2025 · 1 revision

MCP Progress Notifications

Last Updated: 2025-10-23 | Status: ✅ Implemented in v1.13.0+


Overview

Progress Notifications provide real-time updates about long-running operations, allowing MCP clients to show progress bars and status updates to users. Instead of waiting in silence for 30-60 seconds, users see exactly what's happening at each step.


The Problem They Solve

Without Progress Notifications

User: "Find all severity 1, 2, and 3 bugs for ISR4431"
[... 30 seconds of waiting with no feedback ...]
Assistant: "Here are the results..."

User Experience:

  • ❌ No feedback during long operations
  • ❌ Uncertain if system is working or frozen
  • ❌ No way to estimate completion time
  • ❌ Anxiety-inducing "black box" waiting

With Progress Notifications

User: "Find all severity 1, 2, and 3 bugs for ISR4431"
[Progress: 1/3] Searching severity 1 bugs... ⏳
[Progress: 2/3] Searching severity 2 bugs... ⏳
[Progress: 3/3] Searching severity 3 bugs... ⏳
Assistant: "Here are the results..."

User Experience:

  • ✅ Clear feedback at each step
  • ✅ Confidence system is working
  • ✅ Can estimate remaining time
  • ✅ Professional, modern UX

How Progress Notifications Work

MCP Protocol Flow

  1. Client Calls Tool with Progress Token

    {
      "method": "tools/call",
      "params": {
        "name": "multi_severity_search",
        "arguments": { "search_term": "ISR4431" },
        "_meta": {
          "progressToken": "search-123"
        }
      }
    }
  2. Server Sends Progress Updates

    {
      "jsonrpc": "2.0",
      "method": "notifications/progress",
      "params": {
        "progressToken": "search-123",
        "progress": 1,
        "total": 3
      }
    }
  3. Client Displays Progress

    • If supported: Shows progress bar or status indicator
    • If not supported: Gracefully ignores notifications

Real-World Use Cases

1. Multi-Severity Bug Searches

The Cisco Bug API requires separate calls for each severity level. Searching severities 1, 2, and 3 means 3 API calls taking 15-30 seconds total.

Without Progress:

// User sees nothing for 30 seconds
async function multiSeveritySearch() {
  const bugs1 = await searchBySeverity(1); // 10 seconds
  const bugs2 = await searchBySeverity(2); // 10 seconds
  const bugs3 = await searchBySeverity(3); // 10 seconds
  return combine(bugs1, bugs2, bugs3);
}

With Progress:

// User sees updates every 10 seconds
async function multiSeveritySearch(args, meta) {
  sendProgress(meta?.progressToken, 0, 3, "Searching severity 1...");
  const bugs1 = await searchBySeverity(1);

  sendProgress(meta?.progressToken, 1, 3, "Searching severity 2...");
  const bugs2 = await searchBySeverity(2);

  sendProgress(meta?.progressToken, 2, 3, "Searching severity 3...");
  const bugs3 = await searchBySeverity(3);

  return combine(bugs1, bugs2, bugs3);
}

Visual Feedback:

[Progress: 0/3] Searching severity 1...
[Progress: 1/3] Searching severity 2...
[Progress: 2/3] Searching severity 3...
Complete! Found 45 total bugs.

2. Comprehensive Analysis

The comprehensive_analysis tool runs 5+ separate operations that can take 60+ seconds:

async function comprehensiveAnalysis(args, meta) {
  sendProgress(meta?.progressToken, 1, 5, "1/5: Searching bugs...");
  const bugs = await searchBugs(args.product, args.version);

  sendProgress(meta?.progressToken, 2, 5, "2/5: Checking security advisories...");
  const advisories = await checkAdvisories(args.product);

  sendProgress(meta?.progressToken, 3, 5, "3/5: Analyzing CVEs...");
  const cves = await analyzeCVEs(args.product, args.version);

  sendProgress(meta?.progressToken, 4, 5, "4/5: Checking EoL status...");
  const eol = await checkEoL(args.product);

  sendProgress(meta?.progressToken, 5, 5, "5/5: Generating report...");
  return generateReport(bugs, advisories, cves, eol);
}

Visual Timeline:

[Progress: 1/5] 1/5: Searching bugs...          (12s)
[Progress: 2/5] 2/5: Checking security advisories... (8s)
[Progress: 3/5] 3/5: Analyzing CVEs...          (15s)
[Progress: 4/5] 4/5: Checking EoL status...     (10s)
[Progress: 5/5] 5/5: Generating report...       (5s)
Complete! Analysis ready.

3. Software Version Comparison

The compare_software_versions tool runs 6+ comparisons:

async function compareVersions(productId, versionA, versionB, meta) {
  sendProgress(meta?.progressToken, 1, 6, "1/6: Searching bugs in version A...");
  const bugsA = await searchBugs(productId, versionA);

  sendProgress(meta?.progressToken, 2, 6, "2/6: Searching bugs in version B...");
  const bugsB = await searchBugs(productId, versionB);

  sendProgress(meta?.progressToken, 3, 6, "3/6: Checking CVEs in version A...");
  const cvesA = await checkCVEs(productId, versionA);

  sendProgress(meta?.progressToken, 4, 6, "4/6: Checking CVEs in version B...");
  const cvesB = await checkCVEs(productId, versionB);

  sendProgress(meta?.progressToken, 5, 6, "5/6: Analyzing differences...");
  const diff = analyzeDifferences(bugsA, bugsB, cvesA, cvesB);

  sendProgress(meta?.progressToken, 6, 6, "6/6: Generating recommendations...");
  return generateRecommendations(diff);
}

4. Progressive Bug Search with Fallbacks

The progressive_bug_search tool tries multiple search strategies:

async function progressiveBugSearch(args, meta) {
  sendProgress(meta?.progressToken, 1, 4, "1/4: Trying product ID search...");
  let results = await tryProductIdSearch(args);
  if (results) return results;

  sendProgress(meta?.progressToken, 2, 4, "2/4: Trying product series search...");
  results = await tryProductSeriesSearch(args);
  if (results) return results;

  sendProgress(meta?.progressToken, 3, 4, "3/4: Trying keyword search...");
  results = await tryKeywordSearch(args);
  if (results) return results;

  sendProgress(meta?.progressToken, 4, 4, "4/4: Broadening search criteria...");
  return await tryBroadSearch(args);
}

Technical Implementation

Server-Side Code

// Import progress notification support
import { ProgressNotificationSchema } from '@modelcontextprotocol/sdk/types.js';

// Helper function to send progress
function sendProgress(
  progressToken: string | undefined,
  progress: number,
  total: number,
  message?: string
) {
  if (!progressToken) return; // Client didn't request progress

  mcpServer.notification({
    method: 'notifications/progress',
    params: {
      progressToken,
      progress,
      total
    }
  });

  // Log for debugging
  logger.info('Progress notification sent', {
    progressToken,
    progress,
    total,
    percentage: Math.round((progress / total) * 100)
  });
}

// Tool implementation with progress
async function myLongRunningTool(args: ToolArgs, meta?: ToolMeta) {
  const steps = ['Step 1', 'Step 2', 'Step 3'];
  const results = [];

  for (let i = 0; i < steps.length; i++) {
    sendProgress(meta?.progressToken, i, steps.length, steps[i]);
    results.push(await performStep(steps[i]));
  }

  return results;
}

Client-Side (Example)

// Client generates unique progress token
const progressToken = `search-${Date.now()}`;

// Client listens for progress notifications
client.on('notifications/progress', (notification) => {
  const { progressToken, progress, total } = notification.params;

  // Update UI with progress
  updateProgressBar(progress, total);
  console.log(`Progress: ${progress}/${total} (${Math.round(progress/total*100)}%)`);
});

// Client calls tool with progress token
const result = await client.callTool('multi_severity_search', {
  search_term: 'ISR4431'
}, {
  progressToken
});

Client Support Status

Claude Desktop

Current Status (October 2025):

  • ⚠️ Unknown - Progress notification support not publicly documented
  • Graceful degradation - If not supported, notifications are safely ignored
  • Future-proof - When support is added, it will work automatically

Potential Display Options:

  • Status text: "Searching severity 2 bugs... (2/3)"
  • Progress bar: [████████░░] 66%
  • Spinner with message: ⏳ "Checking CVEs..."

Other MCP Clients

Client Progress Support Display Method
MCP Inspector ✅ Likely Debug panel with progress
VS Code MCP ✅ Possible Status bar progress
Custom Clients ✅ Customizable Developer's choice

Benefits

1. Better User Experience

Before Progress Notifications:

  • ❌ "Is it working or frozen?"
  • ❌ "How much longer will this take?"
  • ❌ "Should I cancel and try again?"

With Progress Notifications:

  • ✅ "It's working on step 2 of 5"
  • ✅ "About 60% complete"
  • ✅ "Just one more step to go"

2. Debugging & Transparency

Identify Bottlenecks:

[Progress: 1/5] 1/5: Searching bugs... (2s) ✅
[Progress: 2/5] 2/5: Checking advisories... (25s) ⚠️ SLOW
[Progress: 3/5] 3/5: Analyzing CVEs... (3s) ✅

Better Error Reporting:

[Progress: 1/3] Searching severity 1... ✅
[Progress: 2/3] Searching severity 2... ✅
[Progress: 2/3] Searching severity 3... ❌ ERROR
Error: API timeout during severity 3 search

3. Professional Polish

  • ✅ Modern UX pattern used by all professional applications
  • ✅ MCP specification compliance
  • ✅ Shows attention to detail and user experience
  • ✅ Differentiates from basic implementations

Tools with Progress Support

Currently Implemented

Tool Steps Typical Duration Progress Updates
multi_severity_search 3-6 15-60s Per severity level
comprehensive_analysis 5 30-90s Per analysis step
compare_software_versions 6 40-120s Per comparison step
progressive_bug_search 2-4 10-40s Per search strategy

Future Candidates

Tool Steps Why Progress Helps
Bulk product analysis 100+ Processing many items
Multi-product upgrade planning 10+ Complex workflow
Historical bug trend analysis 20+ Multiple time periods

Implementation Details

Progress Notification Schema

interface ProgressNotification {
  method: 'notifications/progress';
  params: {
    progressToken: string;  // Unique identifier from client
    progress: number;       // Current progress (0-based)
    total: number;          // Total steps
  };
}

Best Practices

1. Progress Token is Optional

// Always check if progressToken exists
if (meta?.progressToken) {
  sendProgress(meta.progressToken, 1, 3);
}

2. Start at 0, End Before Total

// Correct: 0, 1, 2 for total of 3
sendProgress(token, 0, 3); // Step 1 starting
sendProgress(token, 1, 3); // Step 2 starting
sendProgress(token, 2, 3); // Step 3 starting

// Don't send progress === total (that's 100% complete)

3. Update After Each Major Step

// Good: Update after completing work
await doWork();
sendProgress(token, 1, 3);

// Bad: Update before work completes
sendProgress(token, 1, 3);
await doWork(); // What if this fails?

4. Use Meaningful Step Counts

// Good: Accurate step count
sendProgress(token, 2, 5); // 5 actual steps

// Bad: Arbitrary percentages
sendProgress(token, 50, 100); // Not real steps

Backward Compatibility

Graceful Degradation

Old clients (no progress support):

  • Receive notifications, ignore them
  • Tools work exactly as before
  • No breaking changes

New clients (with progress support):

  • Display progress updates
  • Enhanced user experience
  • Same tool behavior

Testing Both Modes

// Test without progress token (old client)
const result1 = await callTool('multi_severity_search', args);

// Test with progress token (new client)
const result2 = await callTool('multi_severity_search', args, {
  progressToken: 'test-123'
});

// Both should return identical results
assert.deepEqual(result1, result2);

Troubleshooting

Progress Not Showing

Problem: Client calls tool but doesn't see progress

Possible Causes:

  1. Client doesn't support progress notifications
  2. Client didn't provide progressToken
  3. Server logs not showing progress notifications

Solutions:

# Check server logs for progress notifications
tail -f logs/mcp-server.log | grep "Progress notification"

# Verify client sends progressToken
# Should see in tool call: "_meta": {"progressToken": "..."}

# Test with MCP Inspector (likely supports progress)

Progress Out of Sync

Problem: Progress shows "3/3" but still running

Cause: Incorrect step counting

Solution:

// Wrong: Counts don't match
const steps = [1, 2, 3];
for (let i = 0; i < steps.length; i++) {
  sendProgress(token, i, 4); // Total is 4 but only 3 steps!
}

// Correct: Match total to actual steps
const steps = [1, 2, 3];
for (let i = 0; i < steps.length; i++) {
  sendProgress(token, i, steps.length);
}

Progress Too Granular

Problem: Hundreds of progress updates

Solution: Only update for major steps

// Bad: Too many updates
for (let i = 0; i < 1000; i++) {
  sendProgress(token, i, 1000); // Too granular!
  await processItem(i);
}

// Good: Group into meaningful steps
const batchSize = 100;
for (let i = 0; i < 1000; i += batchSize) {
  sendProgress(token, i / batchSize, 10);
  await processBatch(i, i + batchSize);
}

Future Enhancements

Optional Message Parameter

The MCP spec allows an optional message:

interface ProgressNotification {
  progressToken: string;
  progress: number;
  total: number;
  message?: string; // Future: descriptive text
}

Potential Implementation:

sendProgress(token, 1, 3, "Searching severity 2 bugs...");

Client Display:

[████████░░] 66% - Searching severity 2 bugs...

Cancellation Support

Combined with progress, cancellation would allow:

// User sees progress, decides to cancel
[Progress: 2/5] Searching bugs... (taking too long)
 User clicks "Cancel"
 Server receives cancellation signal
 Tool stops gracefully

Performance Impact

Minimal Overhead

// Progress notification is async and non-blocking
sendProgress(token, 1, 3); // ~1ms overhead
await longOperation();     // Main work (10000ms)

Performance Analysis:

  • Progress notification: ~1ms per update
  • Network overhead: ~50 bytes per notification
  • For 5 steps: ~5ms total overhead (negligible)

No Impact on Results

Progress notifications are:

  • ✅ Fire-and-forget (async)
  • ✅ Non-blocking
  • ✅ Don't affect tool return values
  • ✅ Safe to ignore by clients

References


Related Pages


Need Help? Report issues at https://github.com/sieteunoseis/mcp-cisco-support/issues

Clone this wiki locally