PRACTICAL GUIDE / MCP cancellation progress testing

Test MCP Cancellation and Progress Contracts

Learn MCP cancellation progress testing with request IDs, race-condition cases, monotonic updates, late events, and deterministic contract checks.

By The Testing AcademyUpdated July 25, 202621 min read
All field guides
In this guide11 sections
  1. What does MCP cancellation progress testing cover?
  2. How do request IDs and progress tokens differ?
  3. Which cancellation behaviors does the protocol require?
  4. How should progress notifications be validated?
  5. How do you build a deterministic long-running test server?
  6. How do you test cancellation before and after completion?
  7. How do you test unknown, duplicate, and malformed notifications?
  8. How should transports handle disconnects and late messages?
  9. Which race conditions deserve permanent regression tests?
  10. Prove cleanup at every terminal boundary
  11. Separate cooperative stop from business rollback
  12. Verify directional identity under concurrency
  13. Audit evidence without creating another race
  14. Check API behavior above the protocol adapter
  15. FAQ
  16. Is cancellation guaranteed to stop work?
  17. Can the initialize request be cancelled?
  18. Should a server respond after cancellation?
  19. Must progress be monotonic?
  20. Is a dropped connection a cancellation?
  21. Can either side send cancellation?
  22. Conclusion

What you will learn

  • What does MCP cancellation progress testing cover?
  • How do request IDs and progress tokens differ?
  • Which cancellation behaviors does the protocol require?
  • How should progress notifications be validated?

MCP cancellation progress testing covers optional asynchronous contracts tied to two different identifiers: request IDs for cancellation and progress tokens for updates. A complete suite checks increasing progress, unknown identifiers, duplicate and malformed notifications, cancellation races, cleanup, late responses, and the Streamable HTTP rule that an SSE disconnection is not an implicit cancellation.

The current MCP cancellation specification and progress specification define the behavior. The lifecycle, notification, JSON-RPC, and transport scenarios in src/db/seed-data/challenges/expansion-mcp-ai.ts supply generic message-shape and ordering scaffolding. They do not contain a cancellation or progress scenario, so the 2025-11-25 utility specifications remain the direct oracle for the feature-specific assertions below.

What does MCP cancellation progress testing cover?

It proves that long-running work remains correlated, observable, and controllable under races. Progress lets a request issuer observe partial advancement when the request carried a progress token. Cancellation lets that issuer ask the peer to stop an in-flight request through notifications/cancelled. Both features are optional, so implementation support and policy must be known before tests demand them. MCP does not define standard initialize capability flags for ordinary cancellation or progress. A requester asks for progress with _meta.progressToken, and the receiver may send no updates. Task-augmented cancellation is separate and is capability-negotiated.

The state model is more useful than a happy-path example:

  1. The requester sends a request with a unique request ID.
  2. It may also supply a progress token in request metadata.
  3. The receiver begins work and may emit progress for that token.
  4. The requester may send cancellation for its request ID.
  5. Work may stop, may already have completed, or may be unable to stop.
  6. The requester processes one terminal outcome and cleans up collectors.

A test should identify which peer issued the request. Either side may send requests in MCP, and either side may cancel a request it previously issued. A client cannot cancel an unrelated server-originated request by copying its ID.

This article does not cover every server lifecycle requirement. Testing MCP servers provides the wider suite, while MCP capability negotiation tests cover features that are actually advertised during initialization. Ordinary progress and cancellation tests should not wait for nonexistent capability fields.

How do request IDs and progress tokens differ?

A request ID correlates one JSON-RPC request with its response. Cancellation parameters refer to that request ID. A progress token is supplied in request metadata and correlates zero or more progress notifications with the long- running operation. It is a string or integer and must be unique among active requests.

Do not use one map for both identifiers merely because their values can look similar. They have different namespaces, lifetimes, and message fields. A request can complete without sending progress. A progress token does not replace the request ID in the response. Cancellation names the request ID, not the progress token.

TypeScript
type RequestId = string | number;
type ProgressToken = string | number;

