Server Configuration & Lifecycle

+15 Mana ✨

Introduction

Beyond the basics of creating an MCP server, understanding the configuration options and lifecycle events gives you control over how your server behaves, what capabilities it advertises, and how it communicates status information back to clients.

Key Concepts

  • Server Info: The metadata object passed to the McpServer constructor, containing the server's name and version.
  • Options: An optional second argument to McpServer that configures capabilities and behavior.
  • Capabilities: Declarations that tell the client which MCP features your server supports (tools, resources, prompts, logging).
  • Lifecycle: The sequence of events from server startup through request handling to shutdown.

Real World Context

In production, you might run multiple MCP servers with different capabilities. A data server might only expose resources, while an action server only exposes tools. Declaring capabilities explicitly lets clients know exactly what each server can do, which improves discoverability and prevents unnecessary requests.

Deep Dive

The McpServer constructor accepts two arguments: server info and an optional options object. The server info identifies your server during the MCP initialization handshake.

Here is how to create a server with explicit capability declarations:

typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

const server = new McpServer(
  {
    name: 'data-server',
    version: '2.1.0'
  },
  {
    capabilities: {
      logging: {},
      tools: {},
      resources: {},
      prompts: {}
    }
  }
);

Each key in the capabilities object tells the client that your server supports that feature. If you only expose tools, you can omit resources and prompts from capabilities.\n\nNote: With the SDK v2 McpServer class, capabilities are auto-detected based on what you register. If you call registerTool(), the tools capability is automatically included. Explicit capability declaration is optional — it is only needed if you want to advertise capabilities before registering handlers, or if you use the lower-level Server class.

The server lifecycle follows a clear sequence. When a client connects, the MCP protocol performs an initialization handshake where the server sends its info and capabilities. After that, the server enters the main loop where it receives requests and sends responses.

Here is a complete example showing the lifecycle with a connect call:

typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new McpServer(
  { name: 'lifecycle-demo', version: '1.0.0' },
  { capabilities: { logging: {}, tools: {} } }
);

// Register handlers before connecting
// (tools, resources, prompts go here)

const transport = new StdioServerTransport();
await server.connect(transport);
// Server is now listening for requests

The key rule is to register all your handlers (tools, resources, prompts) before calling server.connect(). Once connected, the server is ready to receive and respond to client requests.

Inside tool and resource handlers, you can log messages back to the client using the context object. This is useful for debugging and providing status updates.

The following example shows how to use logging inside a handler:

typescript
import { z } from 'zod';

server.registerTool('status-check', {
  description: 'Check system status',
  inputSchema: z.object({})
}, async (_input, { ctx }) => {
  await ctx.mcpReq.log('info', 'Running status check...');
  // perform check
  await ctx.mcpReq.log('info', 'Status check complete');
  return { content: [{ type: 'text', text: 'All systems operational' }] };
});

The ctx.mcpReq.log() method sends log messages to the client. The first argument is the log level (such as 'info', 'warning', or 'error'), and the second is the message string. These logs appear in the client's debug output.

Common Pitfalls

  • Registering handlers after connect: While the SDK may allow this, it is best practice to register all handlers before calling connect() to ensure the capability negotiation is accurate.
  • Forgetting capability declarations: If you declare tools but not resources in capabilities, clients may not attempt to list your resources even if you register resource handlers.
  • Blocking the event loop: Long-running synchronous operations in handlers will block the server from processing other requests.

Best Practices

  • Only declare capabilities for features you actually implement. This keeps the client-server contract honest.
  • Use structured logging via ctx.mcpReq.log() instead of console.error() so that clients can display and filter log messages.
  • Keep the server info version in sync with your package.json version for consistency.

Summary

You learned how to configure an MCP server with capability declarations, understand the initialization and request-handling lifecycle, and use the context object for logging. Proper configuration ensures clients know exactly what your server offers, and understanding the lifecycle helps you structure your code correctly.

Code Examples

typescript
const server = new McpServer(
  { name: 'data-server', version: '2.1.0' },
  {
    capabilities: {
      logging: {},
      tools: {},
      resources: {},
      prompts: {}
    }
  }
);
✓ Completed