[{"data":1,"prerenderedAt":1882},["ShallowReactive",2],{"page-\u002Fguides\u002Fmcp":3},{"id":4,"title":5,"body":6,"description":1875,"extension":1876,"meta":1877,"navigation":467,"path":1878,"seo":1879,"stem":1880,"__hash__":1881},"content\u002F4.guides\u002F16.mcp.md","MCP Server",{"type":7,"value":8,"toc":1855},"minimark",[9,26,31,240,244,307,328,332,341,348,382,385,404,412,701,712,716,729,852,859,863,869,936,940,946,961,976,980,983,988,1029,1038,1145,1159,1164,1167,1175,1196,1204,1296,1301,1335,1341,1345,1348,1391,1395,1463,1467,1649,1659,1663,1666,1749,1753,1756,1811,1815,1851],[10,11,12,13,25],"p",{},"tileserver-rs is the ",[14,15,16,17,24],"strong",{},"first Rust tile server with native ",[18,19,23],"a",{"href":20,"rel":21},"https:\u002F\u002Fmodelcontextprotocol.io\u002F",[22],"nofollow","Model Context Protocol (MCP)"," support",". Any deployment can expose its tile sources, styles, renderer, and PostGIS\u002FSTAC backends as MCP tools — turning the tile server into an LLM-addressable geospatial endpoint.",[27,28,30],"h2",{"id":29},"what-you-get","What you get",[32,33,34,50],"table",{},[35,36,37],"thead",{},[38,39,40,44,47],"tr",{},[41,42,43],"th",{},"Tier",[41,45,46],{},"Tool",[41,48,49],{},"Purpose",[51,52,53,70,84,98,112,126,140,155,169,183,197,211,226],"tbody",{},[38,54,55,61,67],{},[56,57,58],"td",{},[14,59,60],{},"A",[56,62,63],{},[64,65,66],"code",{},"tileserver_list_sources",[56,68,69],{},"List every configured tile source",[38,71,72,76,81],{},[56,73,74],{},[14,75,60],{},[56,77,78],{},[64,79,80],{},"tileserver_get_source_tilejson",[56,82,83],{},"Full TileJSON 3.0 metadata for one source",[38,85,86,90,95],{},[56,87,88],{},[14,89,60],{},[56,91,92],{},[64,93,94],{},"tileserver_list_styles",[56,96,97],{},"List every configured map style",[38,99,100,104,109],{},[56,101,102],{},[14,103,60],{},[56,105,106],{},[64,107,108],{},"tileserver_get_style",[56,110,111],{},"Full MapLibre style JSON",[38,113,114,118,123],{},[56,115,116],{},[14,117,60],{},[56,119,120],{},[64,121,122],{},"tileserver_get_tile_metadata",[56,124,125],{},"Bounds, zoom range, vector-layer schemas",[38,127,128,132,137],{},[56,129,130],{},[14,131,60],{},[56,133,134],{},[64,135,136],{},"tileserver_get_server_info",[56,138,139],{},"Version, source\u002Fstyle counts, renderer state",[38,141,142,147,152],{},[56,143,144],{},[14,145,146],{},"B",[56,148,149],{},[64,150,151],{},"tileserver_render_static_map",[56,153,154],{},"Render a static WebP\u002FPNG\u002FJPEG map (≤ 2048×2048, ≤ 1.5 MB)",[38,156,157,161,166],{},[56,158,159],{},[14,160,146],{},[56,162,163],{},[64,164,165],{},"tileserver_get_tile",[56,167,168],{},"Fetch raw vector tile bytes at z\u002Fx\u002Fy",[38,170,171,175,180],{},[56,172,173],{},[14,174,146],{},[56,176,177],{},[64,178,179],{},"tileserver_query_features_at_point",[56,181,182],{},"Features intersecting a coordinate (PostGIS)",[38,184,185,189,194],{},[56,186,187],{},[14,188,146],{},[56,190,191],{},[64,192,193],{},"tileserver_query_features_cql2",[56,195,196],{},"CQL2 filter against a PostGIS table",[38,198,199,203,208],{},[56,200,201],{},[14,202,146],{},[56,204,205],{},[64,206,207],{},"tileserver_search_stac_items",[56,209,210],{},"Search a configured STAC catalog by bbox\u002Fdatetime",[38,212,213,218,223],{},[56,214,215],{},[14,216,217],{},"D",[56,219,220],{},[64,221,222],{},"tileserver:\u002F\u002Fstyles\u002F{id}",[56,224,225],{},"Resource template — style JSON",[38,227,228,232,237],{},[56,229,230],{},[14,231,217],{},[56,233,234],{},[64,235,236],{},"tileserver:\u002F\u002Fdata\u002F{id}.json",[56,238,239],{},"Resource template — TileJSON metadata",[27,241,243],{"id":242},"transports","Transports",[32,245,246,259],{},[35,247,248],{},[38,249,250,253,256],{},[41,251,252],{},"Transport",[41,254,255],{},"Use case",[41,257,258],{},"Auth",[51,260,261,274],{},[38,262,263,268,271],{},[56,264,265],{},[14,266,267],{},"stdio",[56,269,270],{},"Local — Claude Desktop, Cursor, Copilot CLI, Claude Code",[56,272,273],{},"Trusted (inherits user perms)",[38,275,276,281,288],{},[56,277,278],{},[14,279,280],{},"Streamable HTTP",[56,282,283,284,287],{},"Remote — nested at ",[64,285,286],{},"\u002Fmcp"," inside the main HTTP server",[56,289,290,291,294,295,298,299,302,303,306],{},"Static ",[64,292,293],{},"Bearer"," via ",[64,296,297],{},"[mcp].auth_token"," ",[14,300,301],{},"or"," OAuth 2.0 DCR via ",[64,304,305],{},"[mcp.oauth]"," (mutually exclusive)",[308,309,311],"callout",{"type":310},"info",[10,312,313,316,317,319,320,323,324,327],{},[14,314,315],{},"claude.ai Custom Connectors"," require OAuth 2.0 — they do not accept static bearer tokens. Enable ",[64,318,305],{}," and point claude.ai at ",[64,321,322],{},"https:\u002F\u002Fyour-server\u002F.well-known\u002Foauth-authorization-server",". See the ",[14,325,326],{},"OAuth 2.0 for claude.ai"," section below.",[27,329,331],{"id":330},"quick-start","Quick Start",[333,334,336,337,340],"h3",{"id":335},"_1-build-with-the-mcp-feature","1. Build with the ",[64,338,339],{},"mcp"," feature",[10,342,343,344,347],{},"The MCP server is ",[14,345,346],{},"opt-in"," to keep default builds slim:",[349,350,355],"pre",{"className":351,"code":352,"language":353,"meta":354,"style":354},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","cargo install --git https:\u002F\u002Fgithub.com\u002Fvinayakkulkarni\u002Ftileserver-rs --features mcp\n","bash","",[64,356,357],{"__ignoreMap":354},[358,359,362,366,370,373,376,379],"span",{"class":360,"line":361},"line",1,[358,363,365],{"class":364},"sBMFI","cargo",[358,367,369],{"class":368},"sfazB"," install",[358,371,372],{"class":368}," --git",[358,374,375],{"class":368}," https:\u002F\u002Fgithub.com\u002Fvinayakkulkarni\u002Ftileserver-rs",[358,377,378],{"class":368}," --features",[358,380,381],{"class":368}," mcp\n",[10,383,384],{},"If you build from source:",[349,386,388],{"className":351,"code":387,"language":353,"meta":354,"style":354},"cargo build --release --features mcp\n",[64,389,390],{"__ignoreMap":354},[358,391,392,394,397,400,402],{"class":360,"line":361},[358,393,365],{"class":364},[358,395,396],{"class":368}," build",[358,398,399],{"class":368}," --release",[358,401,378],{"class":368},[358,403,381],{"class":368},[333,405,407,408,411],{"id":406},"_2-add-an-mcp-block-to-your-config","2. Add an ",[64,409,410],{},"[mcp]"," block to your config",[349,413,417],{"className":414,"code":415,"language":416,"meta":354,"style":354},"language-toml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[server]\nhost = \"127.0.0.1\"\nport = 8080\n\n# Embedded MCP at \u002Fmcp (Streamable HTTP). Stdio is always available via\n# the `mcp-stdio` subcommand and ignores this block.\n[mcp]\nenabled = true\n\n# Pick ONE auth mode (mutually exclusive):\n# 1. Static bearer — simplest, works with Cursor \u002F Claude Code \u002F Copilot CLI:\n#    The server refuses to start if MCP_AUTH_TOKEN is unset or empty, so the\n#    literal \"${MCP_AUTH_TOKEN}\" can never become the secret. Use 16+ characters.\n# auth_token = \"${MCP_AUTH_TOKEN}\"\n\n# 2. OAuth 2.0 — required by claude.ai Custom Connectors (see section below):\n# [mcp.oauth]\n# enabled = true\n# issuer_url = \"https:\u002F\u002Ftiles.example.com\"\n# signing_key_path = \"\u002Fetc\u002Ftileserver\u002Fmcp-jwt-key.pem\"\n# approval_secret = \"${MCP_APPROVAL_SECRET}\"\n\n# Strict CORS for the \u002Fmcp endpoint (defaults to [\"*\"] — lock down in prod):\n# cors_origins = [\"https:\u002F\u002Fclaude.ai\", \"https:\u002F\u002Fcursor.sh\"]\n\n[[sources]]\nid = \"openmaptiles\"\ntype = \"pmtiles\"\npath = \"\u002Fdata\u002Ftiles\u002Fopenmaptiles.pmtiles\"\n\n[[styles]]\nid = \"osm-bright\"\npath = \"\u002Fdata\u002Fstyles\u002Fosm-bright\u002Fstyle.json\"\n","toml",[64,418,419,431,450,462,469,476,482,491,503,508,514,520,526,532,538,543,549,555,561,567,573,579,584,590,596,601,613,628,643,658,663,673,687],{"__ignoreMap":354},[358,420,421,425,428],{"class":360,"line":361},[358,422,424],{"class":423},"sMK4o","[",[358,426,427],{"class":364},"server",[358,429,430],{"class":423},"]\n",[358,432,434,438,441,444,447],{"class":360,"line":433},2,[358,435,437],{"class":436},"sTEyZ","host ",[358,439,440],{"class":423},"=",[358,442,443],{"class":423}," \"",[358,445,446],{"class":368},"127.0.0.1",[358,448,449],{"class":423},"\"\n",[358,451,453,456,458],{"class":360,"line":452},3,[358,454,455],{"class":436},"port ",[358,457,440],{"class":423},[358,459,461],{"class":460},"sbssI"," 8080\n",[358,463,465],{"class":360,"line":464},4,[358,466,468],{"emptyLinePlaceholder":467},true,"\n",[358,470,472],{"class":360,"line":471},5,[358,473,475],{"class":474},"sHwdD","# Embedded MCP at \u002Fmcp (Streamable HTTP). Stdio is always available via\n",[358,477,479],{"class":360,"line":478},6,[358,480,481],{"class":474},"# the `mcp-stdio` subcommand and ignores this block.\n",[358,483,485,487,489],{"class":360,"line":484},7,[358,486,424],{"class":423},[358,488,339],{"class":364},[358,490,430],{"class":423},[358,492,494,497,499],{"class":360,"line":493},8,[358,495,496],{"class":436},"enabled ",[358,498,440],{"class":423},[358,500,502],{"class":501},"sfNiH"," true\n",[358,504,506],{"class":360,"line":505},9,[358,507,468],{"emptyLinePlaceholder":467},[358,509,511],{"class":360,"line":510},10,[358,512,513],{"class":474},"# Pick ONE auth mode (mutually exclusive):\n",[358,515,517],{"class":360,"line":516},11,[358,518,519],{"class":474},"# 1. Static bearer — simplest, works with Cursor \u002F Claude Code \u002F Copilot CLI:\n",[358,521,523],{"class":360,"line":522},12,[358,524,525],{"class":474},"#    The server refuses to start if MCP_AUTH_TOKEN is unset or empty, so the\n",[358,527,529],{"class":360,"line":528},13,[358,530,531],{"class":474},"#    literal \"${MCP_AUTH_TOKEN}\" can never become the secret. Use 16+ characters.\n",[358,533,535],{"class":360,"line":534},14,[358,536,537],{"class":474},"# auth_token = \"${MCP_AUTH_TOKEN}\"\n",[358,539,541],{"class":360,"line":540},15,[358,542,468],{"emptyLinePlaceholder":467},[358,544,546],{"class":360,"line":545},16,[358,547,548],{"class":474},"# 2. OAuth 2.0 — required by claude.ai Custom Connectors (see section below):\n",[358,550,552],{"class":360,"line":551},17,[358,553,554],{"class":474},"# [mcp.oauth]\n",[358,556,558],{"class":360,"line":557},18,[358,559,560],{"class":474},"# enabled = true\n",[358,562,564],{"class":360,"line":563},19,[358,565,566],{"class":474},"# issuer_url = \"https:\u002F\u002Ftiles.example.com\"\n",[358,568,570],{"class":360,"line":569},20,[358,571,572],{"class":474},"# signing_key_path = \"\u002Fetc\u002Ftileserver\u002Fmcp-jwt-key.pem\"\n",[358,574,576],{"class":360,"line":575},21,[358,577,578],{"class":474},"# approval_secret = \"${MCP_APPROVAL_SECRET}\"\n",[358,580,582],{"class":360,"line":581},22,[358,583,468],{"emptyLinePlaceholder":467},[358,585,587],{"class":360,"line":586},23,[358,588,589],{"class":474},"# Strict CORS for the \u002Fmcp endpoint (defaults to [\"*\"] — lock down in prod):\n",[358,591,593],{"class":360,"line":592},24,[358,594,595],{"class":474},"# cors_origins = [\"https:\u002F\u002Fclaude.ai\", \"https:\u002F\u002Fcursor.sh\"]\n",[358,597,599],{"class":360,"line":598},25,[358,600,468],{"emptyLinePlaceholder":467},[358,602,604,607,610],{"class":360,"line":603},26,[358,605,606],{"class":423},"[[",[358,608,609],{"class":364},"sources",[358,611,612],{"class":423},"]]\n",[358,614,616,619,621,623,626],{"class":360,"line":615},27,[358,617,618],{"class":436},"id ",[358,620,440],{"class":423},[358,622,443],{"class":423},[358,624,625],{"class":368},"openmaptiles",[358,627,449],{"class":423},[358,629,631,634,636,638,641],{"class":360,"line":630},28,[358,632,633],{"class":436},"type ",[358,635,440],{"class":423},[358,637,443],{"class":423},[358,639,640],{"class":368},"pmtiles",[358,642,449],{"class":423},[358,644,646,649,651,653,656],{"class":360,"line":645},29,[358,647,648],{"class":436},"path ",[358,650,440],{"class":423},[358,652,443],{"class":423},[358,654,655],{"class":368},"\u002Fdata\u002Ftiles\u002Fopenmaptiles.pmtiles",[358,657,449],{"class":423},[358,659,661],{"class":360,"line":660},30,[358,662,468],{"emptyLinePlaceholder":467},[358,664,666,668,671],{"class":360,"line":665},31,[358,667,606],{"class":423},[358,669,670],{"class":364},"styles",[358,672,612],{"class":423},[358,674,676,678,680,682,685],{"class":360,"line":675},32,[358,677,618],{"class":436},[358,679,440],{"class":423},[358,681,443],{"class":423},[358,683,684],{"class":368},"osm-bright",[358,686,449],{"class":423},[358,688,690,692,694,696,699],{"class":360,"line":689},33,[358,691,648],{"class":436},[358,693,440],{"class":423},[358,695,443],{"class":423},[358,697,698],{"class":368},"\u002Fdata\u002Fstyles\u002Fosm-bright\u002Fstyle.json",[358,700,449],{"class":423},[10,702,703,704,711],{},"A complete example lives at ",[18,705,708],{"href":706,"rel":707},"https:\u002F\u002Fgithub.com\u002Fvinayakkulkarni\u002Ftileserver-rs\u002Fblob\u002Fmain\u002Fdata\u002Fconfigs\u002Fmcp.toml",[22],[64,709,710],{},"data\u002Fconfigs\u002Fmcp.toml",".",[333,713,715],{"id":714},"_3a-wire-into-claude-desktop-stdio","3a. Wire into Claude Desktop (stdio)",[10,717,718,719,722,723,728],{},"Edit ",[64,720,721],{},"~\u002FLibrary\u002FApplication Support\u002FClaude\u002Fclaude_desktop_config.json"," (macOS) — see the ",[18,724,727],{"href":725,"rel":726},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fquickstart\u002Fuser",[22],"Claude Desktop docs"," for Windows\u002FLinux paths:",[349,730,734],{"className":731,"code":732,"language":733,"meta":354,"style":354},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"mcpServers\": {\n    \"tileserver-rs\": {\n      \"command\": \"\u002Fusr\u002Flocal\u002Fbin\u002Ftileserver-rs\",\n      \"args\": [\"mcp-stdio\", \"--config\", \"\u002Fpath\u002Fto\u002Fconfig.toml\"]\n    }\n  }\n}\n","json",[64,735,736,741,759,773,795,837,842,847],{"__ignoreMap":354},[358,737,738],{"class":360,"line":361},[358,739,740],{"class":423},"{\n",[358,742,743,746,750,753,756],{"class":360,"line":433},[358,744,745],{"class":423},"  \"",[358,747,749],{"class":748},"spNyl","mcpServers",[358,751,752],{"class":423},"\"",[358,754,755],{"class":423},":",[358,757,758],{"class":423}," {\n",[358,760,761,764,767,769,771],{"class":360,"line":452},[358,762,763],{"class":423},"    \"",[358,765,766],{"class":364},"tileserver-rs",[358,768,752],{"class":423},[358,770,755],{"class":423},[358,772,758],{"class":423},[358,774,775,778,781,783,785,787,790,792],{"class":360,"line":464},[358,776,777],{"class":423},"      \"",[358,779,780],{"class":460},"command",[358,782,752],{"class":423},[358,784,755],{"class":423},[358,786,443],{"class":423},[358,788,789],{"class":368},"\u002Fusr\u002Flocal\u002Fbin\u002Ftileserver-rs",[358,791,752],{"class":423},[358,793,794],{"class":423},",\n",[358,796,797,799,802,804,806,809,811,814,816,819,821,824,826,828,830,833,835],{"class":360,"line":471},[358,798,777],{"class":423},[358,800,801],{"class":460},"args",[358,803,752],{"class":423},[358,805,755],{"class":423},[358,807,808],{"class":423}," [",[358,810,752],{"class":423},[358,812,813],{"class":368},"mcp-stdio",[358,815,752],{"class":423},[358,817,818],{"class":423},",",[358,820,443],{"class":423},[358,822,823],{"class":368},"--config",[358,825,752],{"class":423},[358,827,818],{"class":423},[358,829,443],{"class":423},[358,831,832],{"class":368},"\u002Fpath\u002Fto\u002Fconfig.toml",[358,834,752],{"class":423},[358,836,430],{"class":423},[358,838,839],{"class":360,"line":478},[358,840,841],{"class":423},"    }\n",[358,843,844],{"class":360,"line":484},[358,845,846],{"class":423},"  }\n",[358,848,849],{"class":360,"line":493},[358,850,851],{"class":423},"}\n",[10,853,854,855],{},"Restart Claude Desktop. Ask: ",[856,857,858],"em",{},"\"What tile sources are loaded on my server?\"",[333,860,862],{"id":861},"_3b-wire-into-cursor-copilot-cli-claude-code-stdio","3b. Wire into Cursor \u002F Copilot CLI \u002F Claude Code (stdio)",[10,864,865,866,868],{},"The same ",[64,867,813],{}," subcommand works for any stdio-speaking client. Add to your client's MCP config:",[349,870,872],{"className":731,"code":871,"language":733,"meta":354,"style":354},"{\n  \"tileserver-rs\": {\n    \"command\": \"tileserver-rs\",\n    \"args\": [\"mcp-stdio\"]\n  }\n}\n",[64,873,874,878,890,908,928,932],{"__ignoreMap":354},[358,875,876],{"class":360,"line":361},[358,877,740],{"class":423},[358,879,880,882,884,886,888],{"class":360,"line":433},[358,881,745],{"class":423},[358,883,766],{"class":748},[358,885,752],{"class":423},[358,887,755],{"class":423},[358,889,758],{"class":423},[358,891,892,894,896,898,900,902,904,906],{"class":360,"line":452},[358,893,763],{"class":423},[358,895,780],{"class":364},[358,897,752],{"class":423},[358,899,755],{"class":423},[358,901,443],{"class":423},[358,903,766],{"class":368},[358,905,752],{"class":423},[358,907,794],{"class":423},[358,909,910,912,914,916,918,920,922,924,926],{"class":360,"line":464},[358,911,763],{"class":423},[358,913,801],{"class":364},[358,915,752],{"class":423},[358,917,755],{"class":423},[358,919,808],{"class":423},[358,921,752],{"class":423},[358,923,813],{"class":368},[358,925,752],{"class":423},[358,927,430],{"class":423},[358,929,930],{"class":360,"line":471},[358,931,846],{"class":423},[358,933,934],{"class":360,"line":478},[358,935,851],{"class":423},[333,937,939],{"id":938},"_3c-use-over-http-cursor-claude-code-remote","3c. Use over HTTP (Cursor \u002F Claude Code remote)",[10,941,942,943,755],{},"Start tileserver-rs normally with ",[64,944,945],{},"[mcp].enabled = true",[349,947,949],{"className":351,"code":948,"language":353,"meta":354,"style":354},"tileserver-rs --config config.toml\n",[64,950,951],{"__ignoreMap":354},[358,952,953,955,958],{"class":360,"line":361},[358,954,766],{"class":364},[358,956,957],{"class":368}," --config",[358,959,960],{"class":368}," config.toml\n",[10,962,963,964,967,968,971,972,975],{},"Point your MCP client at ",[64,965,966],{},"http:\u002F\u002Flocalhost:8080\u002Fmcp",". If ",[64,969,970],{},"auth_token"," is set in config, add an ",[64,973,974],{},"Authorization: Bearer \u003Ctoken>"," header.",[333,977,979],{"id":978},"_3d-oauth-20-for-claudeai-custom-connectors","3d. OAuth 2.0 for claude.ai Custom Connectors",[10,981,982],{},"claude.ai's \"Custom Connectors\" feature requires OAuth 2.0 Dynamic Client Registration (RFC 7591). Static bearer tokens are not accepted.",[10,984,985],{},[14,986,987],{},"1. Generate a signing key:",[349,989,991],{"className":351,"code":990,"language":353,"meta":354,"style":354},"openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out \u002Fetc\u002Ftileserver\u002Fmcp-jwt-key.pem\nchmod 600 \u002Fetc\u002Ftileserver\u002Fmcp-jwt-key.pem\n",[64,992,993,1019],{"__ignoreMap":354},[358,994,995,998,1001,1004,1007,1010,1013,1016],{"class":360,"line":361},[358,996,997],{"class":364},"openssl",[358,999,1000],{"class":368}," genpkey",[358,1002,1003],{"class":368}," -algorithm",[358,1005,1006],{"class":368}," RSA",[358,1008,1009],{"class":368}," -pkeyopt",[358,1011,1012],{"class":368}," rsa_keygen_bits:2048",[358,1014,1015],{"class":368}," -out",[358,1017,1018],{"class":368}," \u002Fetc\u002Ftileserver\u002Fmcp-jwt-key.pem\n",[358,1020,1021,1024,1027],{"class":360,"line":433},[358,1022,1023],{"class":364},"chmod",[358,1025,1026],{"class":460}," 600",[358,1028,1018],{"class":368},[10,1030,1031,1034,1035,1037],{},[14,1032,1033],{},"2. Enable OAuth in your config"," (mutually exclusive with ",[64,1036,970],{},"):",[349,1039,1041],{"className":414,"code":1040,"language":416,"meta":354,"style":354},"[mcp]\nenabled = true\n\n[mcp.oauth]\nenabled = true\nissuer_url = \"https:\u002F\u002Ftiles.example.com\"   # must match your public URL\nsigning_key_path = \"\u002Fetc\u002Ftileserver\u002Fmcp-jwt-key.pem\"\napproval_secret = \"${MCP_APPROVAL_SECRET}\"  # you type this on the consent page\ntoken_ttl_secs = 3600                       # clamped at 24h\n",[64,1042,1043,1051,1059,1063,1076,1084,1101,1115,1132],{"__ignoreMap":354},[358,1044,1045,1047,1049],{"class":360,"line":361},[358,1046,424],{"class":423},[358,1048,339],{"class":364},[358,1050,430],{"class":423},[358,1052,1053,1055,1057],{"class":360,"line":433},[358,1054,496],{"class":436},[358,1056,440],{"class":423},[358,1058,502],{"class":501},[358,1060,1061],{"class":360,"line":452},[358,1062,468],{"emptyLinePlaceholder":467},[358,1064,1065,1067,1069,1071,1074],{"class":360,"line":464},[358,1066,424],{"class":423},[358,1068,339],{"class":364},[358,1070,711],{"class":436},[358,1072,1073],{"class":364},"oauth",[358,1075,430],{"class":423},[358,1077,1078,1080,1082],{"class":360,"line":471},[358,1079,496],{"class":436},[358,1081,440],{"class":423},[358,1083,502],{"class":501},[358,1085,1086,1089,1091,1093,1096,1098],{"class":360,"line":478},[358,1087,1088],{"class":436},"issuer_url ",[358,1090,440],{"class":423},[358,1092,443],{"class":423},[358,1094,1095],{"class":368},"https:\u002F\u002Ftiles.example.com",[358,1097,752],{"class":423},[358,1099,1100],{"class":474},"   # must match your public URL\n",[358,1102,1103,1106,1108,1110,1113],{"class":360,"line":484},[358,1104,1105],{"class":436},"signing_key_path ",[358,1107,440],{"class":423},[358,1109,443],{"class":423},[358,1111,1112],{"class":368},"\u002Fetc\u002Ftileserver\u002Fmcp-jwt-key.pem",[358,1114,449],{"class":423},[358,1116,1117,1120,1122,1124,1127,1129],{"class":360,"line":493},[358,1118,1119],{"class":436},"approval_secret ",[358,1121,440],{"class":423},[358,1123,443],{"class":423},[358,1125,1126],{"class":368},"${MCP_APPROVAL_SECRET}",[358,1128,752],{"class":423},[358,1130,1131],{"class":474},"  # you type this on the consent page\n",[358,1133,1134,1137,1139,1142],{"class":360,"line":505},[358,1135,1136],{"class":436},"token_ttl_secs ",[358,1138,440],{"class":423},[358,1140,1141],{"class":460}," 3600",[358,1143,1144],{"class":474},"                       # clamped at 24h\n",[10,1146,1147,1150,1151,1154,1155,1158],{},[64,1148,1149],{},"approval_secret"," is required. The consent page at ",[64,1152,1153],{},"\u002Fauthorize"," asks for it, and ",[64,1156,1157],{},"\u002Fapprove"," issues an authorization code only when it matches. Without it, anyone who reaches your server could approve their own client and get a token. Keep it private; you enter it once per connector you add.",[10,1160,1161],{},[14,1162,1163],{},"3. Add the connector in claude.ai:",[10,1165,1166],{},"In claude.ai → Settings → Custom Connectors → Add → paste:",[349,1168,1173],{"className":1169,"code":1171,"language":1172},[1170],"language-text","https:\u002F\u002Ftiles.example.com\n","text",[64,1174,1171],{"__ignoreMap":354},[10,1176,1177,1178,1181,1182,1185,1186,1188,1189,1192,1193,1195],{},"claude.ai discovers the OAuth flow via ",[64,1179,1180],{},"\u002F.well-known\u002Foauth-authorization-server",", dynamically registers itself via ",[64,1183,1184],{},"\u002Fregister",", walks the consent screen at ",[64,1187,1153],{},", exchanges the code at ",[64,1190,1191],{},"\u002Ftoken",", and finally talks MCP at ",[64,1194,286],{}," with the issued JWT bearer.",[10,1197,1198],{},[14,1199,1200,1201,755],{},"Endpoints exposed when ",[64,1202,1203],{},"[mcp.oauth].enabled = true",[32,1205,1206,1218],{},[35,1207,1208],{},[38,1209,1210,1213,1216],{},[41,1211,1212],{},"Endpoint",[41,1214,1215],{},"Method",[41,1217,49],{},[51,1219,1220,1232,1244,1256,1267,1281],{},[38,1221,1222,1226,1229],{},[56,1223,1224],{},[64,1225,1180],{},[56,1227,1228],{},"GET",[56,1230,1231],{},"RFC 8414 discovery",[38,1233,1234,1239,1241],{},[56,1235,1236],{},[64,1237,1238],{},"\u002F.well-known\u002Foauth-protected-resource",[56,1240,1228],{},[56,1242,1243],{},"RFC 9728 resource metadata",[38,1245,1246,1250,1253],{},[56,1247,1248],{},[64,1249,1184],{},[56,1251,1252],{},"POST",[56,1254,1255],{},"RFC 7591 Dynamic Client Registration",[38,1257,1258,1262,1264],{},[56,1259,1260],{},[64,1261,1153],{},[56,1263,1228],{},[56,1265,1266],{},"Consent screen (askama-rendered)",[38,1268,1269,1273,1275],{},[56,1270,1271],{},[64,1272,1157],{},[56,1274,1252],{},[56,1276,1277,1278,1280],{},"Approval (requires the operator ",[64,1279,1149],{},") → redirects with auth code",[38,1282,1283,1287,1289],{},[56,1284,1285],{},[64,1286,1191],{},[56,1288,1252],{},[56,1290,1291,1292,1295],{},"Authorization code & refresh token grants. Confidential clients must send ",[64,1293,1294],{},"client_secret","; revoked clients are refused",[10,1297,1298],{},[14,1299,1300],{},"Security defaults:",[1302,1303,1304,1316,1319,1325,1328],"ul",{},[1305,1306,1307,1308,1311,1312,1315],"li",{},"PKCE ",[64,1309,1310],{},"S256"," is mandatory; ",[64,1313,1314],{},"plain"," is rejected per the MCP spec.",[1305,1317,1318],{},"JWT access tokens are RS256-signed with the configured key.",[1305,1320,1321,1322,1324],{},"Refresh tokens rotate on every ",[64,1323,1191],{}," call.",[1305,1326,1327],{},"Token TTL is clamped at 86400s (24h) regardless of config.",[1305,1329,1330,1331,1334],{},"Tokens are stored in-memory — ",[14,1332,1333],{},"restarting the server invalidates all active tokens",". Plan deployments accordingly.",[308,1336,1338],{"type":1337},"warning",[10,1339,1340],{},"The in-memory token store is intentional for v1 simplicity. For zero-downtime deployments or long-lived sessions, run tileserver-rs behind a sticky-session reverse proxy and refresh tokens reasonably often (the default 1h TTL plus refresh rotation handles most cases).",[27,1342,1344],{"id":1343},"example-prompts","Example prompts",[10,1346,1347],{},"Once wired in, try:",[1302,1349,1350,1355,1360,1365,1374],{},[1305,1351,1352],{},[856,1353,1354],{},"\"List all tile sources on my server.\"",[1305,1356,1357],{},[856,1358,1359],{},"\"Render a static map of Paris at zoom 12, 800x600 pixels.\"",[1305,1361,1362],{},[856,1363,1364],{},"\"What vector layers are in the openmaptiles source?\"",[1305,1366,1367],{},[856,1368,1369,1370,1373],{},"\"Search the ",[64,1371,1372],{},"sentinel-2-l2a"," STAC collection for items over San Francisco from October 2024.\"",[1305,1375,1376],{},[856,1377,1378,1379,1382,1383,1386,1387,1390],{},"\"Use CQL2 to find all features in the ",[64,1380,1381],{},"buildings"," table where ",[64,1384,1385],{},"height > 50"," within bbox ",[64,1388,1389],{},"[-74, 40.7, -73.9, 40.8]",".\"",[27,1392,1394],{"id":1393},"architecture-notes","Architecture notes",[1302,1396,1397,1410,1427,1439,1449],{},[1305,1398,1399,1402,1403,1405,1406,1409],{},[14,1400,1401],{},"Same-port nest",": the Streamable HTTP service mounts at ",[64,1404,286],{}," inside the existing axum router, not on a separate ",[64,1407,1408],{},"TcpListener",". One bind, one reverse-proxy config.",[1305,1411,1412,1415,1416,1418,1419,1422,1423,1426],{},[14,1413,1414],{},"Renderer-aware",": ",[64,1417,151],{}," returns ",[64,1420,1421],{},"isError: true"," with a descriptive message when the ",[64,1424,1425],{},"raster"," feature is disabled instead of disappearing from the tool list.",[1305,1428,1429,1432,1433,1438],{},[14,1430,1431],{},"Image size discipline",": every static render goes through WebP@Q75 compression, capped at 2048×2048 dimensions and 1.5 MB bytes. The 5 MB hard limit on the Claude side can corrupt sessions irrecoverably (",[18,1434,1437],{"href":1435,"rel":1436},"https:\u002F\u002Fgithub.com\u002Fanthropics\u002Fclaude-code\u002Fissues\u002F2939",[22],"Claude Code #2939",") — better to refuse oversized requests than to ship them.",[1305,1440,1441,1444,1445,1448],{},[14,1442,1443],{},"Error contract",": backend failures return ",[64,1446,1447],{},"CallToolResult { isError: true }",", never JSON-RPC errors. JSON-RPC errors are reserved for protocol-level problems (parse failures, unknown methods).",[1305,1450,1451,1454,1455,1458,1459,1462],{},[14,1452,1453],{},"Feature-gated cascade",": CQL2 tools require ",[64,1456,1457],{},"postgres","; STAC search requires ",[64,1460,1461],{},"stac",". When those features are off, the tools either don't appear in the tool list or return a clear \"not available\" error.",[27,1464,1466],{"id":1465},"configuration-reference","Configuration reference",[32,1468,1469,1485],{},[35,1470,1471],{},[38,1472,1473,1476,1479,1482],{},[41,1474,1475],{},"Key",[41,1477,1478],{},"Type",[41,1480,1481],{},"Default",[41,1483,1484],{},"Description",[51,1486,1487,1510,1534,1557,1574,1591,1608,1628],{},[38,1488,1489,1494,1499,1504],{},[56,1490,1491],{},[64,1492,1493],{},"[mcp].enabled",[56,1495,1496],{},[64,1497,1498],{},"bool",[56,1500,1501],{},[64,1502,1503],{},"false",[56,1505,1506,1507,1509],{},"Nest the MCP HTTP service at ",[64,1508,286],{},". Stdio mode ignores this.",[38,1511,1512,1516,1521,1524],{},[56,1513,1514],{},[64,1515,297],{},[56,1517,1518],{},[64,1519,1520],{},"string?",[56,1522,1523],{},"unset",[56,1525,1526,1527,1530,1531,711],{},"Static bearer token for HTTP. Supports ",[64,1528,1529],{},"${VAR}"," env expansion. Mutually exclusive with ",[64,1532,1533],{},"[mcp.oauth].enabled",[38,1535,1536,1541,1546,1551],{},[56,1537,1538],{},[64,1539,1540],{},"[mcp].cors_origins",[56,1542,1543],{},[64,1544,1545],{},"Vec\u003Cstring>",[56,1547,1548],{},[64,1549,1550],{},"[\"*\"]",[56,1552,1553,1554,1556],{},"Allowed CORS origins for ",[64,1555,286],{},". Lock down in production.",[38,1558,1559,1563,1567,1571],{},[56,1560,1561],{},[64,1562,1533],{},[56,1564,1565],{},[64,1566,1498],{},[56,1568,1569],{},[64,1570,1503],{},[56,1572,1573],{},"Enable OAuth 2.0 DCR. Required for claude.ai Custom Connectors.",[38,1575,1576,1581,1586,1588],{},[56,1577,1578],{},[64,1579,1580],{},"[mcp.oauth].issuer_url",[56,1582,1583],{},[64,1584,1585],{},"string",[56,1587,1523],{},[56,1589,1590],{},"Public URL the server is reachable at. Required when oauth enabled.",[38,1592,1593,1598,1603,1605],{},[56,1594,1595],{},[64,1596,1597],{},"[mcp.oauth].signing_key_path",[56,1599,1600],{},[64,1601,1602],{},"path",[56,1604,1523],{},[56,1606,1607],{},"RSA PKCS#8 private key path for RS256 JWT signing. Required when oauth enabled.",[38,1609,1610,1615,1620,1625],{},[56,1611,1612],{},[64,1613,1614],{},"[mcp.oauth].token_ttl_secs",[56,1616,1617],{},[64,1618,1619],{},"u64",[56,1621,1622],{},[64,1623,1624],{},"3600",[56,1626,1627],{},"Access token TTL in seconds. Clamped to 86400 (24h) max.",[38,1629,1630,1635,1640,1642],{},[56,1631,1632],{},[64,1633,1634],{},"[mcp.oauth].store_path",[56,1636,1637],{},[64,1638,1639],{},"path?",[56,1641,1523],{},[56,1643,1644,1645,1648],{},"SQLite file persisting OAuth clients\u002Ftokens across restarts. Requires the ",[64,1646,1647],{},"mcp-persistence"," feature — startup fails fast if set without it. Unset = in-memory store.",[10,1650,1651,1652,1654,1655,1658],{},"The ",[64,1653,410],{}," block is only honored when tileserver-rs is built with ",[64,1656,1657],{},"--features mcp",". Without that feature, the block is ignored and no MCP code is compiled in.",[27,1660,1662],{"id":1661},"prompt-templates","Prompt templates",[10,1664,1665],{},"When connected, the server advertises four user-role prompt templates that LLM clients can surface as quick actions:",[32,1667,1668,1680],{},[35,1669,1670],{},[38,1671,1672,1675,1678],{},[41,1673,1674],{},"Prompt",[41,1676,1677],{},"Args",[41,1679,49],{},[51,1681,1682,1697,1716,1734],{},[38,1683,1684,1689,1694],{},[56,1685,1686],{},[64,1687,1688],{},"describe_style",[56,1690,1691],{},[64,1692,1693],{},"style_id",[56,1695,1696],{},"Ask the assistant to describe a style's design and use case.",[38,1698,1699,1704,1713],{},[56,1700,1701],{},[64,1702,1703],{},"suggest_cql2_filter",[56,1705,1706,1709,1710],{},[64,1707,1708],{},"table_id",", ",[64,1711,1712],{},"intent",[56,1714,1715],{},"Get a suggested CQL2 expression for a given intent.",[38,1717,1718,1723,1731],{},[56,1719,1720],{},[64,1721,1722],{},"render_location_preview",[56,1724,1725,1709,1728],{},[64,1726,1727],{},"location",[64,1729,1730],{},"zoom?",[56,1732,1733],{},"Render a static map preview of a place.",[38,1735,1736,1741,1746],{},[56,1737,1738],{},[64,1739,1740],{},"explain_tile_metadata",[56,1742,1743],{},[64,1744,1745],{},"source_id",[56,1747,1748],{},"Walk through a source's layers, zoom range, and contents.",[27,1750,1752],{"id":1751},"whats-deferred","What's deferred",[10,1754,1755],{},"These are planned for follow-up releases:",[1302,1757,1758,1768,1782,1798],{},[1305,1759,1760,1763,1764,1767],{},[14,1761,1762],{},"CIMD discovery"," (",[64,1765,1766],{},"oauth_cimd"," — Claude-Initiated Metadata Discovery, the high-traffic alternative to DCR).",[1305,1769,1770,1773,1774,1777,1778,1781],{},[14,1771,1772],{},"Persistent token store"," — current OAuth implementation uses an in-memory ",[64,1775,1776],{},"Arc\u003CRwLock\u003CHashMap>>"," token store. Server restart invalidates all tokens. A future release will add an ",[64,1779,1780],{},"sled"," or SQLite backend.",[1305,1783,1784,1763,1787,1709,1790,1793,1794,1797],{},[14,1785,1786],{},"Admin tools",[64,1788,1789],{},"reload_config",[64,1791,1792],{},"invalidate_cache",") — gated on a separate ",[64,1795,1796],{},"mcp-admin"," sub-feature to keep the security surface small.",[1305,1799,1800,1803,1804,1807,1808,711],{},[14,1801,1802],{},"Per-tool rate limiting"," for ",[64,1805,1806],{},"render_static_map"," and ",[64,1809,1810],{},"get_tile",[27,1812,1814],{"id":1813},"references","References",[1302,1816,1817,1824,1834,1841],{},[1305,1818,1819],{},[18,1820,1823],{"href":1821,"rel":1822},"https:\u002F\u002Fspec.modelcontextprotocol.io\u002F",[22],"Model Context Protocol specification",[1305,1825,1826],{},[18,1827,1830,1833],{"href":1828,"rel":1829},"https:\u002F\u002Fcrates.io\u002Fcrates\u002Frmcp",[22],[64,1831,1832],{},"rmcp"," Rust SDK on crates.io",[1305,1835,1836],{},[18,1837,1840],{"href":1838,"rel":1839},"https:\u002F\u002Fclaude.com\u002Fdocs\u002Fconnectors\u002Fbuilding\u002Fauthentication",[22],"Anthropic — Building MCP Connectors",[1305,1842,1843,1844],{},"Source: ",[18,1845,1848],{"href":1846,"rel":1847},"https:\u002F\u002Fgithub.com\u002Fvinayakkulkarni\u002Ftileserver-rs\u002Ftree\u002Fmain\u002Fcrates\u002Ftileserver-rs\u002Fsrc\u002Fmcp",[22],[64,1849,1850],{},"crates\u002Ftileserver-rs\u002Fsrc\u002Fmcp\u002F",[1852,1853,1854],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":354,"searchDepth":433,"depth":433,"links":1856},[1857,1858,1859,1869,1870,1871,1872,1873,1874],{"id":29,"depth":433,"text":30},{"id":242,"depth":433,"text":243},{"id":330,"depth":433,"text":331,"children":1860},[1861,1863,1865,1866,1867,1868],{"id":335,"depth":452,"text":1862},"1. Build with the mcp feature",{"id":406,"depth":452,"text":1864},"2. Add an [mcp] block to your config",{"id":714,"depth":452,"text":715},{"id":861,"depth":452,"text":862},{"id":938,"depth":452,"text":939},{"id":978,"depth":452,"text":979},{"id":1343,"depth":433,"text":1344},{"id":1393,"depth":433,"text":1394},{"id":1465,"depth":433,"text":1466},{"id":1661,"depth":433,"text":1662},{"id":1751,"depth":433,"text":1752},{"id":1813,"depth":433,"text":1814},"Wire your tileserver-rs deployment into Claude Desktop, Cursor, claude.ai, or Copilot CLI via the Model Context Protocol — 11 tools spanning introspection, rendering, CQL2 feature queries, and STAC search.","md",{},"\u002Fguides\u002Fmcp",{"title":5,"description":1875},"4.guides\u002F16.mcp","98L9Mq1KP__tHZWtzIT-mGcsAteyD0IwW1nRIKBbX2Y",1794150613923]