type ActiveOperation = {
  requestId: RequestId;
  progressToken?: ProgressToken;
  state: "running" | "cancel-requested" | "completed" | "cancelled";
  lastProgress?: number;
};

class OperationRegistry {
  byRequest = new Map<RequestId, ActiveOperation>();
  byProgress = new Map<ProgressToken, ActiveOperation>();

  add(operation: ActiveOperation) {
    if (this.byRequest.has(operation.requestId)) {
      throw new Error("Duplicate active request ID");
    }
    if (
      operation.progressToken !== undefined &&
      this.byProgress.has(operation.progressToken)
    ) {
      throw new Error("Duplicate active progress token");
    }
    this.byRequest.set(operation.requestId, operation);
    if (operation.progressToken !== undefined) {
      this.byProgress.set(operation.progressToken, operation);
    }
  }
}

Test integer zero as a valid token and ID value; code that uses truthiness can accidentally treat it as absent. Also test a string token distinct from a numerically similar request ID. These cases expose registry shortcuts without requiring timing.

Resource-change subscriptions use different notification identity and lifecycle rules. Keep those cases in MCP list-changed resource subscription tests rather than generalizing every notification through the progress collector.

Which cancellation behaviors does the protocol require?

Cancellation uses a notification, so it does not receive a JSON-RPC response. The sender identifies a request it previously issued in the same direction and believes is still in progress. It may include a reason for diagnostics. Clients must not cancel the initialize request.

When a receiver accepts cancellation, it should stop the work, release associated resources, and not send a response for the cancelled request. This is cooperative behavior rather than a transaction rollback. The peer may be unable to halt an external side effect, and a cancellation notification cannot prove that a remote operation did not complete.

The receiver may ignore a cancellation when the identifier is unknown, the request already completed, or the operation cannot be cancelled. A late response can therefore cross the cancellation notification. The sender should ignore that response, while diagnostic evidence can still record it as a race.

Invalid cancellation notifications should be ignored. Do not let malformed params crash the connection or cancel a default request. Task-augmented requests use the task-specific cancellation method, so a test should not apply notifications/cancelled blindly to task lifecycle.

ScenarioReceiver behavior to exerciseSender assertion
Cancel before work startsStop scheduled work and clean resourcesNo normal response; collector closes
Cancel during workCooperatively stop at controlled boundaryNo further accepted progress
Cancel after responseMay ignore as already completeCompleted result remains terminal
Unknown request IDMay ignoreNo unrelated request changes
Duplicate cancellationIgnore safely or preserve cancelled stateNo duplicate cleanup or response
Progress without tokenDo not invent correlationRequest may complete without updates
Regressing progressSender detects contract violationLast accepted value remains unchanged
Streamable HTTP SSE disconnectDo not treat as implicit cancelApply separate reconnect or reconciliation policy

The authorization and side-effect implications extend beyond this wire contract. Architecture for secure agent tool execution is the right place to design compensation and confirmation; cancellation tests should report what happened without implying rollback.

How should progress notifications be validated?

Progress notifications are valid only for a token supplied with an active request. For each token, the numeric progress value must increase from the previous update. The optional total gives a reference amount and can be a floating-point value. The protocol does not require progress notifications to be sent at a fixed frequency or at all.

Validate these properties:

  • The token matches one active request and was supplied by the requester.
  • progress is numeric and greater than the prior accepted value.
  • Optional total is numeric and handled without assuming an integer.
  • Optional message content is treated as untrusted diagnostic text.
  • No progress is accepted after the request reaches a terminal state.
  • Collector cleanup removes the token so it cannot attach to a later request.

Do not infer percentage unless the application explicitly defines it from progress and total. Do not fail because the first value is not zero or because there are fewer updates than expected. The protocol allows arbitrary frequency. Tests should assert properties the sender promises, such as named phase transitions, only when those are part of the product contract.

TypeScript
type ProgressEvent = {
  progressToken: ProgressToken;
  progress: number;
  total?: number;
  message?: string;
};

