Tools, resources, prompts, sampling, and elicitation carry product work. MCP utilities keep that work manageable: ping checks liveness, progress reports long operations, cancellation stops unwanted requests, pagination bounds discovery, logging exposes diagnostics, and completion helps users fill prompt or resource arguments.
Utilities are protocol contracts, not optional polish. A server that ignores cancellation, leaks unlimited lists, or mixes logs into stdio traffic may implement its business capability correctly and still fail as an MCP integration.
Each utility has its own race conditions and trust boundary. Correlate messages with request IDs or opaque tokens, validate ownership, and never treat diagnostics as user authority.
Either party can issue `ping` to check whether the other side is responsive. The receiver should answer promptly with an empty result. Ping confirms protocol responsiveness; it does not prove that a database, model provider, or downstream API is healthy.
Use a timeout that reflects the transport and product. Repeated failures may close the connection, but avoid aggressive probes that create load during an incident. Record latency and failure count without logging unrelated session content.
A request can include an opaque `progressToken` in `_meta`. The receiver may then emit `notifications/progress` with the same token, a current progress value, and optional total or message. The sender must not parse or reinterpret tokens it did not create.
Progress is advisory. It may be delayed, skipped, or arrive near completion, so application correctness cannot depend on every update being delivered. Keep values monotonic when possible and make messages safe for display.
For work that must survive disconnection and support later result retrieval, use the task contract negotiated for that protocol surface rather than treating progress notifications as durable state.
Either side can send `notifications/cancelled` for a request it previously issued in the same direction and still believes is running. The notification includes the request ID and may include a reason. Clients must not cancel initialization this way.
Cancellation is best effort and races with completion. A receiver should stop work and free resources when possible, while the sender should ignore a response that arrives after it requested cancellation. Unknown, completed, or malformed cancellation notifications are ignored.
Task-augmented work uses its task cancellation operation instead of request cancellation. Keep these paths distinct because one is fire-and-forget request control and the other changes durable task state.
List operations such as tools, resources, prompts, resource templates, and tasks where supported can return `nextCursor`. The caller sends that opaque cursor in the next request and stops when the result omits it.
A cursor is not a page number and clients must not decode or modify it. Servers should bind cursor state to the correct query and authorization context, expire it reasonably, and reject invalid cursors without exposing internal database keys.
Permission filtering happens before the page is returned. Filling a page with unauthorized items and removing them afterward can leak counts, create empty loops, and produce inconsistent cursors.
A server that advertises logging can receive `logging/setLevel` and emit `notifications/message` at or above the selected severity. Logs sent through MCP are for client-visible diagnostics; internal service logs still belong in the server logging system.
For stdio, every protocol message stays on stdout and ordinary process diagnostics go to stderr. For any transport, redact secrets, tokens, private resource content, and raw backend errors before a log reaches the client.
Use stable event names and fields for method, capability, duration, outcome, and safe reason code. Avoid free-form dumps that cannot be queried or safely shown to users.
Servers can advertise completions and answer `completion/complete` for prompt arguments or resource-template variables. The request includes the reference, current argument value, and any previously resolved arguments needed to narrow suggestions.
The response returns bounded completion values with optional total and `hasMore`. Suggestions improve entry, but the server must still validate the final prompt or resource request because a client can ignore completion entirely.
Filter completions by user authority and current context. Autocomplete that reveals project names, customer IDs, or private paths is a data leak even if the underlying read later rejects access.
Trace utility events alongside the request they support. Useful fields include request ID, progress token hash, cursor family, log threshold, cancellation reason, elapsed time, and final disposition. Do not store raw opaque handles when a one-way digest is sufficient for correlation.
A review should answer whether the peer stayed responsive, progress remained honest, cancellation reached the backend, pagination terminated, logs stayed redacted, and completion suggestions respected access control.
Ping checks protocol responsiveness; it does not prove that a database, model, or downstream API is healthy. Progress is opt-in: a request includes an opaque progress token, and notifications repeat that token with a monotonically increasing progress value. Do not infer a percentage unless a total is supplied and meaningful.
Cancellation is a notification tied to a request ID. The receiver should stop work where practical, but the sender cannot assume side effects were rolled back merely because it stopped waiting. Design idempotency and transaction boundaries for operations where cancellation can race with completion.
Logging is capability-negotiated and level-filtered. Keep protocol log notifications safe for the client, put local diagnostics on stderr for stdio, and redact credentials and private arguments. Pagination cursors are opaque continuation tokens: clients pass them back unchanged, and servers bind them to the correct user, query, and ordering.
The client carries the server cursor unchanged until no next cursor is returned.
PAGES = {
None: {"items": ["tool-a", "tool-b"], "nextCursor": "cursor-2"},
"cursor-2": {"items": ["tool-c"], "nextCursor": None},
}
cursor = None
items = []
while True:
result = PAGES[cursor]
items.extend(result["items"])
cursor = result["nextCursor"]
if cursor is None:
break
print(items)
['tool-a', 'tool-b', 'tool-c']
The client never parses `cursor-2`; it only returns the value to the same list operation.
A late result is ignored after the caller records that cancellation was requested.
requests = {"req-7": {"state": "running", "result": None}}
def cancel(request_id: str) -> None:
requests[request_id]["state"] = "cancel_requested"
def receive_result(request_id: str, value: str) -> None:
request = requests[request_id]
if request["state"] == "cancel_requested":
request["state"] = "cancelled_late_result_ignored"
return
request["state"] = "completed"
request["result"] = value
cancel("req-7")
receive_result("req-7", "unexpected late response")
print(requests["req-7"])
{'state': 'cancelled_late_result_ignored', 'result': None}
Production code also propagates cancellation and records whether downstream work could be stopped.
0 of 2 checked
Try this next
0 of 3 completed
No. Request cancellation is best effort and can race with completion. The receiver should stop work where possible and both parties must handle late messages safely.
No. Progress is an advisory update for an active request. A task is durable execution state designed for later polling and result retrieval under its negotiated contract.
Explore 500+ free tutorials across 20+ languages and frameworks.