Skip to main content
RapidDev - Software Development Agency
mcp-tutorial

How to version your MCP server API

Version your MCP server API using semantic versioning to evolve tools without breaking existing clients. Use the server's version field for compatibility signaling, implement capability negotiation during the MCP handshake, and follow deprecation patterns that give clients time to migrate. This tutorial covers adding, renaming, and removing tools safely across versions.

What you'll learn

  • How to apply semantic versioning to MCP server tool APIs
  • How to use MCP capability negotiation for version compatibility
  • How to deprecate tools gracefully without breaking clients
  • How to run multiple API versions simultaneously
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced8 min read20-30 minMCP TypeScript SDK v1.x, Node.js 18+March 2026RapidDev Engineering Team
TL;DR

Version your MCP server API using semantic versioning to evolve tools without breaking existing clients. Use the server's version field for compatibility signaling, implement capability negotiation during the MCP handshake, and follow deprecation patterns that give clients time to migrate. This tutorial covers adding, renaming, and removing tools safely across versions.

Versioning MCP Server APIs for Backward Compatibility

As your MCP server evolves, you will add new tools, change parameters, and eventually remove old tools. Without versioning, these changes break clients that depend on the old API. This tutorial shows how to apply semantic versioning to MCP servers, use the protocol's built-in capability negotiation, implement deprecation warnings, and run multiple tool versions side by side during migration periods.

Prerequisites

  • A working MCP server with tools that need versioning
  • Understanding of semantic versioning (major.minor.patch)
  • Node.js 18+ and npm installed
  • Basic familiarity with the MCP protocol handshake

Step-by-step guide

1

Define your versioning strategy with semantic versioning

Apply semantic versioning to your MCP server: MAJOR version for breaking changes (removed tools, changed parameter types), MINOR for additions (new tools, optional parameters), PATCH for bug fixes. Set the version in McpServer configuration. Clients can read this version during connection and adjust their behavior. Document your versioning policy so users know what to expect across releases.

typescript
1// src/version.ts
2export const SERVER_VERSION = "2.1.0";
3
4// Version changelog:
5// 2.1.0 - Added search_files tool, added optional 'recursive' param to list_files
6// 2.0.0 - BREAKING: Renamed get_file to read_file, removed legacy_search tool
7// 1.3.0 - Added write_file tool
8// 1.2.0 - Added list_files tool
9// 1.1.0 - Added get_stats tool
10// 1.0.0 - Initial release with get_file tool
11
12// src/index.ts
13import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14import { SERVER_VERSION } from "./version.js";
15
16const server = new McpServer({
17 name: "versioned-file-server",
18 version: SERVER_VERSION, // Communicated to clients during handshake
19});

Expected result: Server version set and communicated to clients during the MCP handshake.

2

Implement capability negotiation for feature detection

MCP clients and servers exchange capabilities during the initial handshake. Use custom capabilities to signal which tool groups or features are available. Clients that understand your capabilities can enable advanced features, while older clients gracefully fall back. This is more flexible than pure version number comparison because clients can check for specific features.

typescript
1// src/capabilities.ts
2export const SERVER_CAPABILITIES = {
3 tools: {
4 supported: true,
5 features: {
6 fileOperations: true,
7 searchOperations: true,
8 writeOperations: true,
9 metricsReporting: true,
10 },
11 },
12 versioning: {
13 currentVersion: "2.1.0",
14 minimumClientVersion: "1.0.0",
15 deprecatedTools: ["legacy_search"],
16 removedInNext: ["get_file_v1"],
17 },
18};
19
20// Register a version info tool
21server.tool(
22 "get_server_info",
23 "Get server version, capabilities, and deprecation notices",
24 {},
25 async () => {
26 return {
27 content: [{
28 type: "text",
29 text: JSON.stringify(SERVER_CAPABILITIES, null, 2),
30 }],
31 };
32 }
33);

Expected result: Server capabilities include feature flags and version metadata that clients can query.

3

Deprecate tools with warnings before removal

When a tool is being phased out, do not remove it immediately. Mark it as deprecated by adding a warning to its response and updating its description. Run the deprecated tool for at least one major version cycle so clients have time to migrate. Log deprecation usage to track how many clients still use the old tool.

