[{"data":1,"prerenderedAt":1207},["ShallowReactive",2],{"post-\u002Ffr\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website":3},{"id":4,"title":5,"body":6,"contentId":1196,"date":1197,"description":1198,"extension":100,"meta":1199,"navigation":225,"path":1200,"rawbody":1201,"robots":1202,"schemaOrg":1202,"seo":1203,"sitemap":1204,"stem":1205,"__hash__":1206},"posts\u002Ffr\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website.md","Un serveur Model Context Protocol (MCP) pour mon site web",{"type":7,"value":8,"toc":1190},"minimark",[9,17,34,50,55,58,91,94,149,155,161,170,184,190,194,197,203,237,348,351,355,366,1104,1107,1125,1131,1134,1145,1149,1152,1169,1176,1183,1186],[10,11,12,16],"p",{},[13,14,15],"strong",{},"Les Modèles de Langage de Grande Taille"," (LLMs) sont omniprésents ces jours-ci. Le rythme des avancées est stupéfiant, et il est difficile de suivre les mises à jour constantes, c'est tout simplement fou !",[10,18,19,20,23,24,33],{},"Au milieu de cette avalanche d'informations, j'ai trouvé le concept d'un ",[13,21,22],{},"agent",", particulièrement avec le ",[25,26,30],"a",{"href":27,"rel":28},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fintroduction",[29],"nofollow",[13,31,32],{},"Model Context Protocol"," (MCP), particulièrement intrigant et prometteur. J'ai donc décidé d'expérimenter avec cela et de voir comment cela pourrait améliorer mon flux de travail.",[35,36,37],"blockquote",{},[10,38,39,43,44,49],{},[40,41,42],"span",{},"!NOTE","\nVous préférez regarder une vidéo ? Consultez la version vidéo de cet article sur ",[25,45,48],{"href":46,"rel":47},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ",[29],"YouTube",".",[51,52,54],"h2",{"id":53},"le-défi-des-identifiants","Le défi des identifiants",[10,56,57],{},"Actuellement, mon site web se compose de deux composants clés :",[59,60,61,77],"ul",{},[62,63,64,65,68,69,76],"li",{},"Le ",[13,66,67],{},"frontend",", construit avec ",[25,70,73],{"href":71,"rel":72},"https:\u002F\u002Fvitepress.dev",[29],[13,74,75],{},"VitePress",", où chaque page est un fichier Markdown.",[62,78,64,79,82,83,90],{},[13,80,81],{},"backend",", une application ",[25,84,87],{"href":85,"rel":86},"https:\u002F\u002Flaravel.com",[29],[13,88,89],{},"Laravel"," qui ajoute des fonctionnalités supplémentaires.",[10,92,93],{},"Pour garantir que le bon contenu est servi et stocké pour chaque page frontend, je dois partager un identifiant entre ces composants. Chaque fichier Markdown a un identifiant unique dans son 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,127,136,144],"code",{"__ignoreMap":101},[40,106,109],{"class":107,"line":108},"line",1,[40,110,112],{"class":111},"sMK4o","---\n",[40,114,116,120,123],{"class":107,"line":115},2,[40,117,119],{"class":118},"swJcz","id",[40,121,122],{"class":111},":",[40,124,126],{"class":125},"sbssI"," 1\n",[40,128,130,133],{"class":107,"line":129},3,[40,131,132],{"class":118},"title",[40,134,135],{"class":111},":\n",[40,137,139,142],{"class":107,"line":138},4,[40,140,141],{"class":118},"description",[40,143,135],{"class":111},[40,145,147],{"class":107,"line":146},5,[40,148,112],{"class":111},[10,150,151,152,49],{},"Cet identifiant récupère le contenu correspondant du backend lors du chargement de la page. Pour que tout fonctionne sans accroc, le backend doit également stocker cet identifiant dans la base de données. Sans cela, il est impossible de maintenir une base de données relationnelle fonctionnelle, car ma base de données comprend une table ",[103,153,154],{},"posts",[10,156,157,158,160],{},"Pour obtenir ces identifiants, le backend récupère régulièrement un fichier JSON généré au moment de la construction par ",[13,159,75],{},". Ce fichier contient tous les fichiers Markdown et leur frontmatter. Cette approche fonctionne bien, mais il y a un hic.",[10,162,163,164,166,167,169],{},"Lorsque j'écris un nouveau fichier Markdown, je dois accéder à la page d'administration du backend, trier la table ",[103,165,154],{}," par la colonne ",[103,168,119],{},", et trouver le dernier identifiant utilisé. Ensuite, j'écris cet identifiant, incrémenté de un, dans le fichier sur lequel je travaille. C'est une tâche fastidieuse qui n'apporte aucune réelle valeur et consomme du temps, un processus que je déteste profondément.",[10,171,172,173,176,177,180,181],{},"Simultanément, j'utilise fréquemment les ",[13,174,175],{},"LLMs"," pour améliorer le contenu technique de mes articles, y compris le ",[13,178,179],{},"SEO",", la grammaire et l'orthographe. Cela m'a amené à me demander : ",[13,182,183],{},"Puis-je intégrer un agent dans le processus LLM actuel pour m'assurer que l'identifiant est toujours correct ?",[10,185,186],{},[187,188,189],"em",{},"Oui !",[51,191,193],{"id":192},"mise-en-place-dune-nouvelle-route-backend","Mise en place d'une nouvelle route backend",[10,195,196],{},"Les serveurs MCP ne sont pas mystiques ; ils fournissent un moyen pour les LLMs d'interagir avec des sources de données. Ainsi, je dois créer une route API backend pour renvoyer le prochain identifiant de publication. Cette route sera accessible par le serveur MCP via la capacité d'outils.",[10,198,199,200,202],{},"Créer cette route API dans mon application ",[13,201,89],{}," et configurer un contrôleur invocable est assez simple :",[95,204,209],{"className":205,"code":206,"filename":207,"language":208,"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,210,211,216,221,227,232],{"__ignoreMap":101},[40,212,213],{"class":107,"line":108},[40,214,215],{},"use App\\Http\\Controllers\\NextPostIdController;\n",[40,217,218],{"class":107,"line":115},[40,219,220],{},"use Illuminate\\Support\\Facades\\Route;\n",[40,222,223],{"class":107,"line":129},[40,224,226],{"emptyLinePlaceholder":225},true,"\n",[40,228,229],{"class":107,"line":138},[40,230,231],{},"Route::get('posts\u002Fnext-id', NextPostIdController::class)\n",[40,233,234],{"class":107,"line":146},[40,235,236],{},"    ->name('posts.next-id');\n",[95,238,241],{"className":205,"code":239,"filename":240,"language":208,"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,242,243,248,252,257,261,266,272,277,283,289,295,301,307,313,319,325,330,336,342],{"__ignoreMap":101},[40,244,245],{"class":107,"line":108},[40,246,247],{},"\u003C?php\n",[40,249,250],{"class":107,"line":115},[40,251,226],{"emptyLinePlaceholder":225},[40,253,254],{"class":107,"line":129},[40,255,256],{},"namespace App\\Http\\Controllers;\n",[40,258,259],{"class":107,"line":138},[40,260,226],{"emptyLinePlaceholder":225},[40,262,263],{"class":107,"line":146},[40,264,265],{},"use App\\Models\\Post;\n",[40,267,269],{"class":107,"line":268},6,[40,270,271],{},"use Illuminate\\Http\\JsonResponse;\n",[40,273,275],{"class":107,"line":274},7,[40,276,226],{"emptyLinePlaceholder":225},[40,278,280],{"class":107,"line":279},8,[40,281,282],{},"class NextPostIdController extends Controller\n",[40,284,286],{"class":107,"line":285},9,[40,287,288],{},"{\n",[40,290,292],{"class":107,"line":291},10,[40,293,294],{},"    \u002F**\n",[40,296,298],{"class":107,"line":297},11,[40,299,300],{},"     * Handle the incoming request.\n",[40,302,304],{"class":107,"line":303},12,[40,305,306],{},"     *\u002F\n",[40,308,310],{"class":107,"line":309},13,[40,311,312],{},"    public function __invoke(): JsonResponse\n",[40,314,316],{"class":107,"line":315},14,[40,317,318],{},"    {\n",[40,320,322],{"class":107,"line":321},15,[40,323,324],{},"        $nextPostId = Post::max('id') + 1;\n",[40,326,328],{"class":107,"line":327},16,[40,329,226],{"emptyLinePlaceholder":225},[40,331,333],{"class":107,"line":332},17,[40,334,335],{},"        return response()->json(['next_post_id' => $nextPostId]);\n",[40,337,339],{"class":107,"line":338},18,[40,340,341],{},"    }\n",[40,343,345],{"class":107,"line":344},19,[40,346,347],{},"}\n",[10,349,350],{},"Cette configuration, bien que simple, est cruciale pour un serveur MCP efficace.",[51,352,354],{"id":353},"configuration-du-serveur-mcp","Configuration du serveur MCP",[10,356,357,358,365],{},"Pour le serveur MCP, j'utilise le ",[25,359,362],{"href":360,"rel":361},"https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Ftypescript-sdk",[29],[13,363,364],{},"SDK TypeScript"," officiel. C'est simple et très efficace.",[95,367,372],{"className":368,"code":369,"filename":370,"language":371,"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: 'Obtenir le prochain ID de publication pour un post sur 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('Erreur lors de la récupération du prochain ID de post')\n    }\n  }\n\n  throw new Error('Outil inconnu')\n})\n\nconst transport = new StdioServerTransport()\nawait server.connect(transport)\n","src\u002Findex.ts","ts",[103,373,374,403,423,430,438,443,457,477,497,517,521,542,547,564,578,583,587,596,606,610,616,622,627,652,657,685,693,704,710,727,744,764,770,776,781,788,793,822,856,864,907,912,920,933,950,965,974,980,985,1000,1022,1027,1032,1037,1058,1065,1070,1087],{"__ignoreMap":101},[40,375,376,380,383,387,390,393,396,400],{"class":107,"line":108},[40,377,379],{"class":378},"s7zQu","import",[40,381,382],{"class":111}," {",[40,384,386],{"class":385},"sTEyZ"," Server",[40,388,389],{"class":111}," }",[40,391,392],{"class":378}," from",[40,394,395],{"class":111}," '",[40,397,399],{"class":398},"sfazB","@modelcontextprotocol\u002Fsdk\u002Fserver\u002Findex.js",[40,401,402],{"class":111},"'\n",[40,404,405,407,409,412,414,416,418,421],{"class":107,"line":115},[40,406,379],{"class":378},[40,408,382],{"class":111},[40,410,411],{"class":385}," StdioServerTransport",[40,413,389],{"class":111},[40,415,392],{"class":378},[40,417,395],{"class":111},[40,419,420],{"class":398},"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fstdio.js",[40,422,402],{"class":111},[40,424,425,427],{"class":107,"line":129},[40,426,379],{"class":378},[40,428,429],{"class":111}," {\n",[40,431,432,435],{"class":107,"line":138},[40,433,434],{"class":385},"  CallToolRequestSchema",[40,436,437],{"class":111},",\n",[40,439,440],{"class":107,"line":146},[40,441,442],{"class":385},"  ListToolsRequestSchema\n",[40,444,445,448,450,452,455],{"class":107,"line":268},[40,446,447],{"class":111},"}",[40,449,392],{"class":378},[40,451,395],{"class":111},[40,453,454],{"class":398},"@modelcontextprotocol\u002Fsdk\u002Ftypes.js",[40,456,402],{"class":111},[40,458,459,461,463,466,468,470,472,475],{"class":107,"line":274},[40,460,379],{"class":378},[40,462,382],{"class":111},[40,464,465],{"class":385}," ofetch",[40,467,389],{"class":111},[40,469,392],{"class":378},[40,471,395],{"class":111},[40,473,474],{"class":398},"ofetch",[40,476,402],{"class":111},[40,478,479,481,483,486,488,490,492,495],{"class":107,"line":279},[40,480,379],{"class":378},[40,482,382],{"class":111},[40,484,485],{"class":385}," z",[40,487,389],{"class":111},[40,489,392],{"class":378},[40,491,395],{"class":111},[40,493,494],{"class":398},"zod",[40,496,402],{"class":111},[40,498,499,501,503,506,508,510,512,515],{"class":107,"line":285},[40,500,379],{"class":378},[40,502,382],{"class":111},[40,504,505],{"class":385}," zodToJsonSchema",[40,507,389],{"class":111},[40,509,392],{"class":378},[40,511,395],{"class":111},[40,513,514],{"class":398},"zod-to-json-schema",[40,516,402],{"class":111},[40,518,519],{"class":107,"line":291},[40,520,226],{"emptyLinePlaceholder":225},[40,522,523,527,530,533,536,539],{"class":107,"line":297},[40,524,526],{"class":525},"spNyl","const",[40,528,529],{"class":385}," server ",[40,531,532],{"class":111},"=",[40,534,535],{"class":111}," new",[40,537,386],{"class":538},"s2Zo4",[40,540,541],{"class":385},"(\n",[40,543,544],{"class":107,"line":303},[40,545,546],{"class":111},"  {\n",[40,548,549,552,554,556,559,562],{"class":107,"line":309},[40,550,551],{"class":118},"    name",[40,553,122],{"class":111},[40,555,395],{"class":111},[40,557,558],{"class":398},"mcp.soubiran.dev",[40,560,561],{"class":111},"'",[40,563,437],{"class":111},[40,565,566,569,571,573,576],{"class":107,"line":315},[40,567,568],{"class":118},"    version",[40,570,122],{"class":111},[40,572,395],{"class":111},[40,574,575],{"class":398},"0.0.0",[40,577,402],{"class":111},[40,579,580],{"class":107,"line":321},[40,581,582],{"class":111},"  },\n",[40,584,585],{"class":107,"line":327},[40,586,546],{"class":111},[40,588,589,592,594],{"class":107,"line":332},[40,590,591],{"class":118},"    capabilities",[40,593,122],{"class":111},[40,595,429],{"class":111},[40,597,598,601,603],{"class":107,"line":338},[40,599,600],{"class":118},"      tools",[40,602,122],{"class":111},[40,604,605],{"class":111}," {}\n",[40,607,608],{"class":107,"line":344},[40,609,341],{"class":111},[40,611,613],{"class":107,"line":612},20,[40,614,615],{"class":111},"  }\n",[40,617,619],{"class":107,"line":618},21,[40,620,621],{"class":385},")\n",[40,623,625],{"class":107,"line":624},22,[40,626,226],{"emptyLinePlaceholder":225},[40,628,630,632,635,637,639,641,644,647,650],{"class":107,"line":629},23,[40,631,526],{"class":525},[40,633,634],{"class":385}," GetNextId ",[40,636,532],{"class":111},[40,638,485],{"class":385},[40,640,49],{"class":111},[40,642,643],{"class":538},"object",[40,645,646],{"class":385},"(",[40,648,649],{"class":111},"{}",[40,651,621],{"class":385},[40,653,655],{"class":107,"line":654},24,[40,656,226],{"emptyLinePlaceholder":225},[40,658,660,663,665,668,671,674,677,680,683],{"class":107,"line":659},25,[40,661,662],{"class":385},"server",[40,664,49],{"class":111},[40,666,667],{"class":538},"setRequestHandler",[40,669,670],{"class":385},"(ListToolsRequestSchema",[40,672,673],{"class":111},",",[40,675,676],{"class":525}," async",[40,678,679],{"class":111}," ()",[40,681,682],{"class":525}," =>",[40,684,429],{"class":111},[40,686,688,691],{"class":107,"line":687},26,[40,689,690],{"class":378},"  return",[40,692,429],{"class":111},[40,694,696,699,701],{"class":107,"line":695},27,[40,697,698],{"class":118},"    tools",[40,700,122],{"class":111},[40,702,703],{"class":118}," [\n",[40,705,707],{"class":107,"line":706},28,[40,708,709],{"class":111},"      {\n",[40,711,713,716,718,720,723,725],{"class":107,"line":712},29,[40,714,715],{"class":118},"        name",[40,717,122],{"class":111},[40,719,395],{"class":111},[40,721,722],{"class":398},"next_post_id",[40,724,561],{"class":111},[40,726,437],{"class":111},[40,728,730,733,735,737,740,742],{"class":107,"line":729},30,[40,731,732],{"class":118},"        description",[40,734,122],{"class":111},[40,736,395],{"class":111},[40,738,739],{"class":398},"Obtenir le prochain ID de publication pour un post sur soubiran.dev",[40,741,561],{"class":111},[40,743,437],{"class":111},[40,745,747,750,752,754,756,759,762],{"class":107,"line":746},31,[40,748,749],{"class":118},"        inputSchema",[40,751,122],{"class":111},[40,753,505],{"class":538},[40,755,646],{"class":118},[40,757,758],{"class":385},"GetNextId",[40,760,761],{"class":118},")",[40,763,437],{"class":111},[40,765,767],{"class":107,"line":766},32,[40,768,769],{"class":111},"      },\n",[40,771,773],{"class":107,"line":772},33,[40,774,775],{"class":118},"    ]\n",[40,777,779],{"class":107,"line":778},34,[40,780,615],{"class":111},[40,782,784,786],{"class":107,"line":783},35,[40,785,447],{"class":111},[40,787,621],{"class":385},[40,789,791],{"class":107,"line":790},36,[40,792,226],{"emptyLinePlaceholder":225},[40,794,796,798,800,802,805,807,809,812,816,818,820],{"class":107,"line":795},37,[40,797,662],{"class":385},[40,799,49],{"class":111},[40,801,667],{"class":538},[40,803,804],{"class":385},"(CallToolRequestSchema",[40,806,673],{"class":111},[40,808,676],{"class":525},[40,810,811],{"class":111}," (",[40,813,815],{"class":814},"sHdIc","request",[40,817,761],{"class":111},[40,819,682],{"class":525},[40,821,429],{"class":111},[40,823,825,828,830,832,834,837,839,842,845,847,849,851,854],{"class":107,"line":824},38,[40,826,827],{"class":378},"  if",[40,829,811],{"class":118},[40,831,815],{"class":385},[40,833,49],{"class":111},[40,835,836],{"class":385},"params",[40,838,49],{"class":111},[40,840,841],{"class":385},"name",[40,843,844],{"class":111}," ===",[40,846,395],{"class":111},[40,848,722],{"class":398},[40,850,561],{"class":111},[40,852,853],{"class":118},") ",[40,855,288],{"class":111},[40,857,859,862],{"class":107,"line":858},39,[40,860,861],{"class":378},"    try",[40,863,429],{"class":111},[40,865,867,870,873,876,879,881,884,887,889,893,896,898,900,903,905],{"class":107,"line":866},40,[40,868,869],{"class":525},"      const",[40,871,872],{"class":385}," data",[40,874,875],{"class":111}," =",[40,877,878],{"class":378}," await",[40,880,465],{"class":538},[40,882,883],{"class":111},"\u003C{",[40,885,886],{"class":118}," next_post_id",[40,888,122],{"class":111},[40,890,892],{"class":891},"sBMFI"," number",[40,894,895],{"class":111}," }>",[40,897,646],{"class":118},[40,899,561],{"class":111},[40,901,902],{"class":398},"http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fposts\u002Fnext-id",[40,904,561],{"class":111},[40,906,621],{"class":118},[40,908,910],{"class":107,"line":909},41,[40,911,226],{"emptyLinePlaceholder":225},[40,913,915,918],{"class":107,"line":914},42,[40,916,917],{"class":378},"      return",[40,919,429],{"class":111},[40,921,923,926,928,931],{"class":107,"line":922},43,[40,924,925],{"class":118},"        content",[40,927,122],{"class":111},[40,929,930],{"class":118}," [",[40,932,288],{"class":111},[40,934,936,939,941,943,946,948],{"class":107,"line":935},44,[40,937,938],{"class":118},"          type",[40,940,122],{"class":111},[40,942,395],{"class":111},[40,944,945],{"class":398},"text",[40,947,561],{"class":111},[40,949,437],{"class":111},[40,951,953,956,958,960,962],{"class":107,"line":952},45,[40,954,955],{"class":118},"          text",[40,957,122],{"class":111},[40,959,872],{"class":385},[40,961,49],{"class":111},[40,963,964],{"class":385},"next_post_id\n",[40,966,968,971],{"class":107,"line":967},46,[40,969,970],{"class":111},"        }",[40,972,973],{"class":118},"]\n",[40,975,977],{"class":107,"line":976},47,[40,978,979],{"class":111},"      }\n",[40,981,983],{"class":107,"line":982},48,[40,984,341],{"class":111},[40,986,988,991,993,996,998],{"class":107,"line":987},49,[40,989,990],{"class":378},"    catch",[40,992,811],{"class":118},[40,994,995],{"class":385},"error",[40,997,853],{"class":118},[40,999,288],{"class":111},[40,1001,1003,1006,1008,1011,1013,1015,1018,1020],{"class":107,"line":1002},50,[40,1004,1005],{"class":378},"      throw",[40,1007,535],{"class":111},[40,1009,1010],{"class":538}," Error",[40,1012,646],{"class":118},[40,1014,561],{"class":111},[40,1016,1017],{"class":398},"Erreur lors de la récupération du prochain ID de post",[40,1019,561],{"class":111},[40,1021,621],{"class":118},[40,1023,1025],{"class":107,"line":1024},51,[40,1026,341],{"class":111},[40,1028,1030],{"class":107,"line":1029},52,[40,1031,615],{"class":111},[40,1033,1035],{"class":107,"line":1034},53,[40,1036,226],{"emptyLinePlaceholder":225},[40,1038,1040,1043,1045,1047,1049,1051,1054,1056],{"class":107,"line":1039},54,[40,1041,1042],{"class":378},"  throw",[40,1044,535],{"class":111},[40,1046,1010],{"class":538},[40,1048,646],{"class":118},[40,1050,561],{"class":111},[40,1052,1053],{"class":398},"Outil inconnu",[40,1055,561],{"class":111},[40,1057,621],{"class":118},[40,1059,1061,1063],{"class":107,"line":1060},55,[40,1062,447],{"class":111},[40,1064,621],{"class":385},[40,1066,1068],{"class":107,"line":1067},56,[40,1069,226],{"emptyLinePlaceholder":225},[40,1071,1073,1075,1078,1080,1082,1084],{"class":107,"line":1072},57,[40,1074,526],{"class":525},[40,1076,1077],{"class":385}," transport ",[40,1079,532],{"class":111},[40,1081,535],{"class":111},[40,1083,411],{"class":538},[40,1085,1086],{"class":385},"()\n",[40,1088,1090,1093,1096,1098,1101],{"class":107,"line":1089},58,[40,1091,1092],{"class":378},"await",[40,1094,1095],{"class":385}," server",[40,1097,49],{"class":111},[40,1099,1100],{"class":538},"connect",[40,1102,1103],{"class":385},"(transport)\n",[10,1105,1106],{},"Ce code établit un serveur MCP STDIO avec deux gestionnaires de requêtes clés :",[1108,1109,1110,1119],"ol",{},[62,1111,1112,1113,1116,1117,49],{},"Le gestionnaire ",[103,1114,1115],{},"ListToolsRequest"," renvoie les outils disponibles à l'agent. Actuellement, il y a un outil : ",[103,1118,722],{},[62,1120,1112,1121,1124],{},[103,1122,1123],{},"CallToolRequest"," est activé lorsque l'agent utilise l'outil. Il appelle la route API backend pour récupérer le prochain ID de post.",[10,1126,1127,1128,1130],{},"Le processus d'appel d'outil est simple ; il récupère le prochain ID de post depuis la route API backend que j'ai définie, en utilisant la bibliothèque ",[103,1129,474],{}," pour plus de facilité. L'ID de publication est ensuite renvoyé sous forme de nombre.",[10,1132,1133],{},"Par conséquent, tout agent invoquant cet outil recevra le prochain ID de publication en réponse dans son contexte. Cela me permet de rédiger des fichiers Markdown sans me soucier des IDs de post, l'agent s'en occupe parfaitement !",[35,1135,1136],{},[10,1137,1138,1140,1141,49],{},[40,1139,42],{},"\nPour le code complet, regardez la ",[25,1142,1144],{"href":46,"rel":1143},[29],"vidéo sur YouTube",[51,1146,1148],{"id":1147},"ça-fonctionne","Ça fonctionne ?",[10,1150,1151],{},"Oui, cela fonctionne parfaitement ! Voyez-le en action :",[1153,1154,1155,1156,1155,1165],"figure",{},"\n  ",[1157,1158,1159,1160,1155],"video",{"autoPlay":225,"loop":225,"muted":225,"playsInline":225},"\n    ",[1161,1162],"source",{"src":1163,"type":1164},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website\u002Fworking-mcp-soubiran-dev.mp4","video\u002Fmp4",[1166,1167,1168],"figcaption",{},"Demande à l'agent d'améliorer un fichier et d'insérer automatiquement l'ID correct en utilisant le prochain ID de publication du serveur MCP.",[10,1170,1171,1172,1175],{},"Avec ce ",[13,1173,1174],{},"serveur MCP"," opérationnel, je peux désormais interagir avec mes fichiers Markdown tout en fournissant un contexte à l'agent. Cela rationalise mon flux de travail, me permettant de me concentrer sur le contenu plutôt que sur les détails techniques. Mon objectif ultime est d'accélérer mon processus d'écriture, le rendant plus efficace et agréable, et ce logiciel est une étape vers cet objectif.",[10,1177,1178,1179,1182],{},"Je suis impressionné par la facilité d'utilisation du SDK MCP et son intégration avec ",[13,1180,1181],{},"VS Code",". Il permet de créer rapidement de nouveaux agents et d'incorporer facilement le flux de travail.",[10,1184,1185],{},"Je suis impatient d'explorer les possibilités que cela ouvre. L'avenir des agents est prometteur, et je recommande vivement d'expérimenter avec eux !",[1187,1188,1189],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .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":1191},[1192,1193,1194,1195],{"id":53,"depth":115,"text":54},{"id":192,"depth":115,"text":193},{"id":353,"depth":115,"text":354},{"id":1147,"depth":115,"text":1148},"5326cb9f-fd51-4d43-bff8-2cee2a995d89","2025-05-10","Explorer le potentiel du Model Context Protocol (MCP) en l'intégrant dans mon flux de travail et en optimisant les opérations backend de mon site web.",{},"\u002Ffr\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website","---\ncontentId: 5326cb9f-fd51-4d43-bff8-2cee2a995d89\ntitle: \"Un serveur Model Context Protocol (MCP) pour mon site web\"\ndescription: \"Explorer le potentiel du Model Context Protocol (MCP) en l'intégrant dans mon flux de travail et en optimisant les opérations backend de mon site web.\"\ndate: 2025-05-10\n---\n\n**Les Modèles de Langage de Grande Taille** (LLMs) sont omniprésents ces jours-ci. Le rythme des avancées est stupéfiant, et il est difficile de suivre les mises à jour constantes, c'est tout simplement fou !\n\nAu milieu de cette avalanche d'informations, j'ai trouvé le concept d'un **agent**, particulièrement avec le [**Model Context Protocol**](https:\u002F\u002Fmodelcontextprotocol.io\u002Fintroduction) (MCP), particulièrement intrigant et prometteur. J'ai donc décidé d'expérimenter avec cela et de voir comment cela pourrait améliorer mon flux de travail.\n\n> [!NOTE]\n> Vous préférez regarder une vidéo ? Consultez la version vidéo de cet article sur [YouTube](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ).\n\n## Le défi des identifiants\n\nActuellement, mon site web se compose de deux composants clés :\n\n- Le **frontend**, construit avec [**VitePress**](https:\u002F\u002Fvitepress.dev), où chaque page est un fichier Markdown.\n- Le **backend**, une application [**Laravel**](https:\u002F\u002Flaravel.com) qui ajoute des fonctionnalités supplémentaires.\n\nPour garantir que le bon contenu est servi et stocké pour chaque page frontend, je dois partager un identifiant entre ces composants. Chaque fichier Markdown a un identifiant unique dans son frontmatter.\n\n```md [src\u002Fposts\u002Fa-simple-markdown-file.md]\n---\nid: 1\ntitle:\ndescription:\n---\n```\n\nCet identifiant récupère le contenu correspondant du backend lors du chargement de la page. Pour que tout fonctionne sans accroc, le backend doit également stocker cet identifiant dans la base de données. Sans cela, il est impossible de maintenir une base de données relationnelle fonctionnelle, car ma base de données comprend une table `posts`.\n\nPour obtenir ces identifiants, le backend récupère régulièrement un fichier JSON généré au moment de la construction par **VitePress**. Ce fichier contient tous les fichiers Markdown et leur frontmatter. Cette approche fonctionne bien, mais il y a un hic.\n\nLorsque j'écris un nouveau fichier Markdown, je dois accéder à la page d'administration du backend, trier la table `posts` par la colonne `id`, et trouver le dernier identifiant utilisé. Ensuite, j'écris cet identifiant, incrémenté de un, dans le fichier sur lequel je travaille. C'est une tâche fastidieuse qui n'apporte aucune réelle valeur et consomme du temps, un processus que je déteste profondément.\n\nSimultanément, j'utilise fréquemment les **LLMs** pour améliorer le contenu technique de mes articles, y compris le **SEO**, la grammaire et l'orthographe. Cela m'a amené à me demander : **Puis-je intégrer un agent dans le processus LLM actuel pour m'assurer que l'identifiant est toujours correct ?**\n\n_Oui !_\n\n## Mise en place d'une nouvelle route backend\n\nLes serveurs MCP ne sont pas mystiques ; ils fournissent un moyen pour les LLMs d'interagir avec des sources de données. Ainsi, je dois créer une route API backend pour renvoyer le prochain identifiant de publication. Cette route sera accessible par le serveur MCP via la capacité d'outils.\n\nCréer cette route API dans mon application **Laravel** et configurer un contrôleur invocable est assez simple :\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\nCette configuration, bien que simple, est cruciale pour un serveur MCP efficace.\n\n## Configuration du serveur MCP\n\nPour le serveur MCP, j'utilise le [**SDK TypeScript**](https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Ftypescript-sdk) officiel. C'est simple et très efficace.\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: 'Obtenir le prochain ID de publication pour un post sur 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('Erreur lors de la récupération du prochain ID de post')\n    }\n  }\n\n  throw new Error('Outil inconnu')\n})\n\nconst transport = new StdioServerTransport()\nawait server.connect(transport)\n```\n\nCe code établit un serveur MCP STDIO avec deux gestionnaires de requêtes clés :\n\n1. Le gestionnaire `ListToolsRequest` renvoie les outils disponibles à l'agent. Actuellement, il y a un outil : `next_post_id`.\n2. Le gestionnaire `CallToolRequest` est activé lorsque l'agent utilise l'outil. Il appelle la route API backend pour récupérer le prochain ID de post.\n\nLe processus d'appel d'outil est simple ; il récupère le prochain ID de post depuis la route API backend que j'ai définie, en utilisant la bibliothèque `ofetch` pour plus de facilité. L'ID de publication est ensuite renvoyé sous forme de nombre.\n\nPar conséquent, tout agent invoquant cet outil recevra le prochain ID de publication en réponse dans son contexte. Cela me permet de rédiger des fichiers Markdown sans me soucier des IDs de post, l'agent s'en occupe parfaitement !\n\n> [!NOTE]\n> Pour le code complet, regardez la [vidéo sur YouTube](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ).\n\n## Ça fonctionne ?\n\nOui, cela fonctionne parfaitement ! Voyez-le en 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>Demande à l'agent d'améliorer un fichier et d'insérer automatiquement l'ID correct en utilisant le prochain ID de publication du serveur MCP.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nAvec ce **serveur MCP** opérationnel, je peux désormais interagir avec mes fichiers Markdown tout en fournissant un contexte à l'agent. Cela rationalise mon flux de travail, me permettant de me concentrer sur le contenu plutôt que sur les détails techniques. Mon objectif ultime est d'accélérer mon processus d'écriture, le rendant plus efficace et agréable, et ce logiciel est une étape vers cet objectif.\n\nJe suis impressionné par la facilité d'utilisation du SDK MCP et son intégration avec **VS Code**. Il permet de créer rapidement de nouveaux agents et d'incorporer facilement le flux de travail.\n\nJe suis impatient d'explorer les possibilités que cela ouvre. L'avenir des agents est prometteur, et je recommande vivement d'expérimenter avec eux !\n",null,{"title":5,"description":1198},{"loc":1200},"fr\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website","ygVysYx8b1QIWhqlAjROxmI7h27lDmb-L2qGfHZbKVM",1790086903593]