function acceptProgress(
  registry: OperationRegistry,
  event: ProgressEvent,
): void {
  const operation = registry.byProgress.get(event.progressToken);
  if (!operation || operation.state !== "running") {
    throw new Error("Progress for inactive operation");
  }
  if (
    operation.lastProgress !== undefined &&
    event.progress <= operation.lastProgress
  ) {
    throw new Error("Progress did not increase");
  }
  operation.lastProgress = event.progress;
}

This client-side choice throws on invalid progress so tests see the violation. A production client may instead ignore and record it. Freeze that handling policy and test it consistently. The MCP schema remains the authority for wire fields.

How do you build a deterministic long-running test server?

Avoid real sleeps. Give the protocol adapter deferred checkpoints controlled by the test. The request handler starts, emits progress only when instructed, pauses before its terminal action, and observes a cancellation flag at known boundaries. This makes every interleaving reproducible.

TypeScript
type McpRequestId = string | number;
type McpProgressToken = string | number;

type RequestParams = {
  _meta?: { progressToken?: McpProgressToken };
  [key: string]: unknown;
};

type JsonRpcRequest = {
  jsonrpc: "2.0";
  id: McpRequestId;
  method: string;
  params?: RequestParams;
};

type JsonRpcNotification = {
  jsonrpc: "2.0";
  method: string;
  params?: Record<string, unknown>;
};

type JsonRpcResponse = {
  jsonrpc: "2.0";
  id: McpRequestId;
  result: Record<string, unknown>;
};

function deferred<T>() {
  let resolve!: (value: T) => void;
  const promise = new Promise<T>((done) => {
    resolve = done;
  });
  return { promise, resolve };
}

type ActiveOperation = {
  requestId: McpRequestId;
  progressToken?: McpProgressToken;
  cancelled: boolean;
};

class DeterministicMcpAdapter {
  readonly started = deferred<McpRequestId>();
  readonly continueWork = deferred<void>();
  readonly outbound: Array<JsonRpcNotification | JsonRpcResponse> = [];
  private readonly active = new Map<McpRequestId, ActiveOperation>();
  cleanupCount = 0;

  get activeCount() {
    return this.active.size;
  }

  async receive(
    message: JsonRpcRequest | JsonRpcNotification,
  ): Promise<void> {
    if (message.method === "notifications/cancelled") {
      if ("id" in message) {
        throw new Error("Cancellation must be a notification");
      }
      const requestId = message.params?.requestId;
      if (
        typeof requestId !== "string" &&
        typeof requestId !== "number"
      ) {
        return;
      }
      const operation = this.active.get(requestId);
      if (!operation) return;
      operation.cancelled = true;
      this.cleanup(requestId);
      return;
    }

    if (!("id" in message) || message.method !== "tools/call") {
      throw new Error("Fixture only accepts tools/call requests");
    }

    const operation: ActiveOperation = {
      requestId: message.id,
      progressToken: message.params?._meta?.progressToken,
      cancelled: false,
    };
    this.active.set(message.id, operation);
    this.started.resolve(message.id);
    this.emitProgress(operation, 1, 2, "started");

    await this.continueWork.promise;
    if (operation.cancelled) return;

    this.emitProgress(operation, 2, 2, "completed");
    this.outbound.push({
      jsonrpc: "2.0",
      id: message.id,
      result: {
        content: [{ type: "text", text: "done" }],
        isError: false,
      },
    });
    this.cleanup(message.id);
  }

  private emitProgress(
    operation: ActiveOperation,
    progress: number,
    total: number,
    statusMessage: string,
  ) {
    if (operation.progressToken === undefined) return;
    this.outbound.push({
      jsonrpc: "2.0",
      method: "notifications/progress",
      params: {
        progressToken: operation.progressToken,
        progress,
        total,
        message: statusMessage,
      },
    });
  }

  private cleanup(requestId: McpRequestId) {
    if (this.active.delete(requestId)) {
      this.cleanupCount += 1;
    }
  }
}

The adapter receives actual JSON-RPC request and notification shapes. A tools/call request reads _meta.progressToken, emits progress only when the token exists, and retains the request ID for the terminal response. An accepted notifications/cancelled message marks that request, cleans its registry entry, and causes the suspended handler to return without emitting a response.