typescript
1// src/deprecated-tool.ts
2import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3import { z } from "zod";
4import { logger } from "./logger.js";
5
6export function registerDeprecatedTool(
7 server: McpServer,
8 oldName: string,
9 newName: string,
10 description: string,
11 schema: Record<string, z.ZodTypeAny>,
12 handler: (params: any) => Promise<any>
13) {
14 server.tool(
15 oldName,
16 `[DEPRECATED - Use ${newName} instead] ${description}`,
17 schema,
18 async (params) => {
19 logger.warn({ tool: oldName, replacement: newName }, "Deprecated tool called");
20
21 const result = await handler(params);
22
23 // Prepend deprecation warning to response
24 const warning = `⚠ DEPRECATION WARNING: "${oldName}" will be removed in the next major version. Use "${newName}" instead.\n\n`;
25 const content = result.content.map((c: any) =>
26 c.type === "text" ? { ...c, text: warning + c.text } : c
27 );
28
29 return { ...result, content };
30 }
31 );
32}
33
34// Usage:
35// registerDeprecatedTool(server, "get_file", "read_file", "Read file contents",
36// { path: z.string() }, readFileHandler);

Expected result: Deprecated tools still work but return warnings and log usage for migration tracking.

4

Add new parameters safely with backward compatibility

When adding new parameters to existing tools, always make them optional with sensible defaults. This is a MINOR version change that does not break existing clients. Clients that do not send the new parameter get the default behavior. Document the new parameter in the tool description so AI clients know it is available.

typescript
1// Before (v2.0.0):
2server.tool("list_files", "List files in a directory", {
3 directory: z.string().default("."),
4}, listFilesHandler);
5
6// After (v2.1.0) — added optional 'recursive' parameter:
7server.tool("list_files", "List files in a directory. Set recursive=true to include subdirectories.", {
8 directory: z.string().default("."),
9 recursive: z.boolean().default(false).describe("Include files from subdirectories"),
10 fileTypes: z.array(z.string()).optional().describe("Filter by file extensions, e.g. ['.ts', '.js']"),
11}, async ({ directory, recursive, fileTypes }) => {
12 // Existing behavior preserved when recursive=false and fileTypes=undefined
13 const fs = await import("fs/promises");
14 const entries = await fs.readdir(directory, { withFileTypes: true, recursive });
15 let files = entries.filter(e => e.isFile());
16 if (fileTypes?.length) {
17 files = files.filter(e => fileTypes.some(ext => e.name.endsWith(ext)));
18 }
19 return { content: [{ type: "text", text: JSON.stringify(files.map(f => f.name), null, 2) }] };
20});

Expected result: New optional parameters added without breaking clients that omit them.

5

Run versioned tools side by side during migration

For major breaking changes, run both old and new versions of a tool simultaneously. Name the old version with a _v1 suffix and register the new version under the original name. This gives clients a migration window where both versions are available. Remove the v1 tool in the next major release after confirming no clients use it.

typescript
1// src/versioned-tools.ts
2import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3import { z } from "zod";
4
5export function registerVersionedTools(server: McpServer) {
6 // Current version (v2) — new interface
7 server.tool(
8 "search_files",
9 "Search files by content pattern. Returns matched lines with context.",
10 {
11 pattern: z.string().describe("Regex pattern to search for"),
12 directory: z.string().default("."),
13 contextLines: z.number().default(2).describe("Lines of context around matches"),
14 },
15 searchFilesV2Handler
16 );
17
18 // Legacy version (v1) — deprecated, will be removed in v3.0.0
19 server.tool(
20 "search_files_v1",
21 "[DEPRECATED in v3.0.0 - Use search_files instead] Search files by name pattern.",
22 {
23 query: z.string().describe("File name pattern"),
24 },
25 async ({ query }) => {
26 console.error(`[deprecation] search_files_v1 called — migrate to search_files`);
27 // Delegate to v2 with compatible parameters
28 return searchFilesV2Handler({ pattern: query, directory: ".", contextLines: 0 });
29 }
30 );
31}

Expected result: Both tool versions work simultaneously, with deprecated versions logging migration warnings.

Complete working example

