BYOKchat Blog

How MCP Tool Discovery Works

Understand MCP tool discovery, schema freshness, server identity, cache invalidation, capability changes, security boundaries, and how clients should expose discovered tools to models.

· 8 min read

On this page
  1. Discovery is not authorization
  2. Tool identity is server-scoped
  3. A discovered tool is a contract snapshot
  4. Do not treat a description as executable policy
  5. Tool schemas should be validated before use
  6. Providers do not all accept the same schema subset
  7. Discovery can be lazy
  8. Eager
  9. Lazy
  10. Cached with refresh
  11. Cache lifetime should come from protocol metadata when available
  12. Cache scope matters for multi-account clients
  13. Tools can change while the app is open
  14. Modern change notifications are opt-in
  15. Notifications can be missed
  16. Tool removal must revoke exposure immediately
  17. Schema changes are more dangerous than tool removal
  18. Snapshot identity helps race handling
  19. Hash normalized schemas, not raw formatting
  20. Discovery should produce a model-facing subset
  21. Do not let model-facing aliases break audit identity
  22. Tool descriptions can consume significant context
  23. Discovery failures need classification
  24. Preserve the last known snapshot carefully
  25. Reauthentication can change capabilities
  26. Discovery metadata is not a security identity
  27. server/discover and tools/list solve different questions
  28. Tool annotations should be advisory unless your policy says otherwise
  29. A useful discovery state machine
  30. Refresh before sensitive execution
  31. Tool discovery should be testable without a real server
  32. Example normalized flow
  33. Where BYOKchat fits
  34. Further reading

MCP tool discovery answers a simple question:

Which tools does this server currently offer, and what contract does each tool expose?

The difficult part is everything around that answer:

  • server identity;
  • schema freshness;
  • cache invalidation;
  • tool-name collisions;
  • permission policy;
  • model-provider translation;
  • changes while the app is running.

A production client should treat discovery as dynamic metadata, not as a one-time setup step.

Discovery is not authorization

If a server returns:

search
create_issue
delete_issue

that means the server advertises those operations.

It does not mean:

the user approved all three

or:

the model may invoke them automatically

The client still decides:

  • whether the server is enabled for this chat;
  • which tools are exposed to the model;
  • which require approval;
  • which are disabled entirely.

Keep discovery and permission as separate layers.

Tool identity is server-scoped

Tool names are not globally unique.

Two servers can both return:

search

or:

send_message

The host therefore needs an identity such as:

(server_connection_id, tool_name)

not just:

tool_name

This matters for:

  • permission persistence;
  • analytics;
  • debugging;
  • UI labels;
  • result provenance.

A permission for Work GitHub / create_issue must not silently authorize Personal Tracker / create_issue.

A discovered tool is a contract snapshot

A useful normalized record might look like:

interface DiscoveredTool {
  serverID: string
  name: string
  description?: string
  inputSchema: JSONSchema
  outputSchema?: JSONSchema
  annotations?: Record<string, unknown>
  discoveredAt: Date
  sourceRevision: string
}

The exact fields vary by implementation.

The important idea is that the application knows:

  • where the tool came from;
  • which schema version/snapshot it saw;
  • when that metadata was obtained.

Do not treat a description as executable policy

Descriptions are primarily model-facing and human-facing metadata.

A description might say:

Safely deletes old temporary files.

That sentence does not prove the operation is safe.

Authorization should use application policy and normalized arguments.

Descriptions can also be malicious or misleading if the server is untrusted.

Treat them as external content.

Tool schemas should be validated before use

A server can return malformed or unexpectedly complex schemas.

A defensive client should validate what it can support before passing a tool to a provider.

Checks can include:

schema parses
supported schema dialect/features only
reasonable depth
reasonable property count
reasonable description size
no unsupported recursive structure
valid required-property references

The goal is not to rewrite arbitrary JSON Schema.

The goal is to avoid feeding invalid contracts into a model provider and then blaming the provider for the failure.

Providers do not all accept the same schema subset

This is one of the most important reasons to normalize discovery separately from provider translation.

The MCP server may expose a schema that is valid for MCP but includes constructs a specific model API does not support.

The client should have a provider-adaptation step:

