MCP Server Lifecycle
Connect, close, and reconnect MCP servers at the right application boundary.
MCP servers expose external tools to an agent. Treat each MCP connection as infrastructure: decide whether it is required, when it connects, how it is closed, and what happens when it fails.
Scenario
An agent needs tools from a filesystem MCP server, an internal docs MCP server, and a remote CRM MCP server. Some are required for startup. Others are optional and should degrade gracefully.
When to Use It
Use this pattern whenever an agent registers tools with .mcp(...) or wraps MCP tools with local Anvia tools.
Architecture Shape
| Boundary | Pattern |
|---|---|
| required startup dependency | connect once during application boot and fail fast on error |
| optional dependency | attempt connection, log failure, run agent without that server |
| request-scoped server | connect inside try/finally and always close |
| long-lived process | close all servers during shutdown |
| reconnect | close previous server, connect next server, rebuild agents or update tool sets |
Connect at Startup
import { connectMcp, mcp, type McpConnection, type McpServer } from "@anvia/core";
export async function connectMcpServers() {
const filesystem = await connectMcp(
mcp.stdio({
name: "filesystem",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
}),
);
const docs = await connectMcp(
mcp.http({
name: "docs",
url: "https://mcp.example.com/mcp",
}),
);
return { filesystem, docs };
}Register connected servers on an agent.
const { filesystem, docs } = await connectMcpServers();
const agent = new AgentBuilder("research", model)
.instructions("Use MCP tools when external context is needed.")
.mcp([filesystem, docs])
.defaultMaxTurns(4)
.build();Optional Servers
Optional servers should not block the whole application.
async function connectOptional(connection: McpConnection) {
try {
return await connectMcp(connection);
} catch (error) {
logger.warn({ error, server: connection.name }, "optional MCP server unavailable");
return undefined;
}
}
const docs = await connectOptional(
mcp.http({ name: "docs", url: "https://mcp.example.com/mcp" }),
);
const mcpServers = [docs].filter((server): server is McpServer => server !== undefined);Request-Scoped Connections
Use request scope only when the connection depends on request-local credentials or a short-lived resource.
const server = await connectMcp(connection);
try {
const agent = new AgentBuilder("tenant-docs", model)
.mcp([server])
.defaultMaxTurns(3)
.build();
return await agent.prompt(message).send();
} finally {
await server.close();
}Most application servers should prefer startup connections over request-scoped connections.
Registry and Reconnect
type McpRegistry = {
get(name: string): McpServer | undefined;
set(server: McpServer): void;
closeAll(): Promise<void>;
};
async function reconnect(connection: McpConnection, registry: McpRegistry) {
const previous = registry.get(connection.name);
await previous?.close();
const next = await connectMcp(connection);
registry.set(next);
return next;
}After reconnecting, create new agents or update the shared tool catalog used by future runs. A built agent keeps the tools it was built with unless it uses a shared mutable ToolSet.
Failure Modes
| Failure | Fix |
|---|---|
| app starts without required MCP tools | fail fast during startup |
| optional MCP outage breaks all agents | omit optional server and log degraded capability |
| server process leaks | close on shutdown or in finally |
| reconnected tools not visible | rebuild agents or update the shared tool set |
| tool call loops on remote errors | keep turn limits low and expose clear tool error text |
Test Checklist
- Test required server connection failure at startup.
- Test optional server failure produces a reduced agent capability set.
- Test request-scoped connections close in
finally. - Test reconnect closes the previous server before replacing it.
- Use Studio or
/agents/:agentId/mcpsto inspect registered MCP servers.