src/versioning.ts
1import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2import { z } from "zod";
3
4export const SERVER_VERSION = "2.1.0";
5
6export function registerVersionedServer(server: McpServer) {
7 // Version info tool
8 server.tool("get_server_info", "Get server version and capabilities", {}, async () => ({
9 content: [{ type: "text" as const, text: JSON.stringify({
10 name: "versioned-server",
11 version: SERVER_VERSION,
12 deprecated: ["search_files_v1"],
13 removedInNext: [],
14 }, null, 2) }],
15 }));
16
17 // Current tool (v2)
18 server.tool("search_files", "Search files by content pattern with context lines", {
19 pattern: z.string(),
20 directory: z.string().default("."),
21 contextLines: z.number().default(2),
22 }, async ({ pattern, directory, contextLines }) => {
23 return { content: [{ type: "text" as const, text: `Searching ${directory} for ${pattern} with ${contextLines} context lines` }] };
24 });
25
26 // Deprecated v1 adapter
27 server.tool("search_files_v1",
28 "[DEPRECATED - Use search_files] Search by file name",
29 { query: z.string() },
30 async ({ query }) => {
31 console.error(`[deprecation] search_files_v1 called`);
32 return { content: [{ type: "text" as const,
33 text: `WARNING: search_files_v1 is deprecated. Use search_files instead.\n\nResults for: ${query}` }] };
34 }
35 );
36}
37
38// Deprecation wrapper factory
39export function deprecated<T>(
40 oldName: string,
41 newName: string,
42 handler: (params: T) => Promise<any>
43): (params: T) => Promise<any> {
44 return async (params: T) => {
45 console.error(`[deprecated] ${oldName} -> use ${newName}`);
46 const result = await handler(params);
47 const warn = `[DEPRECATED: ${oldName} will be removed. Use ${newName}]\n`;
48 return {
49 ...result,
50 content: result.content.map((c: any) =>
51 c.type === "text" ? { ...c, text: warn + c.text } : c
52 ),
53 };
54 };
55}

Common mistakes when versioning your MCP server API

Why it's a problem: Removing tools without a deprecation period, breaking all existing clients immediately

How to avoid: Deprecate tools for at least one major version cycle before removal. Mark them in the description and log usage.

Why it's a problem: Adding required parameters to existing tools, which is a breaking change

How to avoid: Always make new parameters optional with sensible defaults. Required parameter additions require a new major version.

Why it's a problem: Changing tool return formats without versioning, causing client-side parse errors

How to avoid: Document return format changes in the changelog and treat format changes as breaking changes requiring a major version bump.

Why it's a problem: Not tracking deprecated tool usage, making it impossible to know when it is safe to remove them

How to avoid: Log every call to deprecated tools and monitor the count. Remove only when usage drops to zero.

Best practices

  • Follow semantic versioning: MAJOR for breaking changes, MINOR for additions, PATCH for fixes
  • Set the version in McpServer config so clients see it during the handshake
  • Deprecate tools for at least one major version cycle before removal
  • Make all new parameters optional with defaults to avoid breaking changes
  • Expose a get_server_info tool for programmatic version and capability discovery
  • Log deprecated tool usage to track migration progress
  • Run old and new tool versions side by side during migration periods
  • Document changes in a changelog that clients can reference

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Show me how to version an MCP server API. I need semantic versioning, a get_server_info tool for capability discovery, deprecation wrappers that add warnings to old tools, and a pattern for running v1 and v2 of a tool side by side during migration.

MCP Prompt

Implement API versioning for my MCP server. Add SERVER_VERSION constant, a get_server_info tool, deprecated tool wrappers with logging, and backward-compatible parameter additions. Use TypeScript with Zod schemas.

Frequently asked questions

When should I bump the major version of my MCP server?

Bump the major version when you remove a tool, rename a tool, change a parameter from optional to required, or change the return format in a way that would break existing clients.

How long should I keep deprecated tools available?

At least one major version cycle. If you deprecate a tool in v2.x, keep it available through all v2.x releases and remove it in v3.0.0. Track usage to confirm no clients still depend on it.

Can MCP clients automatically detect breaking changes?

Clients can read the server version during the handshake and compare it to their expected version. The get_server_info tool provides more detailed capability information for programmatic compatibility checks.

Should I version individual tools or the entire server?

Version the entire server. Individual tool versioning (search_v1, search_v2) should be a temporary migration pattern, not a permanent architecture. Keep the server version as the single source of truth.

Can RapidDev help plan MCP server versioning strategy?

Yes. RapidDev helps teams design versioning strategies that balance backward compatibility with the ability to evolve. They can audit existing tool APIs and recommend a migration plan.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.