Use the same adapter to test duplicate cancellation and completion races. A real transport integration can supplement the suite, but it should not be the only proof. The deferred checkpoint produces the same interleaving on a developer machine and in CI while preserving the MCP wire contract.

How do you test cancellation before and after completion?

Write the test chronology explicitly. For cancellation during work:

  1. Create controlled work and register cleanup counters.
  2. Send a request with a unique request ID and progress token.
  3. Wait for the adapter's started checkpoint.
  4. Release one progress event and verify its token and increasing value.
  5. Send notifications/cancelled for the request ID.
  6. Release the work checkpoint so cancellation can be observed.
  7. Assert no normal response and no progress after terminal cancellation.
  8. Verify registry, listener, timer, and operation cleanup exactly once.
  9. Save sanitized chronology as regression evidence.

For cancel-before-start, schedule work but send cancellation before releasing the start checkpoint. For cancel-after-completion, first capture the response and terminal cleanup, then send cancellation. The completed result must remain the only terminal outcome.

TypeScript
import { expect, test } from "vitest";

test("cancels the correlated MCP request without a response", async () => {
  const adapter = new DeterministicMcpAdapter();
  const run = adapter.receive({
    jsonrpc: "2.0",
    id: "req-7",
    method: "tools/call",
    params: {
      name: "slow_lookup",
      arguments: {},
      _meta: { progressToken: "progress-7" },
    },
  });

  await expect(adapter.started.promise).resolves.toBe("req-7");
  expect(adapter.outbound).toEqual([
    {
      jsonrpc: "2.0",
      method: "notifications/progress",
      params: {
        progressToken: "progress-7",
        progress: 1,
        total: 2,
        message: "started",
      },
    },
  ]);

  await adapter.receive({
    jsonrpc: "2.0",
    method: "notifications/cancelled",
    params: { requestId: "req-7", reason: "test cancellation" },
  });
  adapter.continueWork.resolve();
  await run;

  expect(adapter.outbound.some((message) => "id" in message)).toBe(false);
  expect(adapter.activeCount).toBe(0);
  expect(adapter.cleanupCount).toBe(1);
});

test("correlates increasing progress and the final response", async () => {
  const adapter = new DeterministicMcpAdapter();
  const run = adapter.receive({
    jsonrpc: "2.0",
    id: 8,
    method: "tools/call",
    params: {
      name: "slow_lookup",
      arguments: {},
      _meta: { progressToken: 80 },
    },
  });

  await adapter.started.promise;
  adapter.continueWork.resolve();
  await run;

  expect(adapter.outbound).toEqual([
    {
      jsonrpc: "2.0",
      method: "notifications/progress",
      params: {
        progressToken: 80,
        progress: 1,
        total: 2,
        message: "started",
      },
    },
    {
      jsonrpc: "2.0",
      method: "notifications/progress",
      params: {
        progressToken: 80,
        progress: 2,
        total: 2,
        message: "completed",
      },
    },
    {
      jsonrpc: "2.0",
      id: 8,
      result: {
        content: [{ type: "text", text: "done" }],
        isError: false,
      },
    },
  ]);
});

test("allows completion with no progress token or updates", async () => {
  const adapter = new DeterministicMcpAdapter();
  const run = adapter.receive({
    jsonrpc: "2.0",
    id: "req-no-progress",
    method: "tools/call",
    params: { name: "slow_lookup", arguments: {} },
  });

  await adapter.started.promise;
  adapter.continueWork.resolve();
  await run;

  expect(
    adapter.outbound.filter(
      (message) =>
        "method" in message &&
        message.method === "notifications/progress",
    ),
  ).toEqual([]);
  expect(adapter.outbound.at(-1)).toMatchObject({
    jsonrpc: "2.0",
    id: "req-no-progress",
    result: { isError: false },
  });
});

Add an assertion that the client does not send cancellation for initialize. Test it at the request registry or protocol adapter, not by relying on a remote server to reject a prohibited action. MCP sampling and elicitation testing uses other server-to-client interactions; the same directional ownership discipline applies.

How do you test unknown, duplicate, and malformed notifications?

