{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "xbill",
  "title_listed": "FastMCP Is Now MCPServer on AWS: Moving a boto3 EC2 MCP Server to the MCP Python SDK 2.x",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "工程实践",
  "is_paid_flag": "否",
  "url": "https://dev.to/aws-builders/fastmcp-is-now-mcpserver-on-aws-moving-a-boto3-ec2-mcp-server-to-the-mcp-python-sdk-2x-42gk",
  "publish_date": "2026-09-11",
  "corpus_stage": "",
  "notes": "",
  "id": "ee3b28d7ab6b",
  "collected_at": "2026-10-02T13:51:33",
  "title": "FastMCP Is Now MCPServer on AWS: Moving a boto3 EC2 MCP Server to the MCP Python SDK 2.x - DEV Community",
  "body_markdown": "This article provides a step by step migration guide for an AWS MCP server from the MCP Python SDK 1.x (\nFastMCP\n) to 2.x (\nMCPServer\n). The server manages Gemma 4 E2B on an Amazon EC2 G5g instance, a Graviton2 host with an NVIDIA T4G GPU, and a suite of Python MCP tools built on boto3 simplifies management of the vLLM hosted deployment.\nhttps://github.com/xbill9/gemma4-dev/tree/main/gpu-vllm-g5g-2b\nWhat Broke?\nThe rig's\nrequirements.txt\nlisted\nmcp\nwith no version bound. Once the machine's Python moved to mcp 2.2.0, the server stopped importing:\npython3\n-c\n\"import server\"\nEnter fullscreen mode\nExit fullscreen mode\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\nIn an MCP client this surfaces less helpfully: the server is launched, dies on the import, and stdio closes before the handshake. The same code on mcp 1.30.0 passes all 27 of its tests. ✅\nThe error message names both fixes, so the first decision is which one.\nPin or Migrate?\nPin\nmcp<2\nMigrate to\nMCPServer\nCode change\nnone\nimport, class name, tests\nWhere the fix lives\nevery interpreter that runs the server\nthe repository\nShared Python\nholds back\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 migrates because\nevery project here installs into one system Python, with no virtualenvs\n, and the deployment image uses the same layout.\nThat shared Python has an AWS-specific wrinkle. Upgrading\nmcp\non it drew this from pip:\nstrands-agents 1.55.0 requires mcp<2.2,>=1.23.0, but you have mcp 2.2.0 which is incompatible.\nSuccessfully installed mcp-2.2.0 mcp-types-2.2.0\nEnter fullscreen mode\nExit fullscreen mode\npip installs anyway and only warns. If Strands Agents shares an interpreter with your MCP server, check its\nmcp\nbound before choosing a 2.x release, or give the two separate interpreters.\nAt This Point You Should Have…\nPython 3.10 or newer — mcp 2.x declares\nRequires-Python >=3.10\nThe repository cloned, and\ngpu-vllm-g5g-2b/\nas your working directory\npip install -r requirements.txt\ndone against the interpreter your MCP client launches\nruff\non the path for\nmake lint\nNo AWS credentials are needed for any step here. The tests are offline by design, and the stdio check only lists tools, which never runs a handler or calls boto3.\nWhat Changed in 2.x?\nThe migration guide's table of changes most projects hit, against this server — stdio transport, 15\n@mcp.tool()\ntools, boto3 for every AWS call, and no client code:\nChange\nFirst symptom\nThis server\nFastMCP\nrenamed to\nMCPServer\nNo module named 'mcp.server.fastmcp'\n❌ hit\ncamelCase fields renamed to snake_case\n'Tool' object has no attribute 'inputSchema'\n❌ hit, in the tests\nhttpx\nreplaced by\nhttpx2\nNo module named 'httpx'\n⚠️ exposed, already declared\nSync handlers run on a worker thread\nget_running_loop()\nraises in a\ndef\nhandler\n✅ every handler is\nasync\nMCP_*\nenv no longer read into settings\nsettings silently ignored\n✅\nMCP_SERVER_NAME\nis the rig's own\nThe first row is the one everybody hits. The second is the one this article is about, because the companion Cloud Run migration never saw it.\nStep 1 — Find Your Exposure\nMeasure before editing:\ngrep\n-c\n\"^@mcp\n\\.\\(\ntool\n\\|\nresource\n\\|\nprompt\n\\)\n\"\nserver.py\ngrep\n-A1\n\"^@mcp\n\\.\n\"\nserver.py |\ngrep\n-c\n\"^async def\"\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\"^import httpx\"\nserver.py\n;\ngrep\n-n\n\"^httpx\"\nrequirements.txt\ngrep\n-n\n\"Hint\n\\b\\|\ninputSchema\n\\|\noutputSchema\n\\|\nmimeType\"\ntests/\n*\n.py\nEnter fullscreen mode\nExit fullscreen mode\n15\n15\n0\n(no matches)\n26:import httpx\n2:httpx\n43:            name for name, tool in self.tools.items() if tool.annotations.destructiveHint\n53:            schema = self.tools[name].inputSchema[\"properties\"]\nEnter fullscreen mode\nExit fullscreen mode\n15 handlers, all\nasync\n, none touching the event loop, and\nhttpx\ndeclared on its own line. The last grep is the one a FastMCP-only checklist misses:\ntwo camelCase attribute reads in the test suite.\nOne more grep hit deserves a look.\ngrep -n \"MCP_\" server.py\nfinds\nMCP_SERVER_NAME\n. v2 no longer reads\nMCP_*\nenvironment variables into server settings, but this one is the rig's own: it is read with\nos.getenv\nand passed in as the server's name, so nothing changes.\nStep 2 — The Rename\nThe whole change to\nserver.py\n:\n-from mcp.server.fastmcp import FastMCP\n+from mcp.server.mcpserver import MCPServer\nfrom mcp.types import ToolAnnotations\n ...\n MCP_SERVER_NAME = os.getenv(\"MCP_SERVER_NAME\", RIG_NAME)\n-mcp = FastMCP(MCP_SERVER_NAME)\n+mcp = MCPServer(MCP_SERVER_NAME)\nREAD_ONLY = ToolAnnotations(readOnlyHint=True, idempotentHint=True)\n WRITE = ToolAnnotations(destructiveHint=False)\n DESTRUCTIVE = ToolAnnotations(destructiveHint=True)\nEnter fullscreen mode\nExit fullscreen mode\nMake both line changes in one edit. An editor that runs\nruff check --fix\non save deletes an import that is momentarily unused, and the next edit leaves\nMCPServer\nundefined.\nThe three\nToolAnnotations(...)\nlines stay exactly as they are, camelCase and all. That is deliberate, and Step 4 shows why it is safe.\nStep 3 — Run the Tests\npython3\n-m\nunittest discover\n-s\ntests\nEnter fullscreen mode\nExit fullscreen mode\nERROR: test_annotations (test_server.ToolCatalogTests.test_annotations)\nAttributeError: 'ToolAnnotations' object has no attribute 'destructiveHint'. Did you mean: 'destructive_hint'?\nERROR: test_launch_defaults_to_spot_and_build (test_server.ToolCatalogTests.test_launch_defaults_to_spot_and_build)\nAttributeError: 'Tool' object has no attribute 'inputSchema'. Did you mean: 'input_schema'?\nFAIL: test_skill_is_complete_in_both_copies (test_server.RepoHygieneTests.test_skill_is_complete_in_both_copies)\nRan 27 tests in 0.012s\nFAILED (failures=1, errors=2)\nEnter fullscreen mode\nExit fullscreen mode\nThe server imports and registers its tools. What broke is Python code that reads protocol models, and here that code is the test suite.\nThe failing test is the one that guards EC2 termination.\ntest_annotations\nasserts that exactly two tools,\nstop_g5g_instance\nand\nterminate_g5g_instance\n, carry\ndestructiveHint\n. On an AWS server that flag is how a client knows a tool can end an instance and destroy its root volume. A migration that deleted this test to get green would remove the check that matters most.\nThe third failure is this rig's own: a test that the generated skill copies match\nserver.py\n. It clears in Step 5.\nStep 4 — Snake Case in Python, camelCase on the Wire\nv2 renamed every protocol model field to snake_case for Python attribute access. The fix is two identifiers:\n-            name for name, tool in self.tools.items() if tool.annotations.destructiveHint\n+            name for name, tool in self.tools.items() if tool.annotations.destructive_hint\n...\n-            schema = self.tools[name].inputSchema[\"properties\"]\n+            schema = self.tools[name].input_schema[\"properties\"]\nEnter fullscreen mode\nExit fullscreen mode\nWhy the\nToolAnnotations(destructiveHint=True)\nconstructors in\nserver.py\ndid not need touching:\n>>> ToolAnnotations(destructiveHint=True).destructive_hint\nTrue\n>>> ToolAnnotations(destructiveHint=True).destructiveHint\nAttributeError: 'ToolAnnotations' object has no attribute 'destructiveHint'\n>>> Tool(...).model_dump(exclude_none=True)\n{'name': 'x', 'input_schema': {'type': 'object'}}\n>>> Tool(...).model_dump(by_alias=True, exclude_none=True)\n{'name': 'x', 'inputSchema': {'type': 'object'}}\nEnter fullscreen mode\nExit fullscreen mode\nConstructors accept both spellings; attribute access is snake_case only. The last two lines are the trap the guide warns about: a plain\nmodel_dump()\nnow emits snake_case keys that other MCP implementations will not recognise, with no error. If your server serialises protocol models itself, add\nby_alias=True\n. This one does not.\nStep 5 — Pin the Floor\nCode that imports\nmcp.server.mcpserver\ncannot run on 1.x:\n-mcp\n+mcp>=2,<3\nEnter fullscreen mode\nExit fullscreen mode\nThe upper bound is the migration guide's own example: meet 3.x on purpose, not by\npip install\n. Keep\nhttpx\non its own line in the same file. v2 depends on\nhttpx2\ninstead, so a server that imports httpx without declaring it loses it on a fresh install, with a traceback that never mentions mcp.\nThis rig also ships its server as a skill, with copies of\nserver.py\nand\nrequirements.txt\nunder\nskills/\n.\nmake skill\nregenerates them, which clears the third failure. If your MCP server is vendored anywhere, into a container build context or a Lambda package, refresh that copy too.\nStep 6 — Lint and Test\nmake lint\nEnter fullscreen mode\nExit fullscreen mode\nruff check server.py refresh_skill.py tests\nAll checks passed!\nlint OK\nEnter fullscreen mode\nExit fullscreen mode\npython3\n-m\nunittest discover\n-s\ntests\nEnter fullscreen mode\nExit fullscreen mode\n----------------------------------------------------------------------\nRan 27 tests in 0.011s\n\nOK\nEnter fullscreen mode\nExit fullscreen mode\n27 tests, the same count that passed on mcp 1.30.0 before the change. ✅\nStep 7 — Test the Protocol by Hand\nUnit tests call Python. A client speaks JSON-RPC over stdio, and the wire is where the camelCase question gets its real answer.\nHold stdin open with\nsleep\n, or the server sees end-of-input and exits after the first reply:\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;\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='gpu-vllm-g5g-2b' version='' proto 2025-06-18\ntools/list OK: 15 tools -> check_g5g_quotas, create_g5g_instance, get_build_progress, get_deployment_config, ...\nwire keys on terminate_g5g_instance: ['annotations', 'description', 'inputSchema', 'name', 'outputSchema', 'title']\nannotations on the wire: {\"destructiveHint\": true}\ndestructive on the wire: ['stop_g5g_instance', 'terminate_g5g_instance']\nEnter fullscreen mode\nExit fullscreen mode\n🟢 All 15 tools, and the wire is still camelCase:\ninputSchema\n,\ndestructiveHint\n. The same two tools are marked destructive.\nClients see no change.\nThe rename is visible only to Python that reads the models.\nNote\nversion=''\n. In v2 a server that does not pass a version reports an empty string instead of the installed SDK's version. Pass\nversion=\"...\"\nto\nMCPServer(...)\nif anything displays it.\nWhat About boto3?\nboto3 is synchronous, and that is the change most AWS MCP servers should look at hardest.\nThis rig was already safe. Every tool is\nasync def\n, and each boto3 call goes through one helper:\nasync\ndef\n_call\n(\nfunc\n,\n**\nkwargs\n):\nreturn\nawait\nasyncio\n.\nto_thread\n(\nfunc\n,\n**\nkwargs\n)\nEnter fullscreen mode\nExit fullscreen mode\nSo a slow EC2 or SSM call runs on a worker thread in v1 and v2 alike. v2's change to sync handlers does nothing here.\nThe common shape is different: a plain\ndef\ntool that calls\nec2.describe_instances()\ndirectly. The migration guide says v1 ran such a handler inline on the event loop, so one slow AWS call stalled every other request on the server, and v2 runs it on a worker thread. That is a free concurrency gain for boto3 code. The only thing it breaks is code in a\ndef\nhandler that expects the loop's thread, such as\nasyncio.get_running_loop()\n. The guide also points out the reverse: an\nasync def\ntool that calls boto3 without a thread still blocks the loop in both versions.\nThe Rest of the Fleet\nUpgrading one shared Python moves every MCP server on it at once:\ngrep\n-l\n\"from mcp.server.fastmcp import\"\n*\n/server.py |\nwc\n-l\ngrep\n-l\n\"from mcp.server.mcpserver import\"\n*\n/server.py\nEnter fullscreen mode\nExit fullscreen mode\n37\ngpu-2B-cloudrun-devops-agent/server.py\nlocal-llamacpp-1650ti-2b-q4_0/server.py\ngpu-vllm-g5g-2b/server.py\nEnter fullscreen mode\nExit fullscreen mode\n37 servers in the monorepo still import FastMCP, 13 of them EC2 or Inferentia rigs, and none of them starts on this Python until it gets the same rename. That is the cost of migrating over pinning on a shared interpreter, paid up front. The per-server change is small enough to make paying it reasonable.\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\"Hint\n\\b\\|\ninputSchema\n\\|\noutputSchema\n\\|\nmimeType\"\ntests/\n*\n.py\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# attribute reads: .destructiveHint -> .destructive_hint, .inputSchema -> .input_schema\n# constructors:    ToolAnnotations(destructiveHint=True) still works, leave it\n# requirements.txt: mcp -> mcp>=2,<3, and declare httpx if you import it\nmake lint\n&&\npython3\n-m\nunittest discover\n-s\ntests\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\nEnter fullscreen mode\nExit fullscreen mode\nSummary\nThe goal of this article was to give an AWS EC2 MCP server basic MCP Python SDK 2.x support without changing what it does or what its clients see. The key to the solution was auditing the tests as well as the server, because on this rig the SDK's snake_case change broke the test suite and not the server. The migration results were:\n🟢 The server change was the import and the constructor; all 15 tools registered, and the camelCase\nToolAnnotations(...)\nconstructors stayed\n❌ Two camelCase attribute reads broke in the tests, including the one guarding EC2 stop and terminate; the fix is\ndestructive_hint\nand\ninput_schema\n🟢 The wire format did not change:\ntools/list\nstill sends\ninputSchema\nand\ndestructiveHint\n, and the same two tools are marked destructive\n🟢 boto3 was already off the event loop through\nasyncio.to_thread\n, so v2's worker-thread change was a no-op\n⚠️ Upgrading a shared Python to mcp 2.2.0 put\nstrands-agents\n1.55.0 outside its\nmcp<2.2\nbound and stopped 37 other FastMCP servers until they are migrated\nScope: mcp 2.2.0 on Python 3.14.7, with 1.30.0 as the v1 reference, boto3 1.43.90, ruff 0.16.7, on one Debian workstation. Offline unit tests and one stdio handshake; no EC2 instance was provisioned, because nothing in the migration changes an AWS call. The deployment the tools manage is unchanged and is covered in the rig's earlier G5g article.\nThe strategy for using MCP for migrating an AWS 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\nFastMCP Is Now MCPServer: Migrating a Python MCP Server to the MCP SDK 2.x | dev.to\ngpu-vllm-g5g-2b | GitHub\nmcp 2.2.0 (mcp-types 2.2.0), Python 3.14.7, boto3 1.43.90, ruff 0.16.7.",
  "metadata": {
    "title": "FastMCP Is Now MCPServer on AWS: Moving a boto3 EC2 MCP Server to the MCP Python SDK 2.x - DEV Community",
    "description": "What basic MCP Python SDK 2.x support takes for an AWS EC2 MCP server built on boto3: one rename in server.py, snake_case in the tests, a floor in requirements.txt, and why neither the wire format nor the boto3 calls change. Tagged with aws, mcp, python, ec2.",
    "keywords": "aws, mcp, python, ec2, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/aws-builders/fastmcp-is-now-mcpserver-on-aws-moving-a-boto3-ec2-mcp-server-to-the-mcp-python-sdk-2x-42gk",
    "og:title": "FastMCP Is Now MCPServer on AWS: Moving a boto3 EC2 MCP Server to the MCP Python SDK 2.x",
    "og:description": "What basic MCP Python SDK 2.x support takes for an AWS EC2 MCP server built on boto3: one rename in server.py, snake_case in the tests, a floor in requirements.txt, and why neither the wire format nor the boto3 calls change.",
    "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-vllm-g5g-2b%2Fdocs%2Fmcp2%2Fdevto-cover.c9eb2ee2.jpg",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@",
    "twitter:title": "FastMCP Is Now MCPServer on AWS: Moving a boto3 EC2 MCP Server to the MCP Python SDK 2.x",
    "twitter:description": "What basic MCP Python SDK 2.x support takes for an AWS EC2 MCP server built on boto3: one rename in server.py, snake_case in the tests, a floor in requirements.txt, and why neither the wire format nor the boto3 calls change.",
    "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-vllm-g5g-2b%2Fdocs%2Fmcp2%2Fdevto-cover.c9eb2ee2.jpg"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/ee3b28d7ab6b/code/body-000.txt",
      "chars": 27,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-001.txt",
      "chars": 333,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-002.txt",
      "chars": 144,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-003.txt",
      "chars": 360,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-004.txt",
      "chars": 205,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-005.txt",
      "chars": 422,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-006.txt",
      "chars": 38,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-007.txt",
      "chars": 557,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-008.txt",
      "chars": 319,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-009.txt",
      "chars": 400,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-010.txt",
      "chars": 16,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-011.txt",
      "chars": 10,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-012.txt",
      "chars": 71,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-013.txt",
      "chars": 38,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-014.txt",
      "chars": 98,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-015.txt",
      "chars": 347,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-016.txt",
      "chars": 419,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-017.txt",
      "chars": 84,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-018.txt",
      "chars": 116,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-019.txt",
      "chars": 108,
      "language": "text"
    },
    {
      "file": "attachments/ee3b28d7ab6b/code/body-020.txt",
      "chars": 1099,
      "language": "text"
    }
  ],
  "images": [],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://github.com/xbill9/gemma4-dev/tree/main/gpu-vllm-g5g-2b",
      "label": "https://github.com/xbill9/gemma4-dev/tree/main/gpu-vllm-g5g-2b",
      "type": "a"
    },
    {
      "href": "https://py.sdk.modelcontextprotocol.io/v2/migration/",
      "label": "Migration Guide: v1 to v2 | MCP Python SDK",
      "type": "a"
    },
    {
      "href": "https://dev.to/gde/fastmcp-is-now-mcpserver-migrating-a-python-mcp-server-to-the-mcp-sdk-2x-2nhj",
      "label": "FastMCP Is Now MCPServer: Migrating a Python MCP Server to the MCP SDK 2.x | dev.to",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:48:32Z",
  "source_url": "https://dev.to/aws-builders/fastmcp-is-now-mcpserver-on-aws-moving-a-boto3-ec2-mcp-server-to-the-mcp-python-sdk-2x-42gk",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 15617,
  "expected_body_images": 0,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/ee3b28d7ab6b/pages/source.html",
  "local_html": "attachments/ee3b28d7ab6b/pages/article.html",
  "body_base_path": "attachments/ee3b28d7ab6b/pages/",
  "body_markdown_path": "attachments/ee3b28d7ab6b/pages/article.md"
}