{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "xbill",
  "title_listed": "FastMCP Is Now MCPServer: Migrating a Python MCP Server to the MCP SDK 2.x",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "工程实践",
  "is_paid_flag": "否",
  "url": "https://dev.to/gde/fastmcp-is-now-mcpserver-migrating-a-python-mcp-server-to-the-mcp-sdk-2x-2nhj",
  "publish_date": "2026-09-11",
  "corpus_stage": "",
  "notes": "",
  "id": "a8d7726abf7f",
  "collected_at": "2026-10-02T13:51:35",
  "title": "FastMCP Is Now MCPServer: Migrating a Python MCP Server to the MCP SDK 2.x - DEV Community",
  "body_markdown": "This article provides a step by step migration guide for a Python MCP server from the MCP Python SDK 1.x (\nFastMCP\n) to 2.x (\nMCPServer\n), followed by a step by step deployment of Gemma 4 E2B to a Cloud Run hosted GPU enabled system. A suite of Python MCP tools is built to simplify management of the vLLM hosted deployment.\nhttps://github.com/xbill9/gemma4-dev/tree/main/gpu-2B-cloudrun-devops-agent\nWhat Broke?\nNothing in the repository changed. A fresh install did.\nThe project's\nrequirements.txt\nlisted\nmcp\nwith no version bound, so the next\npip install\nresolved the 2.x line. The server stopped importing:\npython3\n-c\n\"from mcp.server.fastmcp import FastMCP\"\nEnter fullscreen mode\nExit fullscreen mode\nraise ModuleNotFoundError(_MESSAGE, name=__name__)\nModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.\nEnter fullscreen mode\nExit fullscreen mode\nIt showed up in Claude Code first, and less helpfully. The MCP server was listed as failed with\nConnection closed\n: Claude Code launched it with\npython3\n, it died on the import, and stdio closed before the handshake.\nThat error message deserves credit. The 2.x package still ships a\nmcp/server/fastmcp.py\n, and its only job is to raise it. A bare\nNo module named\nwould have sent everyone hunting for a broken install. ✅\nPin or Migrate?\nTwo fixes, and the error message names both.\nPin\nmcp<2\nMigrate to\nMCPServer\nCode change\nnone\nimport and class name\nWhere the fix lives\nevery interpreter that runs the server\nthe repository\nShared system Python\ndowngrades\nmcp\nfor everything on it\nnothing global changes\nFuture fixes\nthe v1 maintenance line\nthe current line\nThe migration guide says the v1.x line keeps receiving critical bug fixes and security patches, so pinning is legitimate. This rig rules it out for a different reason:\nthese projects install into one system Python, with no virtualenvs\n, so a pin in one project is a downgrade for every other project on the machine. Migrating keeps the change inside the repository.\nAt This Point You Should Have…\nPython 3.10 or newer — mcp 2.x declares\nRequires-Python >=3.10\nThe Google Cloud SDK, logged in, with application default credentials\nA Google Cloud project with Cloud Run GPU access in your region (this one runs in\nus-east4\n)\nA GCS bucket named\n<project>-bucket\nfor the model weights\nThe repository cloned, and the directory above as your working directory\nWhat Changed in 2.x?\nThe official migration guide opens with a table of the changes almost every project hits. Here they are against this server — stdio transport,\n@mcp.tool()\ntools, one\n@mcp.resource()\n, and no client code:\nChange\nFirst symptom\nThis server\nFastMCP\nrenamed to\nMCPServer\nNo module named 'mcp.server.fastmcp'\n❌ hit\nhttpx\nreplaced by\nhttpx2\nNo module named 'httpx'\n⚠️ exposed, already safe\nSync handlers run on a worker thread\nget_running_loop()\nraises in a\ndef\nhandler\n⚠️ handlers move threads\ncamelCase fields renamed to snake_case\n'Tool' object has no attribute 'inputSchema'\n✅ not used\nResource URIs are\nstr\n, not\nAnyUrl\n'str' object has no attribute 'host'\n✅ tests already call\nstr()\nTransport parameters moved off the constructor\nunexpected keyword argument 'port'\n✅ name only\nMcpError\nrenamed to\nMCPError\ncannot import name 'McpError'\n✅ not used\nThree rows deserve more than a table cell.\nmcp\nno longer installs\nhttpx\n.\nv2 depends on\nhttpx2\n, a fork of httpx, instead.\nserver.py\ndoes\nimport httpx\nfor its HTTP probes and its benchmark, and the guide warns what happens next: a\nModuleNotFoundError\nwhose traceback never mentions mcp. This server escaped only because\nrequirements.txt\nalready listed\nhttpx\non its own line. If your server imports httpx and never declared it, declare it now.\nSync handlers changed threads.\nIn v1 a plain\ndef\ntool ran inline on the event loop, so a blocking call stalled every other request on the server. v2 runs sync handlers on a worker thread. The only thing that breaks is code that expects the loop's thread —\nasyncio.get_running_loop()\nin a\ndef\nhandler now raises. This server has none, so its sync tools gain concurrency for free. The move covers\ndef\nhandlers only: an\nasync def\ntool that makes a blocking call still blocks the loop, in v1 and v2 alike.\nThe server's version went blank.\nIn v1 an unversioned server reported the installed\nmcp\nversion as its own. In v2 it reports an empty string. Nothing breaks, but it shows up in the handshake in Step 5.\nWhat Did Not Change\nThe guide lists the everyday surface that carries over, and it is most of a typical FastMCP server:\n@mcp.tool()\n,\n@mcp.resource()\nand\n@mcp.prompt()\ntake the same arguments and handler signatures\nTool return handling: strings, dicts, models and content blocks are wrapped by the same rules\nlist_tools()\nand\nlist_resources()\nreturn the same lists\nlifespan=\nworks as before\nFor this server that means no tool function changed. Every tool body is exactly what it was.\nStep 1 — Find Your Exposure\nMeasure before editing. Five greps cover everything in the table above:\ngrep\n-c\n\"^@mcp\n\\.\\(\ntool\n\\|\nresource\n\\)\n\"\nserver.py\ngrep\n-A1\n\"^@mcp\n\\.\n\"\nserver.py |\ngrep\n-c\n\"^def\"\ngrep\n-n\n\"get_running_loop\n\\|\nasyncio.run(\"\nserver.py\n||\necho\n\"(no matches)\"\ngrep\n-n\n\"MCP_\n\\|\ndotenv\"\nserver.py\n||\necho\n\"(no matches)\"\ngrep\n-n\n\"^import httpx\"\nserver.py\n;\ngrep\n-n\n\"^httpx\"\nrequirements.txt\nEnter fullscreen mode\nExit fullscreen mode\n28\n12\n(no matches)\n(no matches)\n13:import httpx\n14:httpx\nEnter fullscreen mode\nExit fullscreen mode\n28 handlers, 12 of them sync, none touching the event loop, and\nhttpx\ndeclared.\nThe\nMCP_\ngrep checks one more change. v2 no longer reads\nMCP_*\nenvironment variables or a\n.env\nfile into server settings. The guide points out that constructor arguments always took precedence, so those variables rarely did anything anyway. This server reads its own configuration from\nGOOGLE_CLOUD_PROJECT\n,\nVLLM_BASE_URL\nand friends, so nothing changes.\nOne check a grep cannot do:\nv2 inserted\ntitle\nand\ndescription\ninto the constructor's positional parameters.\nA v1 call like\nFastMCP(\"Demo\", \"You answer questions…\")\nstill runs on v2, but the second string silently becomes the title and stops reaching the model as instructions. Keep the name positional and pass everything else by keyword. This server passes only the name.\nStep 2 — The Rename\nThe whole code change:\n-from mcp.server.fastmcp import FastMCP\n+from mcp.server.mcpserver import MCPServer\nfrom openai import AsyncOpenAI\n ...\n-# Initialize FastMCP server\n-mcp = FastMCP(\"Self-Hosted vLLM DevOps Agent\")\n+# Initialize MCP server (mcp 2.x; FastMCP was renamed MCPServer)\n+mcp = MCPServer(\"Self-Hosted vLLM DevOps Agent\")\nEnter fullscreen mode\nExit fullscreen mode\n@mcp.tool()\n,\n@mcp.resource()\n,\nmcp.run()\nand every tool body stay as they are. Other submodules moved the same way —\nmcp.server.fastmcp.*\nis now\nmcp.server.mcpserver.*\n, and\nctx.fastmcp\nis now\nctx.mcp_server\n— but this server uses neither.\n🔎 Tip: Change the Usage Before the Import\nThis one cost a test run. The project has a Claude Code hook that runs\nruff format\nand\nruff check --fix\nafter every edit. Change the import line first and, for a moment,\nMCPServer\nis imported but unused. The hook deletes it:\ncat\nserver.py\nruff check\n--fix\n--diff\nserver.py\nEnter fullscreen mode\nExit fullscreen mode\nfrom mcp.server.mcpserver import MCPServer\n\nmcp = FastMCP(\"demo\")\n\n--- server.py\n+++ server.py\n@@ -1,3 +1,2 @@\n-from mcp.server.mcpserver import MCPServer\n\n mcp = FastMCP(\"demo\")\n\nWould fix 1 error.\nEnter fullscreen mode\nExit fullscreen mode\nThe next edit renames the class, and the file now has no import at all:\nmcp = MCPServer(\"Self-Hosted vLLM DevOps Agent\")\n          ^^^^^^^^^\nNameError: name 'MCPServer' is not defined\nEnter fullscreen mode\nExit fullscreen mode\nMake both changes in one edit, or change the usage first. Any editor that runs\nruff check --fix\non save will do the same thing.\nStep 3 — Pin the Floor\nCode that imports\nmcp.server.mcpserver\ncannot run on 1.x, so the requirement should say so:\n-mcp\n+mcp>=2\nEnter fullscreen mode\nExit fullscreen mode\nThe guide's own example also caps the major version,\nmcp>=2,<3\n. That is the safer line if you would rather meet 3.x on purpose than by\npip install\n. Keep\nhttpx\non its own line in the same file, for the reason above.\nStep 4 — Lint and Test\nmake lint\nEnter fullscreen mode\nExit fullscreen mode\nruff check .\nAll checks passed!\nruff format --check .\n14 files already formatted\nmypy .\nSuccess: no issues found in 6 source files\nEnter fullscreen mode\nExit fullscreen mode\nmake\ntest\nEnter fullscreen mode\nExit fullscreen mode\n----------------------------------------------------------------------\nRan 28 tests in 1.058s\n\nOK\nEnter fullscreen mode\nExit fullscreen mode\nThe suite compares the registered tool set against a hard-coded list through\nmcp.list_tools()\n. That call is on the guide's unchanged list, and it is the test that would catch a tool silently failing to register after the rename. ✅\nStep 5 — Test the Protocol by Hand\nUnit tests call Python. A client speaks JSON-RPC over stdio, so test that too.\nHold stdin open with\nsleep\n: with a bare\nprintf\npipe the server saw end-of-input and exited after answering only\ninitialize\n.\n{\nprintf\n'%s\\n'\n\\\n'{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{},\"clientInfo\":{\"name\":\"probe\",\"version\":\"0\"}}}'\n\\\n'{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}'\n\\\n'{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}'\n\\\n'{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"resources/list\",\"params\":{}}'\n;\nsleep\n5\n;\n}\n\\\n| python3 server.py 2>/dev/null\nEnter fullscreen mode\nExit fullscreen mode\nThe responses are JSON; summarised:\ninitialize OK: name='Self-Hosted vLLM DevOps Agent' version='' proto 2025-06-18\ntools/list OK: 27 tools -> cloudrun_analyze_cloud_logging, cloudrun_analyze_gpu_logs, cloudrun_check_gpu_quotas, cloudrun_deploy, ...\nresources/list OK: ['config://vllm-deployment-template']\nEnter fullscreen mode\nExit fullscreen mode\n🟢 27 tools and the resource, matching the suite's expected list. Note\nversion=''\n— the unversioned-server change from the table. Pass\nversion=\"...\"\nto\nMCPServer(...)\nif a client or dashboard displays it.\nKeep this snippet. It is the fastest way to tell \"my server is broken\" from \"my client config is broken.\"\nStep 6 — Register It\nClaude Code reads\n.mcp.json\n. Nothing in it changes for the migration — it launches\nserver.py\nwith the system\npython3\n:\n{\n\"mcpServers\"\n:\n{\n\"cloudrun-devops\"\n:\n{\n\"command\"\n:\n\"python3\"\n,\n\"args\"\n:\n[\n\"/home/xbill/gemma4-dev/gpu-2B-cloudrun-devops-agent/server.py\"\n],\n\"env\"\n:\n{\n\"GOOGLE_CLOUD_PROJECT\"\n:\n\"aisprint-491218\"\n,\n\"GOOGLE_CLOUD_LOCATION\"\n:\n\"us-east4\"\n,\n\"VLLM_BASE_URL\"\n:\n\"https://gpu-2b-l4-devops-agent-289270257791.us-east4.run.app\"\n,\n\"MODEL_NAME\"\n:\n\"/mnt/models/gemma-4-E2B-it\"\n}\n}\n}\n}\nEnter fullscreen mode\nExit fullscreen mode\nIf the server failed at startup earlier in the session, reconnect it from\n/mcp\nor start a new session to pick up the fixed code.\nStep 7 — Stage the Model Weights\nCloud Run mounts the bucket read-only at\n/mnt/models\nthrough GCS FUSE, so the weights go to GCS once. Download to a real disk rather than\n/tmp\n, which on this host is a RAM-backed tmpfs smaller than the model:\nhf download google/gemma-4-E2B-it\n--local-dir\n~/hf-downloads/gemma-4-E2B-it\ngcloud storage rsync ~/hf-downloads/gemma-4-E2B-it gs://aisprint-491218-bucket/gemma-4-E2B-it\n\\\n--recursive\n--exclude\n=\n'^\\.cache/'\ngcloud storage\nls\n-l\ngs://aisprint-491218-bucket/gemma-4-E2B-it/\nEnter fullscreen mode\nExit fullscreen mode\nAverage throughput: 41.4MiB/s\n      4954  2026-09-10T14:51:14Z  gs://aisprint-491218-bucket/gemma-4-E2B-it/config.json\n10246621918  2026-09-10T14:55:13Z  gs://aisprint-491218-bucket/gemma-4-E2B-it/model.safetensors\n  32169626  2026-09-10T14:51:34Z  gs://aisprint-491218-bucket/gemma-4-E2B-it/tokenizer.json\nTOTAL: 9 objects, 10278849571 bytes (9.57GiB)\nEnter fullscreen mode\nExit fullscreen mode\nCheck the architecture, not the folder name.\nThe same bucket already held a\ngemma-2b-it/\nfolder that looked like the answer and was the original Gemma, which the\ngemma4\nparsers cannot serve.\nconfig.json\nsettles it:\ngcloud storage\ncat\ngs://aisprint-491218-bucket/gemma-4-E2B-it/config.json\n\\\n| python3\n-c\n'import json,sys; c=json.load(sys.stdin); t=c[\"text_config\"]; print(c[\"model_type\"], c[\"architectures\"], \"hidden\", t[\"hidden_size\"], \"layers\", t[\"num_hidden_layers\"])'\nEnter fullscreen mode\nExit fullscreen mode\ngemma4 ['Gemma4ForConditionalGeneration'] hidden 1536 layers 35\nEnter fullscreen mode\nExit fullscreen mode\nStep 8 — Deploy to Cloud Run\nThe\ndeploy-vllm\ntarget in the\nMakefile\nis the single source of truth for the vLLM and Cloud Run flags:\nFlag\nValue\nWhy\n--gpu-type\nnvidia-l4\none L4 per instance\n--concurrency\n4\nrequests Cloud Run sends one instance\n--max-num-seqs\n8\nvLLM's batch ceiling\n--tool-call-parser\n,\n--reasoning-parser\ngemma4\nGemma 4 tool calling breaks without either\n--no-allow-unauthenticated\ncallers need an identity token\nmake deploy\nEnter fullscreen mode\nExit fullscreen mode\nDeploying container to Cloud Run service [gpu-2b-l4-devops-agent] in project [aisprint-491218] region [us-east4]\nDeploying new service...\nCreating Revision....................done\nRouting traffic.....done\nDone.\nService [gpu-2b-l4-devops-agent] revision [gpu-2b-l4-devops-agent-00001-ssq] has been deployed and is serving 100 percent of traffic.\nService URL: https://gpu-2b-l4-devops-agent-289270257791.us-east4.run.app\nEnter fullscreen mode\nExit fullscreen mode\ngcloud run deploy\nreturns only once the startup probe passes, and the probe waits\ninitialDelaySeconds=180\nbefore its first check. Expect several minutes.\nDemo Mode: Fixed Instances\nThe default autoscales between zero and one instance, which means a cold GPU start after the service goes idle. A demo cannot wait for that. The\nMakefile\ntakes a\nSCALING\nvariable:\nSCALING\n?=\nauto\nifeq\n($(SCALING),auto)\nSCALING_FLAGS\n=\n--scaling\n=\nauto\n--max-instances\n=\n1\n--min-instances\n=\n0\nelse\nSCALING_FLAGS\n=\n--scaling\n=\n$(\nSCALING\n)\nendif\nEnter fullscreen mode\nExit fullscreen mode\nmake deploy\nSCALING\n=\n1\ngcloud run services describe gpu-2b-l4-devops-agent\n--region\nus-east4\n--format\n=\n'yaml(metadata.annotations)'\nEnter fullscreen mode\nExit fullscreen mode\nrun.googleapis.com/manualInstanceCount: '1'\n    run.googleapis.com/scalingMode: manual\nEnter fullscreen mode\nExit fullscreen mode\ngcloud only accepts a positive instance count for manual scaling, so this cannot pin the service at zero. The min and max flags are passed only in auto mode, because gcloud's help does not say how they combine with a fixed count. One L4 now runs until you change it.\nPlain\nmake deploy\n, and the\ncloudrun_deploy\nand\ncloudrun_update_scaling\ntools, all pass\n--scaling=auto\non purpose — a service stuck in manual scaling at zero returns 503 to every request. So any of them quietly takes a demo back to scale-to-zero.\nStep 9 — Validate\nAsk vLLM what it loaded:\ncurl\n-s\n-H\n\"Authorization: Bearer\n$(\ngcloud auth print-identity-token\n)\n\"\n\\\nhttps://gpu-2b-l4-devops-agent-289270257791.us-east4.run.app/v1/models | python3\n-m\njson.tool\nEnter fullscreen mode\nExit fullscreen mode\n{\n\"object\"\n:\n\"list\"\n,\n\"data\"\n:\n[\n{\n\"id\"\n:\n\"/mnt/models/gemma-4-E2B-it\"\n,\n\"object\"\n:\n\"model\"\n,\n\"owned_by\"\n:\n\"vllm\"\n,\n\"max_model_len\"\n:\n16384\n}\n]\n}\nEnter fullscreen mode\nExit fullscreen mode\nThe model id is the mount path, not the Hugging Face repo id. The container is started with\n--model=/mnt/models/<path>\n, so that path is the name the OpenAI API expects.\nThen ask the agent for\ncloudrun_verify_model_health\n:\n✅ Model health check PASSED.\nModel: /mnt/models/gemma-4-E2B-it\nResponse: 'Hello! Yes, I am working. I am Gemma 4, a Large La...'\nLatency: 2.48 seconds.\nEnter fullscreen mode\nExit fullscreen mode\nThat answer came through the migrated server: Claude Code called the tool over MCP, and the tool called vLLM. 🟢\nStep 10 — Benchmark Sweep\nAsk the agent for\ncloudrun_run_benchmark\nwith its defaults: one warmup request, then 20 requests at each concurrency level of 1, 2, 4 and 8, up to 128 output tokens each, one fixed prompt at temperature 0.\nConcurrency\nReq/s\nTokens/s\nAvg latency\nP95 latency\n1\n0.39\n49.63\n2.58 s\n2.59 s\n2\n0.75\n95.36\n2.68 s\n2.74 s\n4\n1.47\n188.46\n2.71 s\n2.78 s\n8\n1.48\n189.53\n4.86 s\n5.47 s\nEvery request at every level succeeded. Three readings:\nFrom 1 to 4, throughput scales almost linearly.\n188.46 tokens/s is 3.8x the single-stream 49.63 (arithmetic), while average latency moves from 2.58 s to 2.71 s. The L4 is nowhere near full at 4.\nFrom 4 to 8, it stops.\nThroughput rises 0.6% (arithmetic) while average latency goes from 2.71 s to 4.86 s. Half the requests are waiting.\nThe ceiling is a Cloud Run setting, not the GPU.\nOne instance accepts\n--concurrency=4\nrequests at a time, and vLLM would batch up to\n--max-num-seqs=8\n. The next sweep worth running redeploys with\n--concurrency=8\nand measures the L4 instead of the setting.\nAnd the SDK version cannot move any of these numbers. The benchmark's HTTP calls go from\nserver.py\nto vLLM; MCP only carries the one tool call that starts them. The sweep is here to show the migrated server driving the whole lifecycle, not to measure the SDK.\nTeardown\nmake destroy\nEnter fullscreen mode\nExit fullscreen mode\nNot run for this article — the demo service is still up. It deletes the Cloud Run service; the weights stay in the bucket for the next deploy.\nCheat Sheet\n# exposure\ngrep\n-rn\n\"mcp.server.fastmcp\"\n.\ngrep\n-A1\n\"^@mcp\n\\.\n\"\nserver.py |\ngrep\n-c\n\"^def\"\ngrep\n-n\n\"^import httpx\"\nserver.py\n;\ngrep\n-n\n\"^httpx\"\nrequirements.txt\n# the rename, in ONE edit\n#   from mcp.server.fastmcp import FastMCP  ->  from mcp.server.mcpserver import MCPServer\n#   FastMCP(\"name\")                         ->  MCPServer(\"name\")\n# requirements.txt: mcp -> mcp>=2 (or mcp>=2,<3), and declare httpx if you import it\nmake lint\n&&\nmake\ntest\n# stdio smoke test: hold stdin open\n{\nprintf\n'%s\\n'\n'{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{},\"clientInfo\":{\"name\":\"p\",\"version\":\"0\"}}}'\n\\\n'{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}'\n\\\n'{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}'\n;\nsleep\n5\n;\n}\n| python3 server.py 2>/dev/null\n# deploy, demo, teardown\nmake deploy\nmake deploy\nSCALING\n=\n1\nmake destroy\nEnter fullscreen mode\nExit fullscreen mode\nSummary\nThe goal of this article was to move a Python MCP server off FastMCP and onto the MCP Python SDK 2.x without changing what it does. The key to the solution was measuring exposure against the migration guide before editing, which reduced the change to one import and one class name. The migration results were:\n🟢 The code change was the import and the constructor; all 27 tools and the resource registered unchanged\n⚠️\nmcp\n2.x no longer installs\nhttpx\n; this server was safe only because it declared\nhttpx\nitself\n🟢 The 12 sync handlers now run on a worker thread, so their blocking calls no longer stall the event loop\n❌ A formatter hook deleted the new import mid-edit; change the usage and the import together\n🟢 The migrated server deployed Gemma 4 E2B to a Cloud Run L4 and drove a benchmark sweep that peaked at 189.53 tokens/s, capped by Cloud Run's\n--concurrency=4\nScope: mcp 2.2.0 on Python 3.14.7, with the 1.30.0 wheel as the v1 reference. One Cloud Run instance with one NVIDIA L4 in\nus-east4\n, vLLM\nv0.26.0-cu129\n, Gemma 4 E2B, in manual scaling at one fixed instance during the sweep. One sweep of 20 requests per level, a single fixed prompt, 128 max output tokens. It measures the serving stack, not the SDK.\nThe strategy for using MCP for migrating a Python MCP server to the MCP SDK 2.x was validated with an incremental step by step approach.\nReferences\nMigration Guide: v1 to v2 | MCP Python SDK\ngpu-2B-cloudrun-devops-agent | GitHub\nmcp 2.2.0 (mcp-types 2.2.0), Python 3.14.7, ruff 0.16.6, Google Cloud SDK 583.0.0, vLLM v0.26.0 on one NVIDIA L4, Cloud Run\nus-east4\n.",
  "metadata": {
    "title": "FastMCP Is Now MCPServer: Migrating a Python MCP Server to the MCP SDK 2.x - DEV Community",
    "description": "Step-by-step: moving a FastMCP server to the MCP Python SDK 2.x — what broke, what changed, what did not, and a redeploy of its Gemma 4 vLLM backend to a Cloud Run L4 GPU. Tagged with mcp, python, googlecloud, vllm.",
    "keywords": "mcp, python, googlecloud, vllm, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/gde/fastmcp-is-now-mcpserver-migrating-a-python-mcp-server-to-the-mcp-sdk-2x-2nhj",
    "og:title": "FastMCP Is Now MCPServer: Migrating a Python MCP Server to the MCP SDK 2.x",
    "og:description": "Step-by-step: moving a FastMCP server to the MCP Python SDK 2.x — what broke, what changed, what did not, and a redeploy of its Gemma 4 vLLM backend to a Cloud Run L4 GPU.",
    "og:site_name": "DEV Community",
    "og:image": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fxbill9%2Fgemma4-dev%2Fmain%2Fgpu-2B-cloudrun-devops-agent%2Fdocs%2Fdevto-cover.5bbae7c1.jpg",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@",
    "twitter:title": "FastMCP Is Now MCPServer: Migrating a Python MCP Server to the MCP SDK 2.x",
    "twitter:description": "Step-by-step: moving a FastMCP server to the MCP Python SDK 2.x — what broke, what changed, what did not, and a redeploy of its Gemma 4 vLLM backend to a Cloud Run L4 GPU.",
    "twitter:card": "summary_large_image",
    "twitter:widgets:new-embed-design": "on",
    "twitter:image:src": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fxbill9%2Fgemma4-dev%2Fmain%2Fgpu-2B-cloudrun-devops-agent%2Fdocs%2Fdevto-cover.5bbae7c1.jpg"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/a8d7726abf7f/code/body-000.txt",
      "chars": 52,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-001.txt",
      "chars": 388,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-002.txt",
      "chars": 291,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-003.txt",
      "chars": 57,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-004.txt",
      "chars": 314,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-005.txt",
      "chars": 48,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-006.txt",
      "chars": 199,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-007.txt",
      "chars": 116,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-008.txt",
      "chars": 13,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-009.txt",
      "chars": 10,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-010.txt",
      "chars": 131,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-011.txt",
      "chars": 10,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-012.txt",
      "chars": 98,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-013.txt",
      "chars": 416,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-014.txt",
      "chars": 271,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-015.txt",
      "chars": 425,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-016.txt",
      "chars": 273,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-017.txt",
      "chars": 353,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-018.txt",
      "chars": 259,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-019.txt",
      "chars": 64,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-020.txt",
      "chars": 12,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-021.txt",
      "chars": 419,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-022.txt",
      "chars": 154,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-023.txt",
      "chars": 130,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-024.txt",
      "chars": 91,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-025.txt",
      "chars": 169,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-026.txt",
      "chars": 212,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-027.txt",
      "chars": 152,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-028.txt",
      "chars": 13,
      "language": "text"
    },
    {
      "file": "attachments/a8d7726abf7f/code/body-029.txt",
      "chars": 924,
      "language": "text"
    }
  ],
  "images": [],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://github.com/xbill9/gemma4-dev/tree/main/gpu-2B-cloudrun-devops-agent",
      "label": "https://github.com/xbill9/gemma4-dev/tree/main/gpu-2B-cloudrun-devops-agent",
      "type": "a"
    },
    {
      "href": "https://py.sdk.modelcontextprotocol.io/v2/migration/",
      "label": "Migration Guide: v1 to v2 | MCP Python SDK",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:48:33Z",
  "source_url": "https://dev.to/gde/fastmcp-is-now-mcpserver-migrating-a-python-mcp-server-to-the-mcp-sdk-2x-2nhj",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 20132,
  "expected_body_images": 0,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/a8d7726abf7f/pages/source.html",
  "local_html": "attachments/a8d7726abf7f/pages/article.html",
  "body_base_path": "attachments/a8d7726abf7f/pages/",
  "body_markdown_path": "attachments/a8d7726abf7f/pages/article.md"
}