Send cancellation for an ID that was never active and another that has already completed. Verify the receiver remains healthy and no active operation changes. Then send the same valid cancellation twice. Cleanup should remain idempotent: one listener removal, one resource release, and no response generated by either notification.

Malformed cases should cover missing requestId, a structurally invalid ID, extra untrusted fields, and a request ID owned by the opposite direction. The receiver should ignore invalid cancellation notifications rather than choosing a fallback target. Record the issue without reflecting attacker-controlled text into logs unsafely.

For progress, test an unknown token, reused active token, progress after completion, equal progress, decreasing progress, and a valid floating-point total. Also test a request with no token. It should complete normally without the server inventing progress.

Do not assert a JSON-RPC error response to an invalid notification. Notifications do not receive responses. A server that sends an error for malformed notification traffic creates another protocol defect. MCP testing interview questions are useful for rehearsing this request-versus-notification distinction.

How should transports handle disconnects and late messages?

The MCP transport specification places its normative disconnection rule inside Streamable HTTP handling. When an SSE stream disconnects, the client should not interpret that event as cancellation; it should send an explicit CancelledNotification when cancellation is intended and the connection permits it. The server may support stream resumption and redelivery under the separate HTTP rules.

Do not generalize that exact clause into an unstated stdio or custom-transport requirement. For stdio, process exit, stdin closure, and child ownership need an explicit application cleanup policy. A custom transport defines its own connection lifetime. Test each selected transport separately:

  • For Streamable HTTP, drop the SSE stream without cancellation and test the configured resume or reconciliation behavior.
  • For stdio, close stdin or terminate the owned child and assert the documented process and resource cleanup.
  • Never record "user cancelled" unless the application observed cancellation intent or sent the explicit notification.
  • Ignore or quarantine late messages for a locally closed operation according to the selected transport and session policy.

A late response can arrive after cancellation because completion won the race or the receiver ignored the request. The sender should ignore the late response. Still record the request ID, cancellation time, response time, and terminal decision in diagnostic evidence. Do not deliver the result to a user or agent after the local operation was cancelled.

Completed tool trajectories can later be scored using agent tool-call trace grading. A cancelled trajectory needs a lifecycle classification first; grading a late response as ordinary success would erase the contract race.

Which race conditions deserve permanent regression tests?

Keep every race that changes a terminal decision or leaks resources. High-value cases include cancellation crossing the first progress event, cancellation crossing final result serialization, duplicate cancellation during cleanup, late progress after completion, progress and response arriving back to back, token reuse before collector teardown, and disconnect between cancellation send and peer receipt.

CI should assert chronology using controlled checkpoints, not narrow sleep windows. Preserve event sequence numbers assigned by the harness, request IDs, tokens, terminal state, cleanup counters, and sanitized messages. A failed test should identify the exact illegal transition.

The repeatable gate should include:

  • Valid no-progress completion for a request without a token.
  • Increasing progress followed by normal completion.
  • Accepted cancellation with no response.
  • Completion winning the race and late cancellation being ignored.
  • Unknown and duplicate notifications leaving other work unchanged.
  • Streamable HTTP SSE disconnect not being labeled cancellation.
  • Stdio and custom-transport cleanup following their explicit local policies.
  • Cleanup after every terminal path.

Contract testing tool schema evolution covers field compatibility, while these regressions protect ordering and state. Debugging missing MCP tools remains a separate discovery concern and should not be used to explain lifecycle races.

Prove cleanup at every terminal boundary

Track resources explicitly in the adapter fixture: active operation entries, progress-token entries, abort controllers, timers, subscriptions, temporary files, and external leases. Assert the count returns to its baseline after normal completion, accepted cancellation, ignored late cancellation, handler failure, and disconnect cleanup. A correct message sequence with a retained operation is still a lifecycle defect.

Cleanup should be idempotent. Force cancellation and local timeout to race, then verify the same disposer can be reached twice without releasing another request's resource or throwing an unhandled exception. The test can keep a counter per resource and require exactly one effective release even when several terminal signals request cleanup.

