From 65d0f9c36e0f5968ec2e7da657a56ddadb8e6bf0 Mon Sep 17 00:00:00 2001 From: jsboige Date: Wed, 30 Sep 2026 13:40:16 +0200 Subject: [PATCH 01/15] feat(genai,#18573): SK-08-MCP refonte PR1 -- vrai FastMCP + ClientSession + MCPStdioPlugin Refonte structurelle des sections 1-4 du carnet 08-SemanticKernel-MCP.ipynb. Le carnet d'origine simulait MCP sans aucun echange de protocole ; cette PR pose les organes reels : - Section 3 : un vrai serveur FastMCP en sous-processus stdio (deux outils prix_ttc et calcule_tva, signatures typees en Pydantic). Le client ClientSession affiche initialize / tools/list / tools/call et lit les schemas inputSchema + outputSchema generes par la decoration. La validation Pydantic des arguments (gt=0, ge=0, le=100) est montree cote serveur avant l'execution. - Section 4 : Semantic Kernel consomme le serveur via semantic_kernel.connectors.mcp.MCPStdioPlugin. Chaque outil du serveur devient une KernelFunction, l'invocation suit la voie SK normale (kernel.invoke), et la validation du serveur remonte au niveau SK. - Sections 1-2 : introduction (pourquoi MCP, trois acteurs) et architecture (Tools/Resources/Prompts, JSON-RPC, schemas). - Mentions explicites de ce que la version precedente simulait : * "L'integration native SK+MCP est en cours de developpement" est refute par la disponibilite de MCPStdioPlugin 1.42.0. * "@anthropic/mcp-server-filesystem" n'existe pas sur npm ; les serveurs de reference sont "@modelcontextprotocol/server-filesystem". PR1 (sections 0-4). PR2 c.1338 couvrira les sections 5-7 + la re-ancrage des trois exemples guides etudiants de #18553 sous forme d'outils MCP reels, plus le sens inverse (Kernel.as_mcp_server()). Outputs : non executes localement (kernel ipykernel Windows + stdio subprocess = UnsupportedOperation fileno sur stderr ; le smoke test standalone passe, le test de bout en bout sous nbconvert echoue sur cette combinaison). Body PR detaille la cause exacte et l'action de suivi. Les cellules portent des stubs propres (C.1) et seront re-executees sous CI Linux (ubuntu-latest) qui n'a pas ce probleme. Co-Authored-By: Claude Haiku 4.5 (1M context) --- .../08-SemanticKernel-MCP.ipynb | 2078 +++-------------- 1 file changed, 281 insertions(+), 1797 deletions(-) diff --git a/MyIA.AI.Notebooks/GenAI/SemanticKernel/08-SemanticKernel-MCP.ipynb b/MyIA.AI.Notebooks/GenAI/SemanticKernel/08-SemanticKernel-MCP.ipynb index a74e9a7d5f..09d8fbc7f8 100644 --- a/MyIA.AI.Notebooks/GenAI/SemanticKernel/08-SemanticKernel-MCP.ipynb +++ b/MyIA.AI.Notebooks/GenAI/SemanticKernel/08-SemanticKernel-MCP.ipynb @@ -2,10 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "bd9f46f9", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "# SK-8-MCP : Model Context Protocol et Integration\n", "\n", @@ -16,20 +13,20 @@ "## Objectifs d'apprentissage\n", "\n", "A la fin de ce notebook, vous saurez :\n", - "1. Comprendre le **Model Context Protocol (MCP)**\n", - "2. Identifier les **serveurs MCP** disponibles\n", - "3. Integrer des **outils MCP** comme plugins SK\n", - "4. Créer un **bridge Agent Framework ↔ MCP**\n", - "5. Concevoir un **serveur MCP personnalise**\n", + "1. Comprendre le **Model Context Protocol (MCP)** et son role d'interoperabilite\n", + "2. Lire un **`inputSchema`** et un **`outputSchema`** generes par la decoration d'un outil\n", + "3. Lancer un **vrai serveur MCP** dans un sous-processus stdio et l'interroger\n", + "4. Connecter **Semantic Kernel** a un serveur MCP via `MCPStdioPlugin`\n", + "5. Distinguer un **vrai echange MCP** (initialize / tools/list / tools/call) d'une simulation\n", "\n", "### Prerequis\n", "\n", "- Python 3.10+\n", "- Notebooks 01-07 completes\n", "- Comprehension des Agents SK (notebook 03)\n", - "- Node.js installe (pour certains serveurs MCP)\n", + "- **Aucun runtime externe requis** (pas de Node.js) — le serveur utilise `FastMCP` en Python\n", "\n", - "### Duree estimee : 45 minutes\n", + "### Duree estimee : 50 minutes\n", "\n", "***\n", "\n", @@ -37,1932 +34,419 @@ "\n", "| Section | Contenu | Concepts cles |\n", "|---------|---------|---------------|\n", - "| 1 | Introduction | Qu'est-ce que MCP ? |\n", - "| 2 | Architecture | Tools, Resources, Prompts |\n", - "| 3 | Serveurs existants | Filesystem, GitHub, Database |\n", - "| 4 | SK + MCP | Integration comme plugins |\n", - "| 5 | Agent + MCP | Bridge avec Agent Framework |\n", - "| 6 | Serveur custom | Créer son propre serveur |\n", - "| 7 | Conclusion | Resume, exercices |\n", - "\n", - "> **Model Context Protocol (MCP)** : Standard ouvert initie par Anthropic et adopte par Microsoft pour permettre aux LLMs d'acceder a des outils externes de maniere standardisee. C'est l'evolution des plugins vers l'interoperabilite." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "de07375f", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "MCP SDK installe\n" - ] - } - ], - "source": [ - "# Installation\n", - "\n", - "import os\n", - "from dotenv import load_dotenv\n", + "| 1 | Introduction | MCP comme standard d'interoperabilite |\n", + "| 2 | Architecture | Tools, Resources, Prompts, JSON-RPC |\n", + "| 3 | Serveur reel | FastMCP + ClientSession + schémas |\n", + "| 4 | SK + MCP | MCPStdioPlugin |\n", + "| 5 | Agent + MCP | (PR2 — bridge FunctionChoiceBehavior) |\n", + "| 6 | Sens inverse | (PR2 — `Kernel.as_mcp_server()`) |\n", + "| 7 | Conclusion | (PR2 — recap et exercices re-ancres) |\n", "\n", - "load_dotenv()\n", - "print(\"MCP SDK installe\")" + "> **Model Context Protocol (MCP)** : standard ouvert initie par Anthropic en novembre 2024, adopte par Microsoft Semantic Kernel et de nombreux autres frameworks, qui definit comment un client (Claude Code, SK, LangChain, AutoGen, etc.) consomme les outils exposes par un serveur. La force du protocole tient dans la **decoration** : la signature de chaque outil, son type de retour et sa docstring produisent automatiquement le `JSON Schema` que le modele lit pour decider quel outil appeler et comment.\n" ] }, { "cell_type": "markdown", - "id": "1b072c98", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "## 1. Introduction au Model Context Protocol\n", "\n", "### Pourquoi MCP ?\n", "\n", - "Avant MCP, chaque framework avait son propre système de plugins :\n", + "Avant MCP, chaque framework d'agent avait son propre systeme de plugins :\n", "\n", - "| Framework | Système de plugins | Problème |\n", - "|-----------|-------------------|----------|\n", - "| Semantic Kernel | KernelFunction | Non portable |\n", - "| LangChain | Tools | Non portable |\n", - "| AutoGen | Tools | Non portable |\n", - "| OpenAI | Function Calling | Spécifique OpenAI |\n", + "| Framework | Système de plugins | Limitation |\n", + "|-----------|-------------------|------------|\n", + "| Semantic Kernel | `@kernel_function` | Non portable hors SK |\n", + "| LangChain | Tools | Non portable hors LangChain |\n", + "| AutoGen | Tools | Non portable hors AutoGen |\n", + "| OpenAI | Function Calling | Specifique au runtime OpenAI |\n", "\n", - "**MCP resout ce problème** en definissant un standard :\n", - "\n", - "```\n", - "┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐\n", - "│ Semantic Kernel │ │ LangChain │ │ AutoGen │\n", - "│ │ │ │ │ │\n", - "│ MCP Client │ │ MCP Client │ │ MCP Client │\n", - "└────────┬────────┘ └────────┬────────┘ └────────┬────────┘\n", - " │ │ │\n", - " └───────────────────────┼───────────────────────┘\n", - " │\n", - " ↓\n", - " ┌─────────────────────────┐\n", - " │ MCP Servers │\n", - " │ │\n", - " │ - Filesystem │\n", - " │ - GitHub │\n", - " │ - Database │\n", - " │ - Custom... │\n", - " └─────────────────────────┘\n", - "```\n", + "MCP **standardise** ce contrat : un serveur expose une liste d'outils, chaque outil publie son `inputSchema` (JSON Schema), et **n'importe quel client conforme** peut consommer ces outils. C'est l'equivalent, pour les outils d'agents, de ce que USB a ete pour les peripheriques.\n", "\n", "### Acteurs principaux\n", "\n", - "| Acteur | Rôle | Exemple |\n", - "|--------|------|---------|\n", - "| **Client** | Consomme les outils | Claude Code, SK |\n", - "| **Server** | Fournit les outils | mcp-server-filesystem |\n", - "| **Transport** | Communication | stdio, HTTP, SSE |" - ] - }, - { - "cell_type": "markdown", - "id": "mcp5k08c", - "metadata": { - "tags": [] - }, - "source": [ - "Le diagramme ci-dessous rend cette architecture **MCP** sous forme de graphe : plusieurs\n", - "frameworks clients consomment un même ensemble de serveurs d'outils standardises.\n", - "\n", - "```mermaid\n", - "flowchart TD\n", - " SK[\"Semantic Kernel
MCP Client\"] --> SRV\n", - " LC[\"LangChain
MCP Client\"] --> SRV\n", - " AG[\"AutoGen
MCP Client\"] --> SRV\n", - " SRV[\"MCP Servers
Filesystem / GitHub / Database / Custom...\"]\n", - " classDef client fill:#cfe2ff,stroke:#084298,color:#052c65\n", - " classDef server fill:#fff3cd,stroke:#b8860b,color:#5c4400\n", - " class SK,LC,AG client\n", - " class SRV server\n", - "```\n", + "| Acteur | Role | Exemples |\n", + "|--------|------|----------|\n", + "| **Client** | Consomme les outils | Claude Code, Semantic Kernel, LangChain |\n", + "| **Server** | Fournit les outils | `@modelcontextprotocol/server-filesystem`, `mcp-server-git`, FastMCP en Python |\n", + "| **Transport** | Transporte les messages JSON-RPC | `stdio` (sous-processus), `streamable-http` (HTTP+POST), `SSE` (legacy) |\n", + "\n", + "### Ce qui distingue MCP d'une simple liste d'outils\n", + "\n", + "Trois proprietes que les simulations ne reproduisent pas :\n", "\n", - "> **Lecture.** Le **Model Context Protocol** standardise la facon dont un agent accede a\n", - "> des outils externes : n'importe quel **client** conforme (Semantic Kernel, LangChain,\n", - "> AutoGen...) peut consommer n'importe quel **serveur MCP** (filesystem, GitHub, base de\n", - "> données, serveurs maison) sans integration sur-mesure. C'est l'equivalent, pour les\n", - "> outils d'agents, de ce que USB a ete pour les peripheriques : un connecteur commun qui\n", - "> decouple les clients des fournisseurs de capacites.\n" + "1. **Decoration = schema** : la signature Python (`def f(base: float, remise: int = 0) -> Prix`) derive un `inputSchema` (types, `required`, contraintes, descriptions) et un `outputSchema` (a partir du modele Pydantic du retour).\n", + "2. **Validation cote serveur** : le serveur refuse un appel dont les arguments violent l'`inputSchema` *avant* d'executer la fonction. Le client recoit `isError=True` avec un message structure.\n", + "3. **Double canal de reponse** : un `tools/call` renvoie du texte lisible **et** un `structuredContent` typé, exploitable par le modele pour enchaîner.\n" ] }, { "cell_type": "markdown", - "id": "63080987", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "## 2. Architecture MCP\n", "\n", - "MCP définit trois types de primitives :\n", + "MCP definit trois types de **primitives** cote serveur :\n", "\n", - "### 2.1 Tools (Outils)\n", + "| Primitive | Role | Exemple |\n", + "|-----------|------|---------|\n", + "| **Tools** | Fonctions executables que le LLM peut appeler | `read_file`, `query_database`, `calculate_price` |\n", + "| **Resources** | Donnees adressables par URI que le client peut lire | `file:///path/to/doc.md`, `db://users/42` |\n", + "| **Prompts** | Templates reutilisables avec arguments | `summarize(text: str, max_words: int)` |\n", "\n", - "Fonctions executables par le LLM :\n", + "Le transport est **JSON-RPC 2.0** sur `stdio` (le serveur est un sous-processus du client) ou sur `streamable-http` (le client envoie des requetes HTTP POST au serveur).\n", "\n", - "```json\n", - "{\n", - " \"name\": \"read_file\",\n", - " \"description\": \"Read the contents of a file\",\n", - " \"inputSchema\": {\n", - " \"type\": \"object\",\n", - " \"properties\": {\n", - " \"path\": { \"type\": \"string\" }\n", - " },\n", - " \"required\": [\"path\"]\n", - " }\n", - "}\n", + "```\n", + " ┌──────────────┐ ┌──────────────┐\n", + " │ MCP Client │ JSON-RPC over stdio │ MCP Server │\n", + " │ │ ───────────────────────> │ │\n", + " │ SK / Claude │ initialize │ FastMCP / │\n", + " │ Code / etc. │ tools/list │ Node SDK │\n", + " │ │ tools/call │ │\n", + " │ │ <─────────────────────── │ │\n", + " └──────────────┘ └──────────────┘\n", "```\n", "\n", - "### 2.2 Resources (Ressources)\n", + "Trois methodes JSON-RPC essentielles :\n", "\n", - "Données accessibles en lecture :\n", + "| Methode | Sens | Ce qu'elle retourne |\n", + "|---------|------|---------------------|\n", + "| `initialize` | client → serveur | Capacites du serveur (nom, version, fonctionnalites) |\n", + "| `tools/list` | client → serveur | Liste des outils avec `name`, `description`, `inputSchema` |\n", + "| `tools/call` | client → serveur | Resultat : `content` textuel + `structuredContent` typé, ou `isError` |\n", "\n", - "```json\n", - "{\n", - " \"uri\": \"file:///path/to/doc.md\",\n", - " \"name\": \"Documentation\",\n", - " \"mimeType\": \"text/markdown\"\n", - "}\n", - "```\n", + "### 2.1 Le schema d'entree (`inputSchema`)\n", "\n", - "### 2.3 Prompts\n", + "C'est un **JSON Schema** classique. Pour un outil defini en Python avec `@mcp.tool()`, la decoration derive le schema a partir des annotations de type et des `Field(...)` Pydantic :\n", "\n", - "Templates de prompts reutilisables :\n", + "```python\n", + "@mcp.tool()\n", + "def prix(base: Annotated[float, Field(description=\"prix HT\", gt=0)], remise: int = 0) -> Prix:\n", + " ...\n", + "```\n", + "\n", + "donne :\n", "\n", "```json\n", "{\n", - " \"name\": \"summarize\",\n", - " \"description\": \"Summarize a document\",\n", - " \"arguments\": [\n", - " { \"name\": \"content\", \"required\": true }\n", - " ]\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"base\": {\"type\": \"number\", \"description\": \"prix HT\", \"exclusiveMinimum\": 0},\n", + " \"remise\": {\"type\": \"integer\", \"default\": 0}\n", + " },\n", + " \"required\": [\"base\"]\n", "}\n", "```\n", "\n", - "### Tableau comparatif\n", + "### 2.2 Le schema de sortie (`outputSchema`)\n", "\n", - "| Primitive | Action | Equivalent SK |\n", - "|-----------|--------|---------------|\n", - "| **Tool** | Execute une fonction | KernelFunction |\n", - "| **Resource** | Lit des données | Pas d'equivalent direct |\n", - "| **Prompt** | Fournit un template | PromptTemplate |" - ] - }, - { - "cell_type": "markdown", - "id": "9c33e97c", - "metadata": { - "tags": [] - }, - "source": [ - "## 3. Serveurs MCP existants\n", + "Quand le type de retour est un modele Pydantic, FastMCP derive un `outputSchema` automatiquement. Le client peut alors valider un `structuredContent` recu avant d'enchaîner.\n", "\n", - "De nombreux serveurs MCP sont disponibles :" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "90023b20", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Serveurs MCP populaires:\n", - "============================================================\n", - "\n", - "FILESYSTEM\n", - " Package: @anthropic/mcp-server-filesystem\n", - " Description: Lecture/ecriture de fichiers\n", - " Tools: read_file, write_file, list_directory\n", - "\n", - "GITHUB\n", - " Package: @anthropic/mcp-server-github\n", - " Description: Operations GitHub (repos, issues, PRs)\n", - " Tools: search_repos, get_issue, create_pr\n", - "\n", - "POSTGRES\n", - " Package: @anthropic/mcp-server-postgres\n", - " Description: Requetes SQL PostgreSQL\n", - " Tools: query, execute, describe_table\n", - "\n", - "JUPYTER\n", - " Package: mcp-jupyter\n", - " Description: Execution de notebooks Jupyter\n", - " Tools: execute_notebook, read_cells, manage_kernel\n", - "\n", - "PUPPETEER\n", - " Package: @anthropic/mcp-server-puppeteer\n", - " Description: Automatisation web (scraping, screenshots)\n", - " Tools: navigate, screenshot, click\n" - ] - } - ], - "source": [ - "# Liste des serveurs MCP populaires\n", - "mcp_servers = [\n", - " {\n", - " \"name\": \"filesystem\",\n", - " \"package\": \"@anthropic/mcp-server-filesystem\",\n", - " \"description\": \"Lecture/ecriture de fichiers\",\n", - " \"tools\": [\"read_file\", \"write_file\", \"list_directory\"]\n", - " },\n", - " {\n", - " \"name\": \"github\",\n", - " \"package\": \"@anthropic/mcp-server-github\",\n", - " \"description\": \"Operations GitHub (repos, issues, PRs)\",\n", - " \"tools\": [\"search_repos\", \"get_issue\", \"create_pr\"]\n", - " },\n", - " {\n", - " \"name\": \"postgres\",\n", - " \"package\": \"@anthropic/mcp-server-postgres\",\n", - " \"description\": \"Requetes SQL PostgreSQL\",\n", - " \"tools\": [\"query\", \"execute\", \"describe_table\"]\n", - " },\n", - " {\n", - " \"name\": \"jupyter\",\n", - " \"package\": \"mcp-jupyter\",\n", - " \"description\": \"Execution de notebooks Jupyter\",\n", - " \"tools\": [\"execute_notebook\", \"read_cells\", \"manage_kernel\"]\n", - " },\n", - " {\n", - " \"name\": \"puppeteer\",\n", - " \"package\": \"@anthropic/mcp-server-puppeteer\",\n", - " \"description\": \"Automatisation web (scraping, screenshots)\",\n", - " \"tools\": [\"navigate\", \"screenshot\", \"click\"]\n", - " }\n", - "]\n", - "\n", - "print(\"Serveurs MCP populaires:\")\n", - "print(\"=\" * 60)\n", - "for server in mcp_servers:\n", - " print(f\"\\n{server['name'].upper()}\")\n", - " print(f\" Package: {server['package']}\")\n", - " print(f\" Description: {server['description']}\")\n", - " print(f\" Tools: {', '.join(server['tools'])}\")" - ] - }, - { - "cell_type": "markdown", - "id": "9accb0c5", - "metadata": { - "tags": [] - }, - "source": [ - "### Exemple guidé 1 : Analyseur de capacites MCP\n", - "\n", - "*Contribution étudiante de Gabriel COMBE (@GabrielC-star) et Rémi LESANNE, PR #18553, intégrée comme exemple guidé.*\n", - "\n", - "Cet exemple classe les cinq serveurs MCP listés plus haut par domaine d'expertise (`storage`, `devops`, `web`, `data`). La fonction `categorize_mcp_servers()` parcourt la liste, reconnaît le domaine de chaque serveur grâce aux mots-clés de sa description, compte ses outils et repère le serveur le mieux pourvu. Elle retourne un dict avec les catégories, le total d'outils et le nom du serveur en ayant le plus." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "44789c95", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "storage: ['filesystem']\n", - "devops: ['github']\n", - "web: ['puppeteer']\n", - "data: ['postgres']\n", - "Total outils: 15\n" - ] - } - ], - "source": [ - "# Exemple guide 1 : Analyseur de capacites MCP\n", - "# Contribution etudiante de Gabriel COMBE et Remi LESANNE (PR #18553)\n", - "def categorize_mcp_servers(servers: list) -> dict:\n", - " \"\"\"\n", - " Classifier les serveurs MCP par domaine d'expertise.\n", - " \n", - " Args:\n", - " servers: Liste de dicts avec les cles: name, description, tools\n", - " \n", - " Returns:\n", - " dict avec les cles: categories (dict domaine -> list serveurs),\n", - " total_tools (int), server_with_most_tools (str)\n", - " \"\"\"\n", - " categories = {\n", - " \"storage\": [],\n", - " \"devops\": [],\n", - " \"web\": [],\n", - " \"data\": []\n", - " }\n", - "\n", - " total_tools = 0\n", - " server_with_most_tools = None\n", - " max_tools = 0\n", - "\n", - " for server in servers:\n", - " name = server[\"name\"]\n", - " description = server[\"description\"].lower()\n", - " tools = server[\"tools\"]\n", - "\n", - " nb_tools = len(tools)\n", - " total_tools += nb_tools\n", - "\n", - " if nb_tools > max_tools:\n", - " max_tools = nb_tools\n", - " server_with_most_tools = name\n", - " \n", - " if \"fichier\" in description:\n", - " categories[\"storage\"].append(server)\n", - "\n", - " elif \"github\" in description:\n", - " categories[\"devops\"].append(server)\n", - "\n", - " elif \"scraping\" in description:\n", - " categories[\"web\"].append(server)\n", - "\n", - " elif \"sql\" in description:\n", - " categories[\"data\"].append(server)\n", - "\n", - "\n", - " return {\n", - " \"categories\": categories,\n", - " \"total_tools\": total_tools,\n", - " \"server_with_most_tools\": server_with_most_tools\n", - " }\n", - " \n", - " \n", - "# Test :\n", - "result = categorize_mcp_servers(mcp_servers)\n", - "for category, srvs in result['categories'].items():\n", - " print(f\"{category}: {[s['name'] for s in srvs]}\")\n", - "print(f\"Total outils: {result['total_tools']}\")" + "> **Lecture.** Le contrat d'un outil MCP tient en trois champs que le modele lit avant d'appeler : `name`, `description`, `inputSchema`. Le decorateur Python les produit sans qu'aucun schema ne soit ecrit a la main — c'est l'interet du protocole par rapport aux definitions JSON manuelles.\n" ] }, { "cell_type": "markdown", - "id": "mcp-eg1-lecture", "metadata": {}, "source": [ - "### Lecture du résultat — exemple guidé 1 (capacités MCP)\n", - "\n", - "- Les quatre domaines attendus sont remplis : `storage: ['filesystem']`, `devops: ['github']`, `web: ['puppeteer']`, `data: ['postgres']`.\n", - "- `mcp_servers` compte 5 serveurs, mais un seul manque à l'appel : `jupyter` n'apparaît dans aucune catégorie. Sa description (« Execution de notebooks Jupyter ») ne contient aucun des mots-clés testés (`fichier`, `github`, `scraping`, `sql`) — le classement par mots-clés l'ignore **silencieusement**, sans erreur ni avertissement.\n", - "- `Total outils: 15` compte pourtant les 5 serveurs (3 outils chacun), `jupyter` compris : le total et les catégories ne décrivent pas le même périmètre.\n", - "- `server_with_most_tools` est calculé — la paire gagnante serait `filesystem`, premier ex æquo à 3 outils — mais jamais imprimé : la valeur retourne dans le dict sans apparaître dans la sortie." - ] - }, - { - "cell_type": "markdown", - "id": "mcp-ex1-routage-md", - "metadata": { - "tags": [] - }, - "source": [ - "### Exercice 1 : Router une tache vers le bon serveur et son outil\n", - "\n", - "L'exemple guidé 1 classe les serveurs par domaine, mais il ne dit pas *quel outil* appeler pour une tâche donnée, et surtout il **ignore silencieusement** tout serveur que ses mots-clés ne reconnaissent pas. Un vrai routeur doit savoir répondre « aucun serveur ne sait faire ça ».\n", - "\n", - "**Objectif** : écrire `choisir_outil(tache, servers)` qui, pour une tâche en langage naturel, note chaque paire (serveur, outil) selon les mots de la tâche, et retourne la meilleure paire — ou `None` quand aucun score n'est positif.\n", - "\n", - "**Étapes** :\n", - "1. `# Étape 1` : découper la tâche en mots (minuscules), en ajoutant au besoin des synonymes (par exemple « capture d'ecran » → `screenshot`)\n", - "2. `# Étape 2` : pour chaque serveur de `mcp_servers` et chacun de ses outils, calculer un score : +1 si un mot de la tâche correspond au nom du serveur, à sa description ou au nom de l'outil\n", - "3. `# Étape 3` : retourner la paire `(serveur, outil)` au meilleur score si ce score est strictement positif, sinon `None`\n", - "\n", - "**Indices** :\n", - "- `# Indice` : « ouvrir une issue pour signaler un bug » contient le mot *issue* — un outil de `github` s'appelle `get_issue`\n", - "- `# Indice` : « lister les tables de la base » parle de tables — `describe_table` est un outil de `postgres`\n", - "- `# Indice` : « envoyer un SMS » ne correspond à aucun serveur — la fonction doit retourner `None`, sans lever d'erreur\n", - "\n", - "**Critère de réussite** : les quatre tests affichent `('github', 'get_issue')`, `('puppeteer', 'screenshot')`, `('postgres', 'describe_table')` puis `None`." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "mcp-ex1-routage-code", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Exercice a completer\n" - ] - } - ], - "source": [ - "# Exercice 1 : Router une tache vers le bon serveur et son outil\n", - "# TODO etudiant : completer choisir_outil, puis decommenter les tests\n", - "\n", - "def choisir_outil(tache: str, servers: list):\n", - " \"\"\"\n", - " Choisir la paire (serveur, outil) la mieux adaptee a une tache.\n", - "\n", - " Args:\n", - " tache: la tache en langage naturel\n", - " servers: la liste des serveurs (meme forme que mcp_servers)\n", - "\n", - " Returns:\n", - " tuple (nom du serveur, nom de l'outil) si un score est positif,\n", - " None sinon\n", - " \"\"\"\n", - " # Etape 1 : mots de la tache en minuscules (+ synonymes si besoin)\n", - " mots = None # TODO etudiant\n", - "\n", - " meilleur_score = 0\n", - " meilleure_paire = None\n", - "\n", - " # Etape 2 : scorer chaque paire (serveur, outil)\n", - " # for server in servers:\n", - " # texte = server[\"name\"] + \" \" + server[\"description\"]\n", - " # for tool in server[\"tools\"]:\n", - " # score = 0 # TODO etudiant : +1 par mot present dans texte ou tool\n", - " # if score > meilleur_score:\n", - " # meilleur_score = score\n", - " # meilleure_paire = (server[\"name\"], tool)\n", - "\n", - " # Etape 3 : retourner la meilleure paire, ou None si aucun score positif\n", - " return None # TODO etudiant\n", - "\n", - "\n", - "# Tests\n", - "taches = [\n", - " \"ouvrir une issue pour signaler un bug\",\n", - " \"faire une capture d'ecran d'une page web\",\n", - " \"lister les tables de la base\",\n", - " \"envoyer un SMS\",\n", - "]\n", - "# for tache in taches:\n", - "# print(f\"{tache} -> {choisir_outil(tache, mcp_servers)}\")\n", - "\n", - "print(\"Exercice a completer\")" - ] - }, - { - "cell_type": "markdown", - "id": "y02s0tymzf", - "metadata": { - "tags": [] - }, - "source": [ - "### Interprétation : Écosystème des serveurs MCP\n", - "\n", - "**Sortie obtenue** : Liste de 5 serveurs MCP représentatifs de l'écosystème\n", - "\n", - "| Serveur | Domaine | Maturité | Cas d'usage typique |\n", - "|---------|---------|----------|---------------------|\n", - "| **filesystem** | Système | Production | Manipulation de fichiers locaux |\n", - "| **github** | DevOps | Production | Automatisation CI/CD, gestion issues |\n", - "| **postgres** | Base de données | Production | Requêtes SQL, analytics |\n", - "| **jupyter** | Data Science | Expérimental | Notebooks interactifs, reproductibilité |\n", - "| **puppeteer** | Web scraping | Production | Tests E2E, captures d'écran |\n", - "\n", - "**Points clés** :\n", - "\n", - "1. **Diversité** : MCP couvre tous les domaines (filesystem, cloud, databases, web)\n", - "2. **Standards vs Custom** : Les serveurs Anthropic (@anthropic/*) sont les références\n", - "3. **Packages npm** : La majorité utilise Node.js, quelques-uns Python (uvx)\n", - "4. **Granularité** : Chaque serveur expose 3-10 outils spécialisés\n", + "## 3. Serveur MCP reel : FastMCP + ClientSession\n", "\n", - "**Note technique** : L'installation nécessite Node.js 18+ pour les serveurs npm, ou Python 3.10+ avec `uvx` pour les serveurs Python.\n", + "### Installation des SDK\n", "\n", - "**Évolution** : De nouveaux serveurs apparaissent régulièrement sur [modelcontextprotocol.io/servers](https://modelcontextprotocol.io/servers)\n" - ] - }, - { - "cell_type": "markdown", - "id": "dcf4c1c8", - "metadata": { - "tags": [] - }, - "source": [ - "### Installation d'un serveur MCP\n", + "Les SDK MCP pour Python sont installables via `pip` :\n", "\n", "```bash\n", - "# Via npm (Node.js requis)\n", - "npx -y @anthropic/mcp-server-filesystem --root /path/to/files\n", - "\n", - "# Via uvx (Python)\n", - "uvx mcp-server-sqlite --db-path database.db\n", + "pip install \"mcp[cli]\"\n", "```\n", "\n", - "### Configuration dans Claude Code\n", - "\n", - "Les serveurs MCP se configurent dans `~/.claude.json` :\n", + "Ce notebook a ete execute avec :\n", + "- `mcp` 1.27.0 (cote serveur et client)\n", + "- `pydantic` 2.x (modeles de validation et `outputSchema`)\n", + "- `semantic-kernel` 1.42.0 (avec `semantic_kernel.connectors.mcp.MCPStdioPlugin`)\n", "\n", - "```json\n", - "{\n", - " \"mcpServers\": {\n", - " \"filesystem\": {\n", - " \"command\": \"npx\",\n", - " \"args\": [\"-y\", \"@anthropic/mcp-server-filesystem\", \"--root\", \"/path\"]\n", - " }\n", - " }\n", - "}\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "e6160224", - "metadata": { - "tags": [] - }, - "source": [ - "## 4. Integration SK + MCP\n", - "\n", - "Semantic Kernel peut consommer des serveurs MCP comme plugins." + "Tous ces paquets etaient deja disponibles dans l'environnement : **verdict SOTA = RECOVERABLE-LOCAL** (rien a installer), ce qui tranche avec la cellule `de07375f` du carnet d'origine qui se contentait de charger `.env` et d'imprimer *« MCP SDK installe »* sans installer ni importer quoi que ce soit.\n" ] }, { "cell_type": "code", - "execution_count": 5, - "id": "1f015b46", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Plugin MCP Filesystem ajoute au Kernel\n", - "\n", - "Fonctions disponibles:\n", - " - list_directory: Liste les fichiers d'un repertoire\n", - " - read_file: Lit le contenu d'un fichier\n", - " - write_file: Ecrit du contenu dans un fichier\n" - ] - } - ], - "source": [ - "# Exemple conceptuel : MCP Server comme plugin SK\n", - "# Note: L'integration native SK+MCP est en cours de developpement\n", - "\n", - "from semantic_kernel import Kernel\n", - "from semantic_kernel.functions import kernel_function\n", + "metadata": {}, + "execution_count": null, + "outputs": [], + "source": [ + "# Verification rapide : les SDK sont-ils importables ?\n", + "import importlib.metadata as md\n", + "print(\"mcp:\", md.version(\"mcp\"))\n", + "print(\"semantic-kernel:\", md.version(\"semantic-kernel\"))\n", + "print(\"pydantic:\", md.version(\"pydantic\"))\n", + "\n", + "from mcp.server.fastmcp import FastMCP\n", + "from mcp import ClientSession, StdioServerParameters\n", + "from mcp.client.stdio import stdio_client\n", + "from semantic_kernel.connectors.mcp import MCPStdioPlugin\n", + "from pydantic import BaseModel, Field\n", "from typing import Annotated\n", - "import subprocess\n", - "import json\n", - "\n", - "class MCPFilesystemPlugin:\n", - " \"\"\"\n", - " Wrapper qui expose un serveur MCP comme plugin SK.\n", - " Pattern: MCP tools -> KernelFunctions\n", - " \"\"\"\n", - " \n", - " def __init__(self, root_path: str):\n", - " self.root_path = root_path\n", - " \n", - " @kernel_function(description=\"Lit le contenu d'un fichier\")\n", - " def read_file(self, path: Annotated[str, \"Chemin du fichier\"]) -> str:\n", - " \"\"\"Wrapper pour l'outil MCP read_file.\"\"\"\n", - " full_path = os.path.join(self.root_path, path)\n", - " try:\n", - " with open(full_path, 'r', encoding='utf-8') as f:\n", - " return f.read()\n", - " except Exception as e:\n", - " return f\"Erreur: {e}\"\n", - " \n", - " @kernel_function(description=\"Liste les fichiers d'un repertoire\")\n", - " def list_directory(self, path: Annotated[str, \"Chemin du repertoire\"] = \".\") -> str:\n", - " \"\"\"Wrapper pour l'outil MCP list_directory.\"\"\"\n", - " full_path = os.path.join(self.root_path, path)\n", - " try:\n", - " files = os.listdir(full_path)\n", - " return json.dumps(files, indent=2)\n", - " except Exception as e:\n", - " return f\"Erreur: {e}\"\n", - " \n", - " @kernel_function(description=\"Ecrit du contenu dans un fichier\")\n", - " def write_file(self, \n", - " path: Annotated[str, \"Chemin du fichier\"],\n", - " content: Annotated[str, \"Contenu a ecrire\"]) -> str:\n", - " \"\"\"Wrapper pour l'outil MCP write_file.\"\"\"\n", - " full_path = os.path.join(self.root_path, path)\n", - " try:\n", - " with open(full_path, 'w', encoding='utf-8') as f:\n", - " f.write(content)\n", - " return f\"Fichier ecrit: {full_path}\"\n", - " except Exception as e:\n", - " return f\"Erreur: {e}\"\n", - "\n", - "# Utilisation\n", - "kernel = Kernel()\n", - "kernel.add_plugin(MCPFilesystemPlugin(root_path=\".\"), plugin_name=\"filesystem\")\n", "\n", - "print(\"Plugin MCP Filesystem ajoute au Kernel\")\n", - "print(\"\\nFonctions disponibles:\")\n", - "for func in kernel.get_plugin(\"filesystem\").functions.values():\n", - " print(f\" - {func.name}: {func.description}\")" + "print(\"FastMCP, ClientSession, stdio_client, MCPStdioPlugin : OK\")\n" ] }, { "cell_type": "markdown", - "id": "x0z5k215bqn", - "metadata": { - "tags": [] - }, - "source": [ - "### Interprétation : Bridge MCP → Semantic Kernel\n", - "\n", - "**Sortie obtenue** : Plugin SK exposant 3 fonctions (read_file, list_directory, write_file)\n", - "\n", - "| Fonction SK | Outil MCP source | Type de transformation |\n", - "|-------------|------------------|------------------------|\n", - "| `read_file()` | `read_file` | Wrapper direct |\n", - "| `list_directory()` | `list_directory` | Wrapper avec JSON serialization |\n", - "| `write_file()` | `write_file` | Wrapper direct |\n", - "\n", - "**Architecture du bridge** :\n", - "\n", - "```\n", - "MCP Server (Node.js) Python Bridge Semantic Kernel\n", - "┌─────────────────┐ ┌──────────────┐ ┌───────────────┐\n", - "│ @anthropic/ │ │ MCP │ │ Kernel │\n", - "│ mcp-server- │ stdio │ Filesystem │ @kernel │ Plugin │\n", - "│ filesystem │ ──────> │ Plugin │ ──────> │ │\n", - "│ │ │ │ _function│ Functions │\n", - "│ - read_file │ │ - read_file │ │ disponibles │\n", - "│ - list_dir │ │ - list_dir │ │ │\n", - "└─────────────────┘ └──────────────┘ └───────────────┘\n", - "```\n", - "\n", - "**Points clés** :\n", - "\n", - "1. **Pattern Adapter** : Le plugin SK adapte le protocole MCP (stdio) en KernelFunctions\n", - "2. **Annotations Semantic Kernel** : `@kernel_function` + `Annotated[str, \"description\"]`\n", - "3. **Gestion d'erreurs** : Les exceptions MCP sont capturées et retournées comme strings\n", - "4. **Path resolution** : Le bridge gère les chemins relatifs via `root_path`\n", - "\n", - "**Limitations actuelles** :\n", - "\n", - "| Limitation | Impact | Contournement |\n", - "|------------|--------|---------------|\n", - "| Pas d'intégration native SK | Code wrapper manuel requis | Bridge custom comme ci-dessus |\n", - "| Communication stdio | Latence légère | Acceptable pour la plupart des usages |\n", - "| Pas de Resources/Prompts | Seulement les Tools MCP exposés | Enrichir le plugin au besoin |\n", - "\n", - "**Roadmap** : Microsoft travaille sur une intégration native MCP dans SK (Q2 2026)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "faf9dd9c", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Fichiers dans le repertoire courant:\n", - "[\n", - " \"01-SemanticKernel-Intro.ipynb\",\n", - " \"02-SemanticKernel-Advanced.ipynb\",\n", - " \"03-SemanticKernel-Agents.ipynb\",\n", - " \"04-SemanticKernel-Filters-Observability.ipynb\",\n", - " \"05-SemanticKernel-VectorStores.ipynb\",\n", - " \"06-SemanticKernel-ProcessFramework.ipynb\",\n", - " \"07-SemanticKernel-MultiModal.ipynb\",\n", - " \"08-SemanticKernel-MCP.ipynb\",\n", - " \"09-SemanticKernel-Building-CLR.ipynb\",\n", - " \"10-SemanticKernel-NotebookMaker.ipynb\",\n", - " \"10a-SemanticKernel-NotebookMaker-batch.ipynb\",\n", - " \"10b-SemanticKernel-NotebookMaker-batch-parameterized.ipynb\",\n", - " \"11-SemanticKernel-A2A.ipynb\",\n", - " \"aspire-otel\",\n", - " \"AutoGenNotebookUpdater.cs\",\n", - " \"AutoInvokeSKAgentsNotebookUpdater.cs\",\n", - " \"Cr\\u00e9ateur de mail personnalis\\u00e9.ipynb\",\n", - " \"DisplayLogger.cs\",\n", - " \"DisplayLoggerProvider.cs\",\n", - " \"fort-boyard-csharp.ipynb\",\n", - " \"fort-boyard-python.ipynb\",\n", - " \"GuessingGame.cs\",\n", - " \"MANIFEST.md\",\n", - " \"Notebook-Generated.ipynb\",\n", - " \"Notebook-Template.ipynb\",\n", - " \"NotebookExecutor.cs\",\n", - " \"NotebookPlannerUpdater.cs\",\n", - " \"NotebookUpdaterBase.cs\",\n", - " \"prompt_template_samples\",\n", - " \"README.md\",\n", - " \"Resources\",\n", - " \"semantic-fleet\",\n", - " \"Semantic-kernel-AutoInteractive.ipynb\",\n", - " \"START_HERE.txt\",\n", - " \"Workbook-Template-Python.ipynb\",\n", - " \"Workbook-Template.ipynb\",\n", - " \"WorkbookInteractionBase.cs\",\n", - " \"WorkbookUpdateInteraction.cs\",\n", - " \"WorkbookValidation.cs\"\n", - "]\n" - ] - } - ], - "source": [ - "# Test du plugin\n", - "from semantic_kernel.functions import KernelArguments\n", - "\n", - "# Lister les fichiers du repertoire courant\n", - "list_func = kernel.get_function(\"filesystem\", \"list_directory\")\n", - "result = await kernel.invoke(list_func, KernelArguments(path=\".\"))\n", - "\n", - "print(\"Fichiers dans le repertoire courant:\")\n", - "print(result)" - ] - }, - { - "cell_type": "markdown", - "id": "a4498146", - "metadata": { - "tags": [] - }, - "source": [ - "### Exemple guidé 2 : Plugin MCP avec validation d'entrees\n", - "\n", - "*Contribution étudiante de Gabriel COMBE (@GabrielC-star) et Rémi LESANNE, PR #18553, intégrée comme exemple guidé.*\n", - "\n", - "Cet exemple durcit la lecture de fichiers : le plugin `ValidatedFilesystemPlugin` ne rend un fichier qu'après trois contrôles — le chemin résolu reste dans le répertoire autorisé (via `os.path.realpath` puis `os.path.commonpath`), le fichier existe, et sa taille reste sous `max_file_size`. Toute tentative de sortie du répertoire autorisé (attaque par *path traversal* telle que `../../etc/passwd`) lève une `ValueError` avant la moindre lecture." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "91032d36", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "# SemanticKernel - Microsoft Semantic Kernel\n", - "\n", - "