Diagram illustrating the surrounding section

If translation would change semantics, fail clearly rather than silently weakening the schema.

See How to Design Good AI Tool Schemas.

Discovery can be lazy

A client does not necessarily need to query every configured server every time the app launches.

Possible strategies:

Eager

Discover all enabled servers at startup.

Pros:

  • complete tool list immediately;
  • capability errors appear early.

Cons:

  • network cost;
  • auth prompts;
  • startup latency;
  • unnecessary traffic for unused servers.

Lazy

Discover when a server becomes relevant to a chat.

Pros:

  • less startup work;
  • fewer needless auth/network operations.

Cons:

  • first-use latency;
  • capability changes surface later.

Cached with refresh

Use a previous snapshot, then refresh according to freshness rules.

This often gives the best UX if stale metadata is handled carefully.

Cache lifetime should come from protocol metadata when available

Modern MCP revisions can carry freshness metadata for list/read results.

The 2026-07-28 generation introduced cache-oriented metadata such as ttlMs and cacheScope for relevant list/resource results.

A client should respect server-provided freshness when it understands it.

Conceptually:

cache entry
  server = github-work
  operation = tools/list
  expires = discoveredAt + ttlMs
  scope = per-user / broader scope as declared

Do not invent a long-lived universal TTL if the server provides one.

Cache scope matters for multi-account clients

Suppose one MCP endpoint supports two authenticated accounts.

Account A may expose:

read_repo
create_issue

while Account B may expose:

read_repo

If the cache is keyed only by endpoint URL, the client can leak capability metadata across identities.

Cache keys may need:

server connection
credential/account identity
protocol revision
relevant server-declared cache scope

Do not use raw secrets as cache keys.

Use stable internal credential-profile identifiers.

Tools can change while the app is open

A server can add, remove, or modify tools.

Reasons include:

  • server deployment;
  • account permission changes;
  • user configuration;
  • installed integration changes;
  • feature flags;
  • organization policy.

A client should not assume discovery is immutable until restart.

Modern change notifications are opt-in

With the 2026-07-28 protocol generation, clients can use subscriptions/listen for supported change notifications instead of relying on unsolicited session notifications.

For tools, this can include tool-list changes when both sides support the relevant capability.

Conceptually:

client opens subscriptions/listen
client asks for tools-list changes
server acknowledges supported subset
server later emits tool-list-changed notification
client refreshes tools/list

The notification should normally trigger authoritative re-discovery.

Do not mutate a cached list based only on a vague “changed” signal.

Notifications can be missed

A subscription stream can drop.

The app can be suspended.

The server can restart.

Therefore:

Notifications are hints that state changed, not proof that unchanged state stayed unchanged forever.

After reconnect, revalidate authoritative discovery state according to your freshness policy.

Tool removal must revoke exposure immediately

Suppose a model turn was prepared with:

create_invoice

Then the server changes and the tool disappears.

Possible race:

T0 discover tool
T1 send tool schema to model
T2 server removes tool
T3 model proposes create_invoice
T4 client calls server

The call may fail because the tool no longer exists.

The client should surface a capability-change error, refresh discovery, and decide whether to retry the model turn.

Do not silently redirect to another similarly named tool.

Schema changes are more dangerous than tool removal

Suppose a tool changes from:

{
  "amount": 10
}

to:

{
  "amount_cents": 1000,
  "currency": "USD"
}

A stale client might send old arguments that are now invalid.

Always validate model-generated arguments against the current tool contract immediately before execution.

See How to Validate AI Tool Arguments Safely.

Snapshot identity helps race handling

A robust design can associate a tool proposal with the discovery snapshot used to expose it.

For example:

ToolProposal {
  serverID
  toolName
  discoveryRevision
  arguments
}

Before execution:

if cached/current revision differs
    revalidate against current schema

You do not need a globally standardized revision token for this; a local hash of the normalized contract can work as application state.

Hash normalized schemas, not raw formatting

If you want local change detection, raw JSON bytes may change because of formatting or property order.

Normalize first.

For example:

canonicalized schema
+ normalized description
+ annotations relevant to execution

Then hash that representation.

This avoids treating harmless serialization differences as semantic tool changes.

Discovery should produce a model-facing subset

Do not dump every tool from every server into every model request.

Problems include:

  • huge context overhead;
  • worse tool selection;
  • privacy exposure;
  • accidental access to irrelevant capabilities;
  • provider tool-count limits.

A useful pipeline is:

Diagram illustrating the surrounding section

Selection can be explicit or context-driven, but security filtering should happen before provider serialization.

Do not let model-facing aliases break audit identity

You may need to rename tools for provider compatibility or to avoid collisions.

Example:

MCP identities:
github-work / search
linear-work / search

Provider-facing names:

github_work_search
linear_work_search

That is fine as long as the host preserves the reverse mapping.

Audit records should still point to the real server/tool identity.

Tool descriptions can consume significant context

A server with many verbose tools can add thousands of tokens before the user message is even counted.

Track tool-schema cost as part of context budgeting.

Possible optimizations:

  • expose only relevant tools;
  • trim redundant descriptions only if semantics are preserved;
  • cache token estimates;
  • group rarely used servers behind explicit enablement.

Do not truncate schemas arbitrarily.

A broken contract is worse than a larger prompt.

Discovery failures need classification

tools/list can fail for different reasons:

network unreachable
auth expired
permission denied
protocol mismatch
malformed response
server bug
rate limit
unsupported operation

The UI should not reduce everything to:

No tools found

An empty list and a failed discovery request are not the same state.

Preserve the last known snapshot carefully

A stale snapshot can still be useful for UI continuity.

But distinguish:

last known tools

from:

currently verified tools

For example:

GitHub Work
Last checked: 2 hours ago
Unable to refresh: authentication expired

Do not let stale tools execute just because they are still displayed.

Reauthentication can change capabilities

After OAuth refresh or account switching, re-run discovery if capabilities can differ by authorization context.

This is especially important when a server conditionally exposes tools based on scopes or roles.

A changed access token can mean a changed tool surface.

Discovery metadata is not a security identity

Server-reported names and version strings are self-reported metadata.

Do not use a display name like:

"Official GitHub MCP"

as proof of server identity.

Trust should come from configured endpoint/authentication/TLS/application policy, not a string the remote endpoint tells you.

server/discover and tools/list solve different questions

At a high level:

server/discover -> server capabilities / metadata

while:

tools/list -> actual available tool contracts

A client may use discovery metadata to decide whether a capability is supported, but still needs the operation-specific list to know the current tools.

Do not treat one as a substitute for the other.

Tool annotations should be advisory unless your policy says otherwise

A protocol/server may expose annotations about a tool.

These can help UX and planning.

But a dangerous pattern is:

annotation says readOnly=true
therefore bypass approval

Only do that if your client explicitly trusts that annotation source for that policy and you understand the consequences.

For remote/untrusted servers, conservative defaults are safer.

A useful discovery state machine

unknown
-> loading
-> ready(fresh snapshot)
-> stale(last known snapshot)
-> refreshing
-> authRequired
-> incompatible
-> failed

This gives the UI and tool coordinator more information than a boolean hasTools.

Refresh before sensitive execution

For high-impact operations, it can be reasonable to require a fresh contract before execution.

Example:

if tool is destructive
and discovery snapshot is expired
    refresh
    revalidate arguments
    then request approval/execute

Whether to do this for every call depends on latency and threat model.

Tool discovery should be testable without a real server

Build fixtures for:

empty list
one valid tool
same name on two servers
invalid schema
schema changes between refreshes
tool removed
very large tool list
auth-dependent list
expired cache
list_changed notification
subscription disconnect
provider cannot represent schema

A fake MCP server is far more useful than depending on public services in unit tests.

Example normalized flow

Diagram illustrating the surrounding section

Notice that discovery is only one step in a larger controlled path.

Where BYOKchat fits

A multi-provider client can use MCP discovery to create one provider-neutral tool catalog, then adapt selected tools to whichever model is active in the conversation.

The client still owns:

  • per-chat server enablement;
  • per-tool permission policy;
  • collision-free tool identity;
  • discovery freshness;
  • provider compatibility filtering;
  • audit history.

That keeps tool availability dynamic without giving discovery metadata authority over execution.

Further reading

Keep reading