Do not delete diagnostic evidence before the assertion can report it. Copy a small sanitized chronology into the result, then release live collectors and buffers. This gives failure reports request IDs, tokens, event order, and terminal decisions without retaining active handlers.

Separate cooperative stop from business rollback

Cancellation tells a peer to stop processing when possible. It does not define how an external service reverses work already completed. A long-running tool may have written one record, sent one message, or submitted one remote job before it observes cancellation. Tests should expose that uncertainty instead of asserting a universal rollback.

Model side effects as controlled checkpoints. Cancel before the checkpoint and assert no effect. Cancel after the checkpoint but before the response and assert the operation is classified for reconciliation. If the application supports compensation, test that as a separate business workflow with its own authorization and evidence.

This distinction also affects user messaging. "Request cancelled" can mean the local UI stopped waiting, not that every remote effect was undone. Tests should use language selected by product policy and ensure traces preserve whether the peer acknowledged cancellation, ignored it, or completed first.

Verify directional identity under concurrency

Use overlapping client-originated and server-originated requests with IDs that look similar. Then cancel only one client-originated request. The server- originated request and another client request must continue. This proves the registry keys include communication direction or separate ownership rather than relying on one global ID map.

Add two active requests with different IDs and progress tokens. Interleave their updates, cancel one, and let the other complete. Progress for the surviving request must remain accepted, while updates for the cancelled request after its terminal transition are ignored or classified according to policy. The test should not clear every progress collector when one operation ends.

Token reuse is valid only after the prior active lifecycle is completely removed. Start a second request with the old token immediately after cleanup and verify its first progress value establishes a new sequence. If the old lastProgress remains, a valid new update can be rejected as regression.

Audit evidence without creating another race

Assign a local sequence number when the harness observes each send, receive, state transition, and cleanup event. Monotonic times can support diagnosis, but the deterministic sequence number should drive assertions. Wall-clock timestamps from separate processes may not be comparable.

The trace should record message direction, request ID, progress token when present, sanitized reason or message, old state, new state, and decision. It should not copy complete request arguments or tool output by default. Include hashes or selected fields when payload identity matters.

Test the observer too. Delay trace export while cancellation and completion race. The protocol adapter must not wait on a slow exporter before updating its terminal state, and export failure must not resurrect work or send a response. Observability should explain the lifecycle without becoming part of its correctness.

Check API behavior above the protocol adapter

The application usually exposes cancellation through a button, timeout, workflow stop, or parent operation. Test that this control sends at most one notification for the owned active request and moves the local UI or job state to the selected terminal status. A disabled button alone is not proof that the wire notification was sent, and a sent notification is not proof that remote work stopped.

When a user cancels after completion is already visible, the application should not replace a successful result with a cancelled status. Use the deferred server to put completion and user action on either side of the terminal commit. Assert which event wins according to the product's state machine and preserve that rule as a regression.

Parent-child workflows need explicit propagation policy. Cancelling a parent may request cancellation of active child requests, but completed children and unrelated sibling operations must remain unchanged. If the application does not support propagation, test that it reports remaining work rather than claiming the whole workflow stopped.

Timeouts also need separate terminology. A local deadline can trigger an MCP cancellation notification, but the timeout event and peer acceptance are different evidence. Record both. When the notification cannot be sent because the transport is already closed, follow the disconnect and reconciliation policy instead of recording confirmed cancellation.

FAQ

Is cancellation guaranteed to stop work?

No. Cancellation is optional and cooperative, and a receiver may be unable to stop work or may ignore an unknown or completed request. Tests should prove accepted cancellation behavior, cleanup, and late-response handling without claiming the notification guarantees remote side effects did not occur.

Can the initialize request be cancelled?

No. The current cancellation specification says clients must not cancel initialize. Add a negative client test that prevents this notification from being sent, plus a defensive server case that ignores prohibited lifecycle traffic without corrupting session initialization or unrelated requests.

Should a server respond after cancellation?

When cancellation is accepted, the receiver should stop work, release resources, and not issue a response for that request. A response can still arrive because cancellation raced with completion or was ignored. The sender should ignore the late response while preserving diagnostic evidence.

Must progress be monotonic?

