[{"data":1,"prerenderedAt":1194},["ShallowReactive",2],{"post-\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website":3},{"id":4,"title":5,"body":6,"contentId":1183,"date":1184,"description":1185,"extension":100,"meta":1186,"navigation":215,"path":1187,"rawbody":1188,"robots":1189,"schemaOrg":1189,"seo":1190,"sitemap":1191,"stem":1192,"__hash__":1193},"posts\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website.md","A Model Context Protocol (MCP) Server for My Website",{"type":7,"value":8,"toc":1177},"minimark",[9,17,34,50,55,58,91,94,137,144,150,160,174,180,184,187,193,227,338,341,345,356,1095,1098,1115,1121,1124,1135,1139,1142,1159,1166,1173],[10,11,12,16],"p",{},[13,14,15],"strong",{},"Large Language Models"," (LLMs) are ubiquitous these days. The pace of advancements is staggering, and it's challenging to keep up with the constant updates, it's absolutely insane!",[10,18,19,20,23,24,33],{},"Amidst the overwhelming information, I found the concept of an ",[13,21,22],{},"agent",", especially with the ",[25,26,30],"a",{"href":27,"rel":28},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fintroduction",[29],"nofollow",[13,31,32],{},"Model Context Protocol"," (MCP), particularly intriguing and promising. So, I decided to experiment with it and see how it could enhance my workflow.",[35,36,37],"blockquote",{},[10,38,39,43,44,49],{},[40,41,42],"span",{},"!NOTE","\nPrefer watching a video? Check out the video version of this article on ",[25,45,48],{"href":46,"rel":47},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ",[29],"YouTube",".",[51,52,54],"h2",{"id":53},"the-challenge-with-identifiers","The challenge with identifiers",[10,56,57],{},"Currently, my website consists of two key components:",[59,60,61,77],"ul",{},[62,63,64,65,68,69,76],"li",{},"The ",[13,66,67],{},"frontend",", built with ",[25,70,73],{"href":71,"rel":72},"https:\u002F\u002Fvitepress.dev",[29],[13,74,75],{},"VitePress",", where each page is a Markdown file.",[62,78,64,79,82,83,90],{},[13,80,81],{},"backend",", a ",[25,84,87],{"href":85,"rel":86},"https:\u002F\u002Flaravel.com",[29],[13,88,89],{},"Laravel"," application that adds additional features and functionality.",[10,92,93],{},"To ensure the correct content is served and stored for each frontend page, I need to share an identifier between these components. Each Markdown file has a unique identifier in its frontmatter.",[95,96,102],"pre",{"className":97,"code":98,"filename":99,"language":100,"meta":101,"style":101},"language-md shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","---\nid: 1\ntitle:\ndescription:\n---\n","src\u002Fposts\u002Fa-simple-markdown-file.md","md","",[103,104,105,113,119,125,131],"code",{"__ignoreMap":101},[40,106,109],{"class":107,"line":108},"line",1,[40,110,112],{"class":111},"sTEyZ","---\n",[40,114,116],{"class":107,"line":115},2,[40,117,118],{"class":111},"id: 1\n",[40,120,122],{"class":107,"line":121},3,[40,123,124],{"class":111},"title:\n",[40,126,128],{"class":107,"line":127},4,[40,129,130],{"class":111},"description:\n",[40,132,134],{"class":107,"line":133},5,[40,135,112],{"class":136},"sMK4o",[10,138,139,140,143],{},"This identifier fetches the corresponding content from the backend when loading the page. For everything to work seamlessly, the backend must also store this identifier in the database. Without it, maintaining a functional relational database is impossible, as my database includes a ",[103,141,142],{},"posts"," table.",[10,145,146,147,149],{},"To obtain these identifiers, the backend routinely fetches a JSON file generated at build time by ",[13,148,75],{},". This file contains all the Markdown files and their frontmatter. This approach works well, but there's a catch.",[10,151,152,153,155,156,159],{},"When writing a new Markdown file, I must access the backend administration page, sort the ",[103,154,142],{}," table by the ",[103,157,158],{},"id"," column, and find the last used identifier. Then, I write this identifier, incremented by one, into the file I'm working on. It's a tedious task that adds no real value and consumes time, a process I thoroughly dislike.",[10,161,162,163,166,167,170,171],{},"Simultaneously, I frequently leverage ",[13,164,165],{},"LLMs"," to enhance my articles' technical content, including ",[13,168,169],{},"SEO",", grammar, and spelling. This led me to wonder: ",[13,172,173],{},"Can I integrate an agent within the current LLM process to ensure that the identifier is always correct?",[10,175,176],{},[177,178,179],"em",{},"Short answer: yes!",[51,181,183],{"id":182},"implementing-a-new-backend-route","Implementing a new backend route",[10,185,186],{},"MCP servers aren't mystical; they provide a means for LLMs to interact with data sources. Hence, I need to create a backend API route to return the next post identifier. This route will be accessed by the MCP server via the tools capability.",[10,188,189,190,192],{},"Creating this API route in my ",[13,191,89],{}," application and setting up an invokable controller is fairly straightforward:",[95,194,199],{"className":195,"code":196,"filename":197,"language":198,"meta":101,"style":101},"language-php shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","use App\\Http\\Controllers\\NextPostIdController;\nuse Illuminate\\Support\\Facades\\Route;\n\nRoute::get('posts\u002Fnext-id', NextPostIdController::class)\n    ->name('posts.next-id');\n","routes\u002Fapi.php","php",[103,200,201,206,211,217,222],{"__ignoreMap":101},[40,202,203],{"class":107,"line":108},[40,204,205],{},"use App\\Http\\Controllers\\NextPostIdController;\n",[40,207,208],{"class":107,"line":115},[40,209,210],{},"use Illuminate\\Support\\Facades\\Route;\n",[40,212,213],{"class":107,"line":121},[40,214,216],{"emptyLinePlaceholder":215},true,"\n",[40,218,219],{"class":107,"line":127},[40,220,221],{},"Route::get('posts\u002Fnext-id', NextPostIdController::class)\n",[40,223,224],{"class":107,"line":133},[40,225,226],{},"    ->name('posts.next-id');\n",[95,228,231],{"className":195,"code":229,"filename":230,"language":198,"meta":101,"style":101},"\u003C?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Models\\Post;\nuse Illuminate\\Http\\JsonResponse;\n\nclass NextPostIdController extends Controller\n{\n    \u002F**\n     * Handle the incoming request.\n     *\u002F\n    public function __invoke(): JsonResponse\n    {\n        $nextPostId = Post::max('id') + 1;\n\n        return response()->json(['next_post_id' => $nextPostId]);\n    }\n}\n","app\u002FHttp\u002FControllers\u002FNextPostIdController.php",[103,232,233,238,242,247,251,256,262,267,273,279,285,291,297,303,309,315,320,326,332],{"__ignoreMap":101},[40,234,235],{"class":107,"line":108},[40,236,237],{},"\u003C?php\n",[40,239,240],{"class":107,"line":115},[40,241,216],{"emptyLinePlaceholder":215},[40,243,244],{"class":107,"line":121},[40,245,246],{},"namespace App\\Http\\Controllers;\n",[40,248,249],{"class":107,"line":127},[40,250,216],{"emptyLinePlaceholder":215},[40,252,253],{"class":107,"line":133},[40,254,255],{},"use App\\Models\\Post;\n",[40,257,259],{"class":107,"line":258},6,[40,260,261],{},"use Illuminate\\Http\\JsonResponse;\n",[40,263,265],{"class":107,"line":264},7,[40,266,216],{"emptyLinePlaceholder":215},[40,268,270],{"class":107,"line":269},8,[40,271,272],{},"class NextPostIdController extends Controller\n",[40,274,276],{"class":107,"line":275},9,[40,277,278],{},"{\n",[40,280,282],{"class":107,"line":281},10,[40,283,284],{},"    \u002F**\n",[40,286,288],{"class":107,"line":287},11,[40,289,290],{},"     * Handle the incoming request.\n",[40,292,294],{"class":107,"line":293},12,[40,295,296],{},"     *\u002F\n",[40,298,300],{"class":107,"line":299},13,[40,301,302],{},"    public function __invoke(): JsonResponse\n",[40,304,306],{"class":107,"line":305},14,[40,307,308],{},"    {\n",[40,310,312],{"class":107,"line":311},15,[40,313,314],{},"        $nextPostId = Post::max('id') + 1;\n",[40,316,318],{"class":107,"line":317},16,[40,319,216],{"emptyLinePlaceholder":215},[40,321,323],{"class":107,"line":322},17,[40,324,325],{},"        return response()->json(['next_post_id' => $nextPostId]);\n",[40,327,329],{"class":107,"line":328},18,[40,330,331],{},"    }\n",[40,333,335],{"class":107,"line":334},19,[40,336,337],{},"}\n",[10,339,340],{},"This setup, although simple, is crucial for an effective MCP server.",[51,342,344],{"id":343},"configuring-the-mcp-server","Configuring the MCP server",[10,346,347,348,355],{},"For the MCP server, I'm utilizing the official ",[25,349,352],{"href":350,"rel":351},"https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Ftypescript-sdk",[29],[13,353,354],{},"TypeScript SDK",". It's straightforward and highly efficient.",[95,357,362],{"className":358,"code":359,"filename":360,"language":361,"meta":101,"style":101},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { Server } from '@modelcontextprotocol\u002Fsdk\u002Fserver\u002Findex.js'\nimport { StdioServerTransport } from '@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fstdio.js'\nimport {\n  CallToolRequestSchema,\n  ListToolsRequestSchema\n} from '@modelcontextprotocol\u002Fsdk\u002Ftypes.js'\nimport { ofetch } from 'ofetch'\nimport { z } from 'zod'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\n\nconst server = new Server(\n  {\n    name: 'mcp.soubiran.dev',\n    version: '0.0.0'\n  },\n  {\n    capabilities: {\n      tools: {}\n    }\n  }\n)\n\nconst GetNextId = z.object({})\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n  return {\n    tools: [\n      {\n        name: 'next_post_id',\n        description: 'Get the next post ID for a post on soubiran.dev',\n        inputSchema: zodToJsonSchema(GetNextId),\n      },\n    ]\n  }\n})\n\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n  if (request.params.name === 'next_post_id') {\n    try {\n      const data = await ofetch\u003C{ next_post_id: number }>('http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fposts\u002Fnext-id')\n\n      return {\n        content: [{\n          type: 'text',\n          text: data.next_post_id\n        }]\n      }\n    }\n    catch (error) {\n      throw new Error('Error fetching next post ID')\n    }\n  }\n\n  throw new Error('Unknown tool')\n})\n\nconst transport = new StdioServerTransport()\nawait server.connect(transport)\n","src\u002Findex.ts","ts",[103,363,364,392,412,419,427,432,446,466,486,506,510,531,536,555,569,574,578,587,597,601,607,613,618,643,648,676,684,695,701,718,735,755,761,767,772,779,784,813,847,855,898,903,911,924,941,956,965,971,976,991,1013,1018,1023,1028,1049,1056,1061,1078],{"__ignoreMap":101},[40,365,366,370,373,376,379,382,385,389],{"class":107,"line":108},[40,367,369],{"class":368},"s7zQu","import",[40,371,372],{"class":136}," {",[40,374,375],{"class":111}," Server",[40,377,378],{"class":136}," }",[40,380,381],{"class":368}," from",[40,383,384],{"class":136}," '",[40,386,388],{"class":387},"sfazB","@modelcontextprotocol\u002Fsdk\u002Fserver\u002Findex.js",[40,390,391],{"class":136},"'\n",[40,393,394,396,398,401,403,405,407,410],{"class":107,"line":115},[40,395,369],{"class":368},[40,397,372],{"class":136},[40,399,400],{"class":111}," StdioServerTransport",[40,402,378],{"class":136},[40,404,381],{"class":368},[40,406,384],{"class":136},[40,408,409],{"class":387},"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fstdio.js",[40,411,391],{"class":136},[40,413,414,416],{"class":107,"line":121},[40,415,369],{"class":368},[40,417,418],{"class":136}," {\n",[40,420,421,424],{"class":107,"line":127},[40,422,423],{"class":111},"  CallToolRequestSchema",[40,425,426],{"class":136},",\n",[40,428,429],{"class":107,"line":133},[40,430,431],{"class":111},"  ListToolsRequestSchema\n",[40,433,434,437,439,441,444],{"class":107,"line":258},[40,435,436],{"class":136},"}",[40,438,381],{"class":368},[40,440,384],{"class":136},[40,442,443],{"class":387},"@modelcontextprotocol\u002Fsdk\u002Ftypes.js",[40,445,391],{"class":136},[40,447,448,450,452,455,457,459,461,464],{"class":107,"line":264},[40,449,369],{"class":368},[40,451,372],{"class":136},[40,453,454],{"class":111}," ofetch",[40,456,378],{"class":136},[40,458,381],{"class":368},[40,460,384],{"class":136},[40,462,463],{"class":387},"ofetch",[40,465,391],{"class":136},[40,467,468,470,472,475,477,479,481,484],{"class":107,"line":269},[40,469,369],{"class":368},[40,471,372],{"class":136},[40,473,474],{"class":111}," z",[40,476,378],{"class":136},[40,478,381],{"class":368},[40,480,384],{"class":136},[40,482,483],{"class":387},"zod",[40,485,391],{"class":136},[40,487,488,490,492,495,497,499,501,504],{"class":107,"line":275},[40,489,369],{"class":368},[40,491,372],{"class":136},[40,493,494],{"class":111}," zodToJsonSchema",[40,496,378],{"class":136},[40,498,381],{"class":368},[40,500,384],{"class":136},[40,502,503],{"class":387},"zod-to-json-schema",[40,505,391],{"class":136},[40,507,508],{"class":107,"line":281},[40,509,216],{"emptyLinePlaceholder":215},[40,511,512,516,519,522,525,528],{"class":107,"line":287},[40,513,515],{"class":514},"spNyl","const",[40,517,518],{"class":111}," server ",[40,520,521],{"class":136},"=",[40,523,524],{"class":136}," new",[40,526,375],{"class":527},"s2Zo4",[40,529,530],{"class":111},"(\n",[40,532,533],{"class":107,"line":293},[40,534,535],{"class":136},"  {\n",[40,537,538,542,545,547,550,553],{"class":107,"line":299},[40,539,541],{"class":540},"swJcz","    name",[40,543,544],{"class":136},":",[40,546,384],{"class":136},[40,548,549],{"class":387},"mcp.soubiran.dev",[40,551,552],{"class":136},"'",[40,554,426],{"class":136},[40,556,557,560,562,564,567],{"class":107,"line":305},[40,558,559],{"class":540},"    version",[40,561,544],{"class":136},[40,563,384],{"class":136},[40,565,566],{"class":387},"0.0.0",[40,568,391],{"class":136},[40,570,571],{"class":107,"line":311},[40,572,573],{"class":136},"  },\n",[40,575,576],{"class":107,"line":317},[40,577,535],{"class":136},[40,579,580,583,585],{"class":107,"line":322},[40,581,582],{"class":540},"    capabilities",[40,584,544],{"class":136},[40,586,418],{"class":136},[40,588,589,592,594],{"class":107,"line":328},[40,590,591],{"class":540},"      tools",[40,593,544],{"class":136},[40,595,596],{"class":136}," {}\n",[40,598,599],{"class":107,"line":334},[40,600,331],{"class":136},[40,602,604],{"class":107,"line":603},20,[40,605,606],{"class":136},"  }\n",[40,608,610],{"class":107,"line":609},21,[40,611,612],{"class":111},")\n",[40,614,616],{"class":107,"line":615},22,[40,617,216],{"emptyLinePlaceholder":215},[40,619,621,623,626,628,630,632,635,638,641],{"class":107,"line":620},23,[40,622,515],{"class":514},[40,624,625],{"class":111}," GetNextId ",[40,627,521],{"class":136},[40,629,474],{"class":111},[40,631,49],{"class":136},[40,633,634],{"class":527},"object",[40,636,637],{"class":111},"(",[40,639,640],{"class":136},"{}",[40,642,612],{"class":111},[40,644,646],{"class":107,"line":645},24,[40,647,216],{"emptyLinePlaceholder":215},[40,649,651,654,656,659,662,665,668,671,674],{"class":107,"line":650},25,[40,652,653],{"class":111},"server",[40,655,49],{"class":136},[40,657,658],{"class":527},"setRequestHandler",[40,660,661],{"class":111},"(ListToolsRequestSchema",[40,663,664],{"class":136},",",[40,666,667],{"class":514}," async",[40,669,670],{"class":136}," ()",[40,672,673],{"class":514}," =>",[40,675,418],{"class":136},[40,677,679,682],{"class":107,"line":678},26,[40,680,681],{"class":368},"  return",[40,683,418],{"class":136},[40,685,687,690,692],{"class":107,"line":686},27,[40,688,689],{"class":540},"    tools",[40,691,544],{"class":136},[40,693,694],{"class":540}," [\n",[40,696,698],{"class":107,"line":697},28,[40,699,700],{"class":136},"      {\n",[40,702,704,707,709,711,714,716],{"class":107,"line":703},29,[40,705,706],{"class":540},"        name",[40,708,544],{"class":136},[40,710,384],{"class":136},[40,712,713],{"class":387},"next_post_id",[40,715,552],{"class":136},[40,717,426],{"class":136},[40,719,721,724,726,728,731,733],{"class":107,"line":720},30,[40,722,723],{"class":540},"        description",[40,725,544],{"class":136},[40,727,384],{"class":136},[40,729,730],{"class":387},"Get the next post ID for a post on soubiran.dev",[40,732,552],{"class":136},[40,734,426],{"class":136},[40,736,738,741,743,745,747,750,753],{"class":107,"line":737},31,[40,739,740],{"class":540},"        inputSchema",[40,742,544],{"class":136},[40,744,494],{"class":527},[40,746,637],{"class":540},[40,748,749],{"class":111},"GetNextId",[40,751,752],{"class":540},")",[40,754,426],{"class":136},[40,756,758],{"class":107,"line":757},32,[40,759,760],{"class":136},"      },\n",[40,762,764],{"class":107,"line":763},33,[40,765,766],{"class":540},"    ]\n",[40,768,770],{"class":107,"line":769},34,[40,771,606],{"class":136},[40,773,775,777],{"class":107,"line":774},35,[40,776,436],{"class":136},[40,778,612],{"class":111},[40,780,782],{"class":107,"line":781},36,[40,783,216],{"emptyLinePlaceholder":215},[40,785,787,789,791,793,796,798,800,803,807,809,811],{"class":107,"line":786},37,[40,788,653],{"class":111},[40,790,49],{"class":136},[40,792,658],{"class":527},[40,794,795],{"class":111},"(CallToolRequestSchema",[40,797,664],{"class":136},[40,799,667],{"class":514},[40,801,802],{"class":136}," (",[40,804,806],{"class":805},"sHdIc","request",[40,808,752],{"class":136},[40,810,673],{"class":514},[40,812,418],{"class":136},[40,814,816,819,821,823,825,828,830,833,836,838,840,842,845],{"class":107,"line":815},38,[40,817,818],{"class":368},"  if",[40,820,802],{"class":540},[40,822,806],{"class":111},[40,824,49],{"class":136},[40,826,827],{"class":111},"params",[40,829,49],{"class":136},[40,831,832],{"class":111},"name",[40,834,835],{"class":136}," ===",[40,837,384],{"class":136},[40,839,713],{"class":387},[40,841,552],{"class":136},[40,843,844],{"class":540},") ",[40,846,278],{"class":136},[40,848,850,853],{"class":107,"line":849},39,[40,851,852],{"class":368},"    try",[40,854,418],{"class":136},[40,856,858,861,864,867,870,872,875,878,880,884,887,889,891,894,896],{"class":107,"line":857},40,[40,859,860],{"class":514},"      const",[40,862,863],{"class":111}," data",[40,865,866],{"class":136}," =",[40,868,869],{"class":368}," await",[40,871,454],{"class":527},[40,873,874],{"class":136},"\u003C{",[40,876,877],{"class":540}," next_post_id",[40,879,544],{"class":136},[40,881,883],{"class":882},"sBMFI"," number",[40,885,886],{"class":136}," }>",[40,888,637],{"class":540},[40,890,552],{"class":136},[40,892,893],{"class":387},"http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fposts\u002Fnext-id",[40,895,552],{"class":136},[40,897,612],{"class":540},[40,899,901],{"class":107,"line":900},41,[40,902,216],{"emptyLinePlaceholder":215},[40,904,906,909],{"class":107,"line":905},42,[40,907,908],{"class":368},"      return",[40,910,418],{"class":136},[40,912,914,917,919,922],{"class":107,"line":913},43,[40,915,916],{"class":540},"        content",[40,918,544],{"class":136},[40,920,921],{"class":540}," [",[40,923,278],{"class":136},[40,925,927,930,932,934,937,939],{"class":107,"line":926},44,[40,928,929],{"class":540},"          type",[40,931,544],{"class":136},[40,933,384],{"class":136},[40,935,936],{"class":387},"text",[40,938,552],{"class":136},[40,940,426],{"class":136},[40,942,944,947,949,951,953],{"class":107,"line":943},45,[40,945,946],{"class":540},"          text",[40,948,544],{"class":136},[40,950,863],{"class":111},[40,952,49],{"class":136},[40,954,955],{"class":111},"next_post_id\n",[40,957,959,962],{"class":107,"line":958},46,[40,960,961],{"class":136},"        }",[40,963,964],{"class":540},"]\n",[40,966,968],{"class":107,"line":967},47,[40,969,970],{"class":136},"      }\n",[40,972,974],{"class":107,"line":973},48,[40,975,331],{"class":136},[40,977,979,982,984,987,989],{"class":107,"line":978},49,[40,980,981],{"class":368},"    catch",[40,983,802],{"class":540},[40,985,986],{"class":111},"error",[40,988,844],{"class":540},[40,990,278],{"class":136},[40,992,994,997,999,1002,1004,1006,1009,1011],{"class":107,"line":993},50,[40,995,996],{"class":368},"      throw",[40,998,524],{"class":136},[40,1000,1001],{"class":527}," Error",[40,1003,637],{"class":540},[40,1005,552],{"class":136},[40,1007,1008],{"class":387},"Error fetching next post ID",[40,1010,552],{"class":136},[40,1012,612],{"class":540},[40,1014,1016],{"class":107,"line":1015},51,[40,1017,331],{"class":136},[40,1019,1021],{"class":107,"line":1020},52,[40,1022,606],{"class":136},[40,1024,1026],{"class":107,"line":1025},53,[40,1027,216],{"emptyLinePlaceholder":215},[40,1029,1031,1034,1036,1038,1040,1042,1045,1047],{"class":107,"line":1030},54,[40,1032,1033],{"class":368},"  throw",[40,1035,524],{"class":136},[40,1037,1001],{"class":527},[40,1039,637],{"class":540},[40,1041,552],{"class":136},[40,1043,1044],{"class":387},"Unknown tool",[40,1046,552],{"class":136},[40,1048,612],{"class":540},[40,1050,1052,1054],{"class":107,"line":1051},55,[40,1053,436],{"class":136},[40,1055,612],{"class":111},[40,1057,1059],{"class":107,"line":1058},56,[40,1060,216],{"emptyLinePlaceholder":215},[40,1062,1064,1066,1069,1071,1073,1075],{"class":107,"line":1063},57,[40,1065,515],{"class":514},[40,1067,1068],{"class":111}," transport ",[40,1070,521],{"class":136},[40,1072,524],{"class":136},[40,1074,400],{"class":527},[40,1076,1077],{"class":111},"()\n",[40,1079,1081,1084,1087,1089,1092],{"class":107,"line":1080},58,[40,1082,1083],{"class":368},"await",[40,1085,1086],{"class":111}," server",[40,1088,49],{"class":136},[40,1090,1091],{"class":527},"connect",[40,1093,1094],{"class":111},"(transport)\n",[10,1096,1097],{},"This code establishes a STDIO MCP server with two key request handlers:",[1099,1100,1101,1109],"ol",{},[62,1102,64,1103,1106,1107,49],{},[103,1104,1105],{},"ListToolsRequest"," handler returns the available tools to the agent. Currently, there is one tool: ",[103,1108,713],{},[62,1110,64,1111,1114],{},[103,1112,1113],{},"CallToolRequest"," handler is activated when the agent uses the tool. It calls the backend API route to retrieve the next post ID.",[10,1116,1117,1118,1120],{},"The tool call process is simple; it fetches the next post ID from the backend API route I've defined, using the ",[103,1119,463],{}," library for ease. The post ID is then returned as a number.",[10,1122,1123],{},"Consequently, any agent invoking this tool will receive the next post ID as a response within its context. This allows me to draft Markdown files without worrying about post IDs, the agent handles it perfectly!",[35,1125,1126],{},[10,1127,1128,1130,1131,49],{},[40,1129,42],{},"\nFor the complete code, watch the ",[25,1132,1134],{"href":46,"rel":1133},[29],"YouTube video",[51,1136,1138],{"id":1137},"does-it-work","Does it work?",[10,1140,1141],{},"Yes, it works seamlessly! See it in action:",[1143,1144,1145,1146,1145,1155],"figure",{},"\n  ",[1147,1148,1149,1150,1145],"video",{"autoPlay":215,"loop":215,"muted":215,"playsInline":215},"\n    ",[1151,1152],"source",{"src":1153,"type":1154},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website\u002Fworking-mcp-soubiran-dev.mp4","video\u002Fmp4",[1156,1157,1158],"figcaption",{},"Requesting the agent to enhance a file and insert the correct ID automatically using the next post ID from the MCP server.",[10,1160,1161,1162,1165],{},"With this ",[13,1163,1164],{},"MCP server"," operational, I can now interact with my Markdown files while supplying context to the agent. This allows me to focus on content rather than technical details. My ultimate aim is to hasten my writing process, making it more efficient and enjoyable, and this software is a step towards that goal.",[10,1167,1168,1169,1172],{},"I'm thoroughly impressed by the MCP SDK's user-friendliness and integration with ",[13,1170,1171],{},"VS Code",". The future of agents is promising, and I highly recommend experimenting with them!",[1174,1175,1176],"style",{},"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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":101,"searchDepth":115,"depth":115,"links":1178},[1179,1180,1181,1182],{"id":53,"depth":115,"text":54},{"id":182,"depth":115,"text":183},{"id":343,"depth":115,"text":344},{"id":1137,"depth":115,"text":1138},"4ff51ee2-926d-4924-86f5-97cd9e378fa7","2025-05-10","Exploring the potential of the Model Context Protocol (MCP) by integrating it into my workflow and optimizing my website's backend operations.",{},"\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website","---\ncontentId: 4ff51ee2-926d-4924-86f5-97cd9e378fa7\ntitle: \"A Model Context Protocol (MCP) Server for My Website\"\ndescription: \"Exploring the potential of the Model Context Protocol (MCP) by integrating it into my workflow and optimizing my website's backend operations.\"\ndate: 2025-05-10\n---\n\n**Large Language Models** (LLMs) are ubiquitous these days. The pace of advancements is staggering, and it's challenging to keep up with the constant updates, it's absolutely insane!\n\nAmidst the overwhelming information, I found the concept of an **agent**, especially with the [**Model Context Protocol**](https:\u002F\u002Fmodelcontextprotocol.io\u002Fintroduction) (MCP), particularly intriguing and promising. So, I decided to experiment with it and see how it could enhance my workflow.\n\n> [!NOTE]\n> Prefer watching a video? Check out the video version of this article on [YouTube](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ).\n\n## The challenge with identifiers\n\nCurrently, my website consists of two key components:\n\n- The **frontend**, built with [**VitePress**](https:\u002F\u002Fvitepress.dev), where each page is a Markdown file.\n- The **backend**, a [**Laravel**](https:\u002F\u002Flaravel.com) application that adds additional features and functionality.\n\nTo ensure the correct content is served and stored for each frontend page, I need to share an identifier between these components. Each Markdown file has a unique identifier in its frontmatter.\n\n```md [src\u002Fposts\u002Fa-simple-markdown-file.md]\n---\nid: 1\ntitle:\ndescription:\n---\n```\n\nThis identifier fetches the corresponding content from the backend when loading the page. For everything to work seamlessly, the backend must also store this identifier in the database. Without it, maintaining a functional relational database is impossible, as my database includes a `posts` table.\n\nTo obtain these identifiers, the backend routinely fetches a JSON file generated at build time by **VitePress**. This file contains all the Markdown files and their frontmatter. This approach works well, but there's a catch.\n\nWhen writing a new Markdown file, I must access the backend administration page, sort the `posts` table by the `id` column, and find the last used identifier. Then, I write this identifier, incremented by one, into the file I'm working on. It's a tedious task that adds no real value and consumes time, a process I thoroughly dislike.\n\nSimultaneously, I frequently leverage **LLMs** to enhance my articles' technical content, including **SEO**, grammar, and spelling. This led me to wonder: **Can I integrate an agent within the current LLM process to ensure that the identifier is always correct?**\n\n_Short answer: yes!_\n\n## Implementing a new backend route\n\nMCP servers aren't mystical; they provide a means for LLMs to interact with data sources. Hence, I need to create a backend API route to return the next post identifier. This route will be accessed by the MCP server via the tools capability.\n\nCreating this API route in my **Laravel** application and setting up an invokable controller is fairly straightforward:\n\n```php [routes\u002Fapi.php]\nuse App\\Http\\Controllers\\NextPostIdController;\nuse Illuminate\\Support\\Facades\\Route;\n\nRoute::get('posts\u002Fnext-id', NextPostIdController::class)\n    ->name('posts.next-id');\n```\n\n```php [app\u002FHttp\u002FControllers\u002FNextPostIdController.php]\n\u003C?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Models\\Post;\nuse Illuminate\\Http\\JsonResponse;\n\nclass NextPostIdController extends Controller\n{\n    \u002F**\n     * Handle the incoming request.\n     *\u002F\n    public function __invoke(): JsonResponse\n    {\n        $nextPostId = Post::max('id') + 1;\n\n        return response()->json(['next_post_id' => $nextPostId]);\n    }\n}\n```\n\nThis setup, although simple, is crucial for an effective MCP server.\n\n## Configuring the MCP server\n\nFor the MCP server, I'm utilizing the official [**TypeScript SDK**](https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Ftypescript-sdk). It's straightforward and highly efficient.\n\n```ts [src\u002Findex.ts]\nimport { Server } from '@modelcontextprotocol\u002Fsdk\u002Fserver\u002Findex.js'\nimport { StdioServerTransport } from '@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fstdio.js'\nimport {\n  CallToolRequestSchema,\n  ListToolsRequestSchema\n} from '@modelcontextprotocol\u002Fsdk\u002Ftypes.js'\nimport { ofetch } from 'ofetch'\nimport { z } from 'zod'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\n\nconst server = new Server(\n  {\n    name: 'mcp.soubiran.dev',\n    version: '0.0.0'\n  },\n  {\n    capabilities: {\n      tools: {}\n    }\n  }\n)\n\nconst GetNextId = z.object({})\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n  return {\n    tools: [\n      {\n        name: 'next_post_id',\n        description: 'Get the next post ID for a post on soubiran.dev',\n        inputSchema: zodToJsonSchema(GetNextId),\n      },\n    ]\n  }\n})\n\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n  if (request.params.name === 'next_post_id') {\n    try {\n      const data = await ofetch\u003C{ next_post_id: number }>('http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fposts\u002Fnext-id')\n\n      return {\n        content: [{\n          type: 'text',\n          text: data.next_post_id\n        }]\n      }\n    }\n    catch (error) {\n      throw new Error('Error fetching next post ID')\n    }\n  }\n\n  throw new Error('Unknown tool')\n})\n\nconst transport = new StdioServerTransport()\nawait server.connect(transport)\n```\n\nThis code establishes a STDIO MCP server with two key request handlers:\n\n1. The `ListToolsRequest` handler returns the available tools to the agent. Currently, there is one tool: `next_post_id`.\n2. The `CallToolRequest` handler is activated when the agent uses the tool. It calls the backend API route to retrieve the next post ID.\n\nThe tool call process is simple; it fetches the next post ID from the backend API route I've defined, using the `ofetch` library for ease. The post ID is then returned as a number.\n\nConsequently, any agent invoking this tool will receive the next post ID as a response within its context. This allows me to draft Markdown files without worrying about post IDs, the agent handles it perfectly!\n\n> [!NOTE]\n> For the complete code, watch the [YouTube video](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ).\n\n## Does it work?\n\nYes, it works seamlessly! See it in action:\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website\u002Fworking-mcp-soubiran-dev.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Requesting the agent to enhance a file and insert the correct ID automatically using the next post ID from the MCP server.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nWith this **MCP server** operational, I can now interact with my Markdown files while supplying context to the agent. This allows me to focus on content rather than technical details. My ultimate aim is to hasten my writing process, making it more efficient and enjoyable, and this software is a step towards that goal.\n\nI'm thoroughly impressed by the MCP SDK's user-friendliness and integration with **VS Code**. The future of agents is promising, and I highly recommend experimenting with them!\n",null,{"title":5,"description":1185},{"loc":1187},"posts\u002Fa-model-context-protocol-mcp-server-for-my-website","YNWY8DOJmhb_q5sIL8EKVe4RHVZ5VSL9bqOf1tiZV3M",1790086900831]