
Premier aperçu de Copilot Studio
Quelles sont les nouvelles fonctionnalités intéressantes apportées par Copilot Studio ?
Cet article de blog est le premier d’une série sur la construction de votre serveur MCP, son hébergement sur Azure et finalement son intégration dans un agent M365 Copilot.
Le Model Context Protocol (MCP) est un standard ouvert, initialement introduit par Anthropic et maintenant largement adopté, qui définit comment les modèles d’IA communiquent avec des outils et des sources de données externes. Pensez-y comme un adaptateur universel — au lieu que chaque assistant IA ait besoin d’une intégration sur mesure pour chaque API ou service, MCP fournit un contrat unique et cohérent.
Pour Microsoft 365 Copilot spécifiquement, la prise en charge MCP signifie que vous pouvez exposer n’importe quelle capacité — interroger une base de données, appeler une API REST, lire depuis SharePoint, exécuter une logique métier — en tant qu’ensemble d’outils que Copilot peut découvrir et invoquer au nom de l’utilisateur.
Un serveur MCP expose trois primitives :
getProjectStatus, createTicket)Pour la plupart des scénarios d’agent Copilot, les outils sont ce sur quoi vous vous concentrerez.
Nous allons écrire le serveur en JavaScript simple, sans étape de compilation TypeScript nécessaire, ce qui garde les choses simples et fonctionne très bien avec l’inspecteur MCP pour les tests locaux.
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod express
Mettez à jour package.json pour utiliser les modules ESM :
{
"type": "module",
"scripts": {
"dev": "node server.js",
"start": "node server.js"
}
}
Créez server.js à la racine du projet. La décision de conception clé ici est un fichier unique pour les deux transports — STDIO pour le développement local et l’inspecteur MCP, HTTP pour Azure et Copilot. Une seule variable d’environnement contrôle lequel démarre :
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import express from "express";
// Données (remplacez par votre vraie source de données)
const projects = {
"PRJ-001": { name: "Intranet Redesign", status: "In Progress", owner: "Arne" },
"PRJ-002": { name: "Copilot Rollout", status: "Planning", owner: "Simon" },
"PRJ-003": { name: "Purview Governance", status: "Completed", owner: "Nico" },
};
// Serveur MCP et outils (partagés entre les deux transports)
const server = new McpServer({ name: "project-status-server", version: "1.0.0" });
server.tool(
"getProjectStatus",
"Retrieves the current status and owner of a project by its ID.",
{ projectId: z.string().describe("The project ID, e.g. PRJ-001") },
async ({ projectId }) => {
const project = projects[projectId];
if (!project) return { content: [{ type: "text", text: `Project ${projectId} not found.` }], isError: true };
return { content: [{ type: "text", text: `**${project.name}** (${projectId})\nStatus: ${project.status}\nOwner: ${project.owner}` }] };
}
);
server.tool(
"updateProjectStatus",
"Updates the status of a project.",
{
projectId: z.string().describe("The project ID"),
status: z.enum(["Planning", "In Progress", "On Hold", "Completed"]).describe("The new status"),
},
async ({ projectId, status }) => {
if (!projects[projectId]) return { content: [{ type: "text", text: `Project ${projectId} not found.` }], isError: true };
projects[projectId].status = status;
return { content: [{ type: "text", text: `Updated ${projectId} to "${status}".` }] };
}
);
server.tool(
"listProjects",
"Returns a list of all projects with their current status.",
{},
async () => {
const list = Object.entries(projects)
.map(([id, p]) => `- **${id}** - ${p.name} (${p.status}, owner: ${p.owner})`)
.join("\n");
return { content: [{ type: "text", text: list }] };
}
);
// Sélection du transport
if (process.env.TRANSPORT === "stdio") {
// STDIO - pour le développement local et l'inspecteur MCP
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server running via STDIO");
} else {
// HTTP - pour Azure et Copilot
const app = express();
app.use(express.json());
app.get("/health", (_req, res) => res.json({ status: "ok" }));
app.post("/mcp", async (req, res) => {
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
res.on("finish", () => transport.close());
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.error(`MCP server running via HTTP on http://localhost:${PORT}`));
}
Quelques éléments à souligner :
process.env.TRANSPORT.console.error partout. En mode STDIO, stdout est réservé aux messages JSON-RPC. Tout console.log corrompt le protocole et provoque des erreurs d’analyse JSON cryptiques dans l’inspecteur. Utilisez toujours console.error pour la journalisation.StreamableHTTPServerTransport du SDK MCP attend un corps de requête pré-analysé. Express gère cela proprement dès le départ.Démarrez le serveur en mode HTTP :
node server.js
Le transport MCP utilise les Server-Sent Events (SSE), il y a donc quelques éléments à bien paramétrer :
-N (pas de mise en mémoire tampon), sinon curl reste silencieuxAccept: application/json, text/event-stream, sans cela vous obtenez une erreur 406 Not Acceptablecurl -N -X POST http://localhost:3000/mcp -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}"
Une réponse réussie ressemble à :

L’inspecteur MCP vous fournit une interface utilisateur appropriée dans le navigateur pour parcourir et appeler des outils de manière interactive.
npx @modelcontextprotocol/inspector
Configurez la connexion dans l’interface de l’inspecteur :
nodeserver.jsTRANSPORT = stdioCliquez sur Connecter et vous verrez les trois outils prêts à l’emploi.
Caution
L’inspecteur démarre votre serveur lui-même via STDIO — ne démarrez pas le serveur séparément avant de vous connecter. Assurez-vous également que TRANSPORT=stdio est défini dans les Variables d’environnement, sinon le serveur démarre en mode HTTP et l’inspecteur ne peut pas communiquer avec lui.

Une réponse réussie :