Yes. Progress values for a token must increase. Total is optional and may be a floating-point value, while notification frequency is chosen by the sender. Test regression, token mismatch, and post-completion updates without requiring a notification count the protocol does not promise.

Is a dropped connection a cancellation?

Not by itself in Streamable HTTP. Its SSE rules say disconnection should not be interpreted as cancellation, so send the explicit notification when possible. Do not present that clause as a universal stdio or custom-transport rule. Define and test cleanup for those transports separately, including whether remote work can continue.

Can either side send cancellation?

Yes. Either peer can cancel a request it previously issued in the same direction and believes is still in progress. The notification identifies the request ID and may include a reason. Tests must not let one side cancel the other side's request.

Conclusion

Cancellation and progress are small messages around a larger state machine. The safest tests model request IDs, progress tokens, peer direction, active state, terminal decisions, and cleanup explicitly. They also preserve the protocol's flexibility: progress may be absent, cancellation may be ignored, and a race may produce a late response.

Deterministic checkpoints make those outcomes testable without timing guesses. Once each illegal transition has a named regression, teams can change transports or long-running handlers while retaining evidence that progress, cancellation, and disconnect remain distinct contracts.

// LIVE COURSE / THE TESTING ACADEMY

AI Tester Blueprint

Master GenAI, AI Agents, MCP, RAG, CrewAI. Build 23+ real AI projects.

From the instructor behind this guide.

AI testing roles are up 180% and pay 12-22 LPA. 12+ weeks / 65+ live hrs / Sat-Sun 8:30 AM IST.

Code PROMODE / 10% offJoin the batch

The Testing Academy editorial desk

Practical QA guidance built around test evidence, production tradeoffs, and interview-ready explanations.

Published July 25, 2026 / Reviewed July 25, 2026

PRIMARY REFERENCES

Verify the details at the source

QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.

  1. 01
    Official modelcontextprotocol.io reference

    modelcontextprotocol.io

    Primary documentation selected and verified for the claims in this guide.

  2. 02
    Official modelcontextprotocol.io reference

    modelcontextprotocol.io

    Primary documentation selected and verified for the claims in this guide.

  3. 03
    Official modelcontextprotocol.io reference

    modelcontextprotocol.io

    Primary documentation selected and verified for the claims in this guide.

  4. 04
    Official modelcontextprotocol.io reference

    modelcontextprotocol.io

    Primary documentation selected and verified for the claims in this guide.

FAQ / QUICK ANSWERS

Questions testers ask

Is cancellation guaranteed to stop work?

No. Cancellation is an optional cooperative contract, and a receiver may be unable to stop work or may ignore an unknown or already completed request. Tests should prove accepted cancellation behavior, cleanup, and late-response handling without claiming that sending the notification guarantees remote side effects did not occur.

Can the initialize request be cancelled?

No. The current MCP cancellation specification says clients must not cancel the initialize request. Add a negative client test that prevents this notification from being sent, and a server-side defensive case that ignores malformed or prohibited lifecycle traffic without corrupting session initialization or unrelated requests.

Should a server respond after cancellation?

When a receiver accepts cancellation, it should stop the work, release associated resources, and not issue a response for that request. A response can still arrive because cancellation raced with completion or was ignored. The sender should ignore a late response while preserving enough evidence to diagnose the race.

Must progress be monotonic?

Yes. Progress values for a token must increase with each notification. The total is optional and can be a floating-point value, while notification frequency is chosen by the sender. Tests should reject regression, token mismatch, and progress after completion without requiring a notification count the protocol does not promise.

Is a dropped connection a cancellation?

Not by itself in Streamable HTTP. Its SSE rules say disconnection should not be interpreted as cancellation, so send the explicit cancellation notification when possible. Do not present that clause as a universal stdio or custom-transport rule. Define and test cleanup for those transports separately, including whether remote work can continue.

Can either side send cancellation?

Yes. Either peer can cancel a request it previously issued in the same communication direction and believes is still in progress. The notification identifies that request ID and may include a reason. Tests must not let one side cancel the other side's request or reuse unrelated identifiers.