
MCP Server : Implémenter un serveur Model Context Protocol en TypeScript
Découvrez comment créer un serveur MCP en TypeScript à l'aide du SDK officiel. Apprenez à fournir un contexte riche aux LLMs.
Progression
Avant de mettre en place les resolvers
pour les query en lecture, vous devez créer le type query.
Il faut ensuite créer un type avec l'ensemble des fonctions que vous souhaitez avoir. Nous allons :
Ajoutez le fichier schemas.js
à la racine de votre projet. Importez l'ensemble des types que nous avons défini à l'étape précédente :
import Astronaute from './typedefs/astronaute'; import Planet from './typedefs/planet'; import Grade from './typedefs/grade'; const RootQuery = ` type RootQuery { astronaute(id: Int!): Astronaute, astronautes: [Astronaute] planet(id: Int!): Planet } `; const SchemaDefinition = ` schema { query: RootQuery } `;
Toujours dans le même fichier schemas.js
vous devez dire à votre serveur GraphQL où est votre schéma.
Pour cela vous devez ajouter le module graphql-tools
:
yarn add graphql-tools
Ce module expose la fonction makeExecutableSchema
qui prend en paramètre vos types.
Vous devriez avoir le code suivant :
import { makeExecutableSchema } from 'graphql-tools'; import { resolvers } from './resolver'; import Astronaute from './typedefs/astronaute'; import Planet from './typedefs/planet'; import Grade from './typedefs/grade'; const RootQuery = ` type RootQuery { astronaute(id: Int!): Astronaute, astronautes: [Astronaute] planet(id: Int!): Planet } `; const SchemaDefinition = ` schema { query: RootQuery } `; export default makeExecutableSchema({ typeDefs: [SchemaDefinition, RootQuery, Astronaute, Planet, Grade], resolvers: resolvers, });
Ajoutez un fichier resolver.js
contenant seulement :
export const resolvers = {}
Il nous reste à indiquer à GraphQL comment récupérer notre schéma.
Dans le fichier index.js
vous devez importer ledit schéma :
import express from 'express'; import bodyParser from 'body-parser'; import { graphqlExpress, graphiqlExpress } from 'apollo-server-express'; import schema from './schemas'; const PORT = 3000; const app = express(); app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })); app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })); app.listen(PORT);
Nous ajoutons au même moment l'IDE GraphiQL qui est contenu dans la librairie Apollo. L'IDE permet d'afficher directement la documentation, ainsi que d'effectuer les query.
Si tout est ok, vous devriez avoir accès à l'url suivante http://127.0.0.1:3000/graphiql et voir la doucmentation (à droite).
Si vous essayez la query :
{ astronautes { id } }
Vous devriez voir la réponse suivante :
{ "data": { "astronautes": null } }
Et oui, pour l'instant vous n'avez aucun resolver !
Le resolver est le code qui permet de récuperer la donnée dans la base.
Dans un dossier resolvers
vous devez ajouter le fichier astronautes.js
avec le code suivant :
import pg from './../pg';
import Astronaute from '../typedefs/astronaute';
const resolvers = {
RootQuery: {
async astronautes() {
return await pg.select().table('astronaute');
}
},
};
export default resolvers;
Dans le fichier resolver.js
il vous faut ajouter le resolver que nous venons de définir :
import AstronauteResolver from './resolvers/astronaute'; export const resolvers = AstronauteResolver;
Si tout est ok la réponse à votre requête est :
{ "data": { "astronautes": [] } }
Et si vous ajoutez des astronautes dans votre base de donnnées et changer la requête en :
{ astronautes { id, pseudo } }
la réponse est donc :
{ "data": { "astronautes": [ { "id": 1, "pseudo": "CaptainJojo" }, { "id": 2, "pseudo": "Pouzor" }, { "id": 3, "pseudo": "Franky" } ] } }
Maintenant nous allons modifer le resolver pour récupérer via l'id :
import pg from './../pg';
import Astronaute from '../typedefs/astronaute';
const resolvers = {
RootQuery: {
async astronautes() {
return await pg.select().table('astronaute');
},
async astronaute(obj, args, context, info) {
return (await pg
.select()
.table('astronaute')
.where('id', args.id)
.limit(1)).pop();
},
},
};
export default resolvers;
Puis nous allons résoudre la récupération du grade
et de la planet
:
import pg from './../pg';
import Astronaute from '../typedefs/astronaute';
const resolvers = {
RootQuery: {
async astronautes() {
return await pg.select().table('astronaute');
},
async astronaute(obj, args, context, info) {
return (await pg
.select()
.table('astronaute')
.where('id', args.id)
.limit(1)).pop();
},
},
Astronaute: {
async grade(astronaute) {
return (await pg
.select()
.table('grade')
.where('id', astronaute.grade_id)
.limit(1)).pop();
},
async planet(astronaute) {
return (
await pg
.select()
.table('planet')
.innerJoin('planet-astronaute', 'planet-astronaute.planet_id', '=', 'planet.id')
.where('planet-astronaute.astronaute_id', astronaute.id)
.limit(1)).pop();
}
},
};
export default resolvers;
Comme vous pouvez le voir, c'est assez simple, il suffit de spécifier pour chaque attribut comment le récupérer.
Enfin créons le resolver pour la planet
.
Ajouter le fichier planet.js
au dossier resolvers :
import pg from './../pg';
import Planet from '../typedefs/planet';
const resolvers = {
RootQuery: {
async planet(obj, args, context, info) {
return (await pg
.select()
.table('planet')
.where('id', args.id)
.limit(1)).pop();
},
},
Planet: {
async astronautes(planet) {
return (await pg
.select()
.table('astronaute')
.innerJoin('planet-astronaute', 'planet-astronaute.astronaute_id', '=', 'astronaute.id')
.where('planet-astronaute.planet_id', planet.id)
);
}
},
};
export default resolvers;
Puis dans le fichier resolver.js
vous devez ajouter le resolver :
import { merge } from 'lodash'; import AstronauteResolver from './resolvers/astronaute'; import PlanetResolver from './resolvers/planet'; export const resolvers = merge(AstronauteResolver, PlanetResolver);
Si tout est ok, la requête suivante doit fonctionner :
{
astronautes {
id,
pseudo
},
astronaute(id: 1) {
id,
pseudo
grade {
id,
name
}, planet {
id,
name
}
},
planet(id: 2) {
id,
astronautes {
id,
pseudo,
grade {
name
}
}
}
}
La réponse doit ressembler à cela :
{ "data": { "astronautes": [ { "id": 1, "pseudo": "CaptainJojo" }, { "id": 2, "pseudo": "Pouzor" }, { "id": 3, "pseudo": "Franky" } ], "astronaute": { "id": 1, "pseudo": "CaptainJojo", "grade": { "id": 1, "name": "admiral" }, "planet": { "id": 1, "name": "duck" } }, "planet": { "id": 2, "astronautes": [ { "id": 2, "pseudo": "Pouzor", "grade": { "name": "rookie" } }, { "id": 3, "pseudo": "Franky", "grade": { "name": "rookie" } } ] } } }
Retrouvez le code directement ici
Auteur(s)
Jonathan Jalouzot
Lead développeur au @lemondefr, mes technologies sont le symfony depuis 2009, le nodejs, l'angularjs, rabbitMq etc ... J'adore les médias et aimerai continuer dans ce secteur plein de surprise. Vous pouvez me retrouver sur les réseaux sociaux: Twitter: @captainjojo42 Instagram: @captainjojo42 Linkedin: https://fr.linkedin.com/in/jonathanjalouzot Github: https://github.com/captainjojo
Vous souhaitez en savoir plus sur le sujet ?
Organisons un échange !
Notre équipe d'experts répond à toutes vos questions.
Nous contacterDécouvrez nos autres contenus dans le même thème
Découvrez comment créer un serveur MCP en TypeScript à l'aide du SDK officiel. Apprenez à fournir un contexte riche aux LLMs.
Découvrez comment créer un plugin ESLint en TypeScript avec la nouvelle configuration "flat config" et publiez-le sur npm.
Apprenez à concevoir une barre de recherche accessible pour le web, conforme RGAA. Bonnes pratiques, erreurs fréquentes à éviter et exemples concrets en HTML et React/MUI.