Does an MCP server rebuild reset an agent session?
Define when an MCP server rebuild must refresh tool metadata, replace executable identity, and invalidate agent authorization during a live session.

A local MCP server rebuild is not one event. It can change the tool catalog the agent sees, replace the code that handles calls, or alter the authority that a human approved. Treating those changes as one generic "refresh" produces the two failures that matter most: agents call tools with stale schemas, or fresh code inherits permission granted to old code.
The practical rule is simple: refresh metadata when the advertised contract changes, replace executable identity when the code that can run changes, and reset authorization when the approved authority changes. Those three actions often happen together after a rebuild, but they do not mean the same thing. A host that separates them can keep a long agent task moving without quietly extending trust.
A rebuild has three separate effects
A rebuild can change metadata, executable identity, and authorization state independently. Make each one explicit in your architecture, because a tool list refresh cannot prove that the executable stayed the same, and an approval dialog cannot tell an agent that its cached schema is stale.
Tool metadata is the description exposed through tools/list: tool names, descriptions, input schemas, output schemas when supplied, and annotations. This is what the agent and host use to decide whether a call is available and how to form it.
Executable identity answers a different question: what code will receive this call now? For a local server, the answer may include a compiled binary, an interpreted entry file, a lockfile, a runtime version, a container image digest, or a supervisor configuration. A display name such as payments-dev is not an identity. Neither is a path such as /Users/dev/work/payments/dist/server.js.
Authorization state records what someone approved and for whom. A human may approve an agent process for one run, approve a tool call individually, or approve a server connection. Each model needs a clear scope. If the approved subject changes, the approval should not drift along just because the tool name did not.
Teams blur these boundaries because a development loop often looks like one action:
- Edit a handler.
- Build or save.
- Restart a local server.
- Continue the agent session.
That loop changes more than one thing. A handler can gain a side effect without changing its name or schema. A schema can add an environment field without changing any executable bytes yet. A supervisor can replace a child process while the parent retains an open stdio connection. If you call all of that "hot reload," you have no dependable rule for what the host must invalidate.
Use three words in code and in operational discussions: catalog revision, execution epoch, and approval scope. A catalog revision describes what tools claim to be. An execution epoch identifies the code currently able to act. Approval scope describes the exact subject and duration of a grant. The names do not matter much. Keeping the concepts apart does.
Metadata must refresh when the advertised contract changes
Refresh tool metadata whenever a rebuild changes anything the client could use to select, compose, display, or constrain a call. That includes adding or removing a tool, but the difficult cases are changes to an existing tool.
The MCP Tools specification gives servers notifications/tools/list_changed for notifying clients that their tool list changed. It is useful, but it is deliberately small: the notification says that the list changed, not what changed and not whether the client has already fetched it. A server that emits it should expect the client to issue tools/list again. A client should not assume every server or host reacts instantly, especially across client implementations that cache aggressively.
Refresh when any of these fields changes:
- A tool name is added, removed, or renamed.
- The description changes in a way that alters intended use or side effects.
- The input schema changes, including defaults, enum values, required fields, bounds, and object shape.
- An output schema changes and the agent uses that result to make its next decision.
- An annotation, title, or presentation field changes in a way that affects host review.
The last item needs judgment. The MCP schema describes annotations as hints, and the specification warns clients not to make security decisions from annotations received from an untrusted server. That warning is correct. A readOnlyHint can help a host present a call, but it cannot convert a write into a read. If a local server changes readOnlyHint from true to false, refresh the catalog so the human view stays honest. Do not let the field decide whether the call receives credentials.
A schema change deserves more care than many teams give it. Consider a tool that began with this contract:
{
"name": "publish_preview",
"inputSchema": {
"type": "object",
"required": ["branch"],
"properties": {
"branch": { "type": "string" }
},
"additionalProperties": false
}
}
A developer rebuilds it and adds this optional field:
"target": {
"type": "string",
"enum": ["preview", "production"],
"default": "preview"
}
That can appear harmless. Yet the agent may have cached the first schema, the host may render an approval card without the target, and the implementation may have a bug that treats an omitted value as production. The right response is not merely to accept the extra field. Refresh the catalog, make the default visible in the review surface, and test an omitted argument against the running code.
A metadata refresh is enough when the executable has not changed and the existing approval scope remains valid. That happens when a server generates tools from remote data and publishes a new list while the same code continues to run. It also happens when a host corrects documentation outside the server process. Do not restart a session just to correct a typo in a description.
But do not use metadata refresh as a substitute for an execution boundary. It tells the agent what the server says it can do. It does not tell you what the server will actually do.
A changed executable needs a new identity
Replace server identity whenever a new code image, runtime configuration, or dependency set can handle calls. This includes a restarted binary, a reloaded JavaScript module, a new container image, a changed interpreter environment, and a changed wrapper script that dispatches to another program.
The error I see most often is binding trust to a command line. A host stores something like this:
server = "inventory"
command = "node"
args = ["/work/inventory/server.js"]
Then it assumes the server remains the same until the configuration changes. It does not. The node process can load a different server.js after a rebuild. That file can resolve a different dependency tree. The file can even stay byte-for-byte identical while a native addon, environment variable, or shell wrapper sends execution somewhere else.
You do not need a perfect universal fingerprint to improve this. You need an identity with a stated scope and a conservative invalidation rule. For a local development server, create an execution record at launch time:
{
"serverLabel": "inventory-local",
"launchCommand": ["node", "/work/inventory/dist/server.js"],
"entryDigest": "sha256:9e4c...71af",
"lockfileDigest": "sha256:344b...0d19",
"runtime": "node 22.14.0",
"workingDirectory": "/work/inventory",
"epoch": "01JQ7R4S4S0QJ7GZP1S2",
"processId": 48192
}
The digests prevent a path from posing as identity. The runtime and working directory explain how the host resolved the entry point. The epoch gives every restart a unique handle, even if the rebuilt artifact happens to produce the same digest. The process ID helps an operator investigate, but it is not an identity because operating systems reuse it.
For a compiled server, hash the actual executable after the build finishes. For a script server, hash the entry point and the dependency lockfile at minimum. If your runtime loads code outside that lockfile, include the resolved package tree or run a packaged artifact. If a shell script launches the real server, hash the script and the child artifact. An identity that ignores the dispatcher only proves the wrong program stayed unchanged.
On macOS, this basic check gives a developer a repeatable record before starting a local server:
shasum -a 256 dist/server.js package-lock.json
Typical output has one digest and one path on each line:
9e4c1b2d8f3a6d...71af dist/server.js
344bb81b6c09de...0d19 package-lock.json
Do not calculate this digest after the agent has already resumed work. Capture it at process launch, attach it to the server epoch, and record it beside every authorization decision. Otherwise an audit trail can prove only that some file eventually existed.
A rebuild that changes code but preserves the same tool catalog still needs an identity refresh. Imagine get_invoice retains its name, schema, description, and read-only annotation. The rebuilt handler now sends each invoice number to an external debugging endpoint before returning the same invoice. Metadata did not move. Authority did.
The reverse can also happen. A running server might publish a different set of tenant-specific tools based on an updated configuration while its executable identity stays fixed. Refresh metadata, keep the epoch, and decide authorization based on whether the new catalog crosses the approved scope.
Authorization must follow the execution epoch
Invalidate authorization when the server execution epoch changes, unless the authorization explicitly covers a trusted publisher and a defined update channel. For local rebuilt servers, that exception is usually more trouble than it is worth.
People often argue for carrying approval across rebuilds because development becomes irritating otherwise. The argument has a fair point: requiring someone to approve each file save makes the control useless. The answer is not to make approval immortal. Scope it to the right unit.
A practical approval grant can bind these fields:
{
"agentRun": "run_01JQ7R1",
"serverLabel": "inventory-local",
"executionEpoch": "01JQ7R4S4S0QJ7GZP1S2",
"toolScope": ["inventory_lookup", "inventory_adjust"],
"credentialScope": ["inventory-api-staging"],
"issuedAt": "2026-07-22T14:31:08Z",
"expiresWhen": "agent-run-ends"
}
This grant says something a reviewer can understand: this agent run may use these tools through this exact server instance with this named credential scope. A restarted server gets a new epoch. The host rejects the old grant before it injects a credential, and it requests fresh approval if the call still needs authority.
Do not bind the grant to tool names alone. Tool names are an interface convention. A rebuild can turn inventory_adjust from "change a staging count" into "call a production endpoint selected by an environment variable." If the agent keeps calling the same name, the host still needs to notice that the executable handling the name changed.
The same rule applies to an agent-side wrapper. If the agent starts a local MCP server through a launcher that rebuilds or rewrites itself, the launcher belongs in the identity record. A malicious or broken wrapper can preserve every user-facing tool name while redirecting calls to another program.
There are cases where an approval can survive a code update, but they need more structure than a local build usually has. For example, an organization may approve artifacts signed by a named publisher, restricted to a deployment channel, with a verified version policy, fixed credential scope, and a separate review process for permission changes. That is release management. Do not pretend a file watcher and an unpinned development directory provide the same assurance.
Per-call approval changes the tradeoff. If a credential or tool requires confirmation on every use, a rebuild still must create a new identity for audit purposes, but the immediate action has a fresh human decision. That does not erase the need to refresh schemas. It does limit the damage from a stale session approval.
Sallyport takes a related approach for agent actions: its vault gate denies actions while locked, its per-session authorization identifies a newly connected agent process, and individual credential settings can require approval on every use. The important design lesson is that a human decision needs a clear subject and an end point, not a vague promise that a familiar label is still safe.
Stdio hides process replacement behind one pipe
A stdio MCP connection makes rebuild behavior especially deceptive because the connection belongs to processes, not files. Rebuilding a file does nothing to a running child process until something replaces or reloads that process.
In the simple case, an MCP host launches a child server and holds its standard input and output pipes. The child loaded its code at startup. A developer runs the build again, but the existing child remains in memory. The agent is still talking to the old implementation, even though the directory now contains new artifacts.
That case needs no protocol refresh. The actual executable identity has not changed. The mistake is telling developers that their rebuild took effect when it did not. Your development tooling should print an unambiguous status line such as:
build complete: dist/server.js changed
running server unchanged: pid=48192 epoch=01JQ7R4S4S0QJ7GZP1S2
The harder case uses a watcher. A parent process owns the stdio pipe, watches files, kills its worker, and starts a new worker. The parent can keep the pipe alive while calls silently begin reaching the new child. From the MCP client's view, the connection never closed. From the security reviewer's view, the executable identity changed underneath an existing session.
Do not allow that swap to remain invisible. Choose one of these designs:
- Close the MCP connection when the child restarts, forcing the host to reconnect, initialize, and authorize the new instance.
- Keep the outer connection but make the supervisor publish a new execution epoch to the host before it forwards any new call.
- Avoid in-process reload during development and restart the entire server process under host control.
The first design is clearest. The second can preserve long-running agent context, but it requires a trustworthy boundary between the supervisor and the host. The third costs a few seconds and saves weeks of explaining why a session approval covered unknown code.
Do not rely on a server's own notification to attest to its replacement. The new code can lie, and a compromised server has every reason to announce the same identity. The process manager, host, or credential gateway should observe the launch and create the epoch. If those layers cannot observe a restart, they cannot safely distinguish a rebuild from a stable server.
Tool-list notifications are a hint, not a handoff
notifications/tools/list_changed should prompt a catalog fetch, but it does not restart a session, renegotiate capabilities, or carry an authorization decision. Build your control flow around what the notification actually says.
The MCP Lifecycle specification describes initialization as the phase where client and server negotiate protocol version and capabilities. After that, normal operation begins. A server advertising tools.listChanged says it can notify the client that its tool list changed. It does not say that the server can rewrite its own identity mid-session without consequences, and it does not require a host to treat the notification as a security attestation.
That distinction matters when you design a reload path. A weak implementation does this:
watcher rebuilds server
server sends tools/list_changed
client fetches tools/list
agent continues
It succeeds in a demo. It fails to answer four operational questions:
- Did the existing process load the rebuilt code?
- Did a different process take over the connection?
- Does the new code have the same approved authority?
- Did the host discard a call the agent prepared using the old schema?
A stronger path assigns responsibilities to separate layers. The build system reports artifacts. The supervisor reports process replacement. The MCP server reports catalog changes. The host refreshes agent-visible metadata. The authorization layer compares the execution epoch with the grant. The audit log records each transition.
If the host receives a list-change notification and later discovers an epoch change, it should process the epoch change first. Mark any cached tool catalog as suspect, block credentialed calls until it has the current catalog and an authorization decision, then resume. The agent can still keep its conversation history. It simply cannot assume that a tool invocation composed before the rebuild remains valid.
The same caution applies to capability changes. If a rebuild adds resources, prompts, logging behavior, or an experimental extension, a legacy initialized session may not have negotiated those features. Reconnect rather than trying to mutate the negotiated connection in place. Long-lived sessions are convenient, but a connection contract must stay understandable when something goes wrong at 2 a.m.
Write a refresh contract before you add hot reload
A refresh contract should state who detects a rebuild, which state changes it creates, which calls pause, and what evidence enters the audit log. If this is not written down, each component will make a locally reasonable choice and the combined behavior will be unsafe.
Use a small state machine. It does not need a policy language or a maze of rules.
ready(epoch A, catalog 12, approval A)
build artifact changes
ready(epoch A, catalog 12, approval A)
worker restarts
identity-pending(epoch B, catalog unknown, approval A invalid)
host fetches tools/list
catalog-ready(epoch B, catalog 13, approval A invalid)
reviewer approves required scope
ready(epoch B, catalog 13, approval B)
The important transition is identity-pending. In that state, the host must not pass a credentialed call through merely because the agent already prepared it. It can allow harmless discovery requests if you have a clear definition of harmless, but do not guess. For most local servers, pausing all tool calls until the catalog and grant are current is simpler.
Your contract should include these decisions in plain language:
- The component that creates an execution epoch.
- The artifacts and runtime facts included in executable identity.
- The metadata changes that require
tools/listto run again. - The authorization scopes that expire when the epoch changes.
- The behavior for an in-flight call during a restart.
In-flight calls deserve a firm rule. If a worker dies after receiving a tool call but before producing a response, return an error that identifies the epoch transition. Do not retry a write automatically against the new process. A retry can duplicate a payment, publish twice, or apply a change after the arguments mean something different.
For read-only calls, an automatic retry may be acceptable if the host can prove that the first attempt never reached the action boundary. That proof is difficult across local subprocesses and remote APIs. A timeout is not proof. An empty response is not proof. Start with explicit failure, then add safe retries only where you can demonstrate idempotency.
Test rebuilds as authority changes
A rebuild test should prove more than "the new tool appears." It should prove that stale metadata cannot form a dangerous call, old authorization cannot reach new code, and the audit trail distinguishes the two execution epochs.
Run this test in a local fixture with a server that has one credentialed write tool and one harmless read tool.
- Start server revision A. Capture its execution record, fetch
tools/list, and authorize one agent run for the write tool. - Call the write tool once with a marker such as
revision=A. Confirm the log records epoch A and the approval grant for epoch A. - Rebuild revision B. Keep the tool name the same, but add a required schema field or change the handler to write
revision=B. - Replace the running worker through the same mechanism used in normal development.
- Attempt the old prepared call before metadata refresh and approval. The host should deny it because the epoch changed.
- Fetch the new catalog, obtain new approval if the call needs authority, then call again. Confirm the log records epoch B and a new grant.
The expected denial should be specific enough to debug:
{
"error": "authorization_stale",
"reason": "server execution epoch changed",
"approvedEpoch": "01JQ7R4S4S0QJ7GZP1S2",
"currentEpoch": "01JQ7R9KQ6K2Y8W4JH0M",
"retry": "refresh tool metadata and request authorization"
}
Do not conceal this behind a generic "tool unavailable" message. The agent needs to know whether it should refresh its catalog, wait for a server restart, or ask a human. The operator needs to know whether a watcher replaced a worker unexpectedly.
Add failure tests that developers tend to skip:
- The build succeeds but the old process remains running.
- The process restarts but the tool list remains identical.
- The schema changes but a client ignores
tools/list_changed. - A restart occurs while a write call is waiting for a response.
- The server path remains fixed while the resolved dependency tree changes.
Those tests expose whether your design relies on a friendly server telling the truth. It should not. Local development code is exactly where accidental trust expansion happens, because developers rebuild constantly and assumptions become invisible.
Audit records must answer which code acted
An audit record must let you determine which executable epoch handled a call, or it cannot settle an incident involving a rebuild. Tool name, arguments, and timestamp are useful, but they leave the hardest question unanswered.
Record the execution epoch on every tool call. Record the catalog revision or metadata digest when the host presents tool information to the agent. Record approval decisions with the subject they covered. If a call crosses a credential boundary, record the credential scope name without recording the secret itself.
A compact event sequence might look like this:
{"type":"server_started","epoch":"01JQ7R4...","entryDigest":"sha256:9e4c...71af"}
{"type":"approval_granted","run":"run_01JQ7R1","epoch":"01JQ7R4...","scope":"inventory-api-staging"}
{"type":"tool_called","run":"run_01JQ7R1","epoch":"01JQ7R4...","tool":"inventory_adjust"}
{"type":"server_replaced","oldEpoch":"01JQ7R4...","newEpoch":"01JQ7R9..."}
{"type":"authorization_denied","run":"run_01JQ7R1","epoch":"01JQ7R9...","reason":"stale_epoch"}
This structure also helps with ordinary debugging. When someone reports that an agent used an old schema after a rebuild, you can see whether the host failed to refresh metadata, whether the server never restarted, or whether a supervisor changed code without announcing it. Those are different defects and should not end up in the same bug bucket.
Sallyport's Sessions journal and Activity journal are useful examples of separating agent-run records from individual action records, while projecting both from one encrypted, hash-chained audit log. The same separation applies here: one record explains who was approved to run, and another explains which call occurred under which execution epoch.
Do not make the audit system depend on the server to self-report its identity. Put identity capture beside process launch or credential dispatch. Then verify the audit chain independently where your environment supports that. A server that can alter code during a session must not become the sole witness of which code ran.
Rebuild speed does not justify inherited trust
Fast rebuilds are a development convenience. They do not turn new code into previously reviewed code. If a local MCP server can touch credentials, files, or remote systems, a rebuild should create a visible boundary between the code that was approved and the code that will act next.
Start by making process replacement observable. Add an epoch at launch. Tie session authorization to that epoch. Refresh tool metadata whenever its contract changes. Then make one test fail on purpose: rebuild a server under an active agent session and confirm the next credentialed call stops until the host has current metadata and a current grant.
If that test passes for the right reason, your agent can keep working after a rebuild without receiving permission that belongs to an earlier executable.
FAQ
Does rebuilding an MCP server require restarting the agent?
A rebuild alone does not always require a reset. If the running process still holds the old code in memory, nothing has changed yet. Reset the session when a new executable instance can receive calls, especially if the rebuilt code can change what an existing tool name does.
What does tools/list_changed do after an MCP server rebuild?
Use notifications/tools/list_changed when the tool list or its descriptions and schemas changed. Treat it as a request for the client to fetch fresh metadata, not proof that every client will do so immediately. It does not establish a new executable identity or renew authorization.
Should a rebuilt local MCP server need approval again?
A new server process needs new approval when authorization is bound to the executable instance, its build digest, or its launch context. That is the safe default for local development servers with access to credentials, files, or production APIs. Reusing approval after unknown code replacement turns one click into permission for code the reviewer never saw.
Is an executable path enough to identify an MCP server?
No. A path tells you where the launcher looked, not which bytes the operating system executed. Use an execution identity that includes a digest of the launch artifact, the resolved runtime or interpreter, relevant dependency state, and a fresh process or server epoch.
What if only an MCP tool input schema changes?
A tool schema change can make a formerly safe argument mean something else, so refresh metadata whenever the schema changes. If the running implementation changed too, also replace the identity and invalidate any authorization tied to it. These are separate actions because metadata and executable bytes answer different questions.
Can I hot reload an MCP server safely?
Hot reload is safe only when the reload mechanism publishes a new execution epoch and the authorization layer sees it. A reload that quietly replaces handlers under a long-lived process is hard to review and harder to audit. During local development, a full child-process restart is usually easier to reason about.
Does MCP renegotiate capabilities after a server rebuild?
The MCP Lifecycle specification treats initialization as capability negotiation for a connection, not as a general rebuild protocol. Tool-list change notifications help with discovery, but they do not renegotiate the connection or attest to new server code. Your host or gateway needs its own refresh contract for identity and approvals.
Can I keep the same MCP tool name after changing its behavior?
Keep the tool name stable only if its authority and argument semantics remain stable. If deploy changes from a dry run to a live deployment, use a new tool name or force a clear reauthorization boundary. Stable names are convenient for prompts, but convenience is not a security property.
What should an audit log record for rebuilt MCP servers?
Record the agent run identifier, server process identifier, executable digest, server epoch, tool metadata digest, approval decision, and every call. That lets you answer whether a call ran before or after a rebuild. A journal that records only tool names cannot settle that question.
How should an MCP gateway handle a rebuilt server?
A gateway should authorize the action against the current server identity, not merely the server label supplied by the agent. It should deny calls if the identity changed after approval and should log the decision. Credentials must stay outside the agent and outside the local server process unless that process is explicitly trusted to hold them.