diff --git a/README.md b/README.md index 9bd790c..8ca1cac 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Serveur MCP expérimental fournissant du contexte spatial pour les LLM sur la ba - [Avec Docker en local](#avec-docker-en-local) - [Debug de la version locale](#debug-de-la-version-locale) - [Paramétrage](#paramétrage) + - [Tests](#tests) - [Fonctionnalités (Tools)](#fonctionnalités-tools) - [Utiliser des services spatiaux](#utiliser-des-services-spatiaux) - [Recherche d'informations pour un lieu](#recherche-dinformations-pour-un-lieu) @@ -156,16 +157,17 @@ Pour les tests d'intégration et les tests E2E agent, voir [la documentation dé Pour une utilisation avancée : -| Nom | Description | Valeur par défaut | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | -| `TRANSPORT_TYPE` | [Transport](https://mcp-framework.com/docs/Transports/transports-overview) permet de choisir entre "stdio" et "http" | "stdio" | -| `HTTP_HOST` | Adresse d'écoute en mode HTTP. Utile avec Docker pour exposer le service via `0.0.0.0`. | "127.0.0.1" | -| `HTTP_PORT` | Port d'écoute du MCP | 3000 | -| `HTTP_MCP_ENDPOINT` | Chemin d'exposition du MCP en HTTP | "/mcp" | -| `HTTP_TIMEOUT` | Délai maximal, en secondes, pour les appels HTTP sortants vers les services amont IGN. Au-delà, la requête est interrompue et l'outil renvoie une erreur de timeout structurée. | `15` | -| `GPF_WFS_MINISEARCH_OPTIONS` | Chaîne JSON optionnelle pour ajuster les options MiniSearch utilisées par `gpf_wfs_search_types` (`fields`, `combineWith`, `fuzzy`, `boost.namespace`, `boost.name`, `boost.title`, `boost.description`, `boost.properties`, `boost.enums`, `boost.identifierTokens`). | options par défaut de `@ignfab/gpf-schema-store` | -| `LOG_FORMAT` | Le format d'écriture des logs : "json" ou "simple". | "simple" | -| `LOG_LEVEL` | Le niveau d'écriture des logs : ["error", "info", ou "debug"](https://github.com/winstonjs/winston#logging-levels) | "debug" | +| Nom | Description | Valeur par défaut | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | +| `TRANSPORT_TYPE` | [Transport](https://mcp-framework.com/docs/Transports/transports-overview) permet de choisir entre "stdio" et "http" | "stdio" | +| `HTTP_HOST` | Adresse d'écoute en mode HTTP. Utile avec Docker pour exposer le service via `0.0.0.0`. | "127.0.0.1" | +| `HTTP_PORT` | Port d'écoute du MCP | 3000 | +| `HTTP_MCP_ENDPOINT` | Chemin d'exposition du MCP en HTTP | "/mcp" | +| `HTTP_CORS_ALLOWED_ORIGINS` | Permet la [configuration de allowedOrigins pour protection contre les attaques par DNS rebinding](https://www.mcp-framework.com/docs/transports/http-stream#origin-validation-dns-rebinding-protection). Exemple : `HTTP_CORS_ALLOWED_ORIGINS="http://localhost:3000,https://geollm.beta.ign.fr"` | Aucun (warning) | +| `HTTP_TIMEOUT` | Délai maximal, en secondes, pour les appels HTTP sortants vers les services amont IGN. Au-delà, la requête est interrompue et l'outil renvoie une erreur de timeout structurée. | `15` | +| `GPF_WFS_MINISEARCH_OPTIONS` | Chaîne JSON optionnelle pour ajuster les options MiniSearch utilisées par `gpf_wfs_search_types` (`fields`, `combineWith`, `fuzzy`, `boost.namespace`, `boost.name`, `boost.title`, `boost.description`, `boost.properties`, `boost.enums`, `boost.identifierTokens`). | options par défaut de `@ignfab/gpf-schema-store` | +| `LOG_FORMAT` | Le format d'écriture des logs : "json" ou "simple". | "simple" | +| `LOG_LEVEL` | Le niveau d'écriture des logs : ["error", "info", ou "debug"](https://github.com/winstonjs/winston#logging-levels) | "debug" | Exemple : diff --git a/src/index.ts b/src/index.ts index 2a5ea37..0bc1280 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { MCPServer, TransportConfig } from "mcp-framework"; import { dirname, join } from "path"; import { fileURLToPath } from "url"; import { readFileSync } from "fs"; +import logger from "./logger.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -11,6 +12,10 @@ function isTransportType(value: string): value is TransportType { return value === "stdio" || value === "http"; } +/** + * Get the transport type from the environment variable TRANSPORT_TYPE. + * Valid values are "stdio" and "http". If not set, defaults to "stdio". + */ function getTransportType(): TransportType { const transportType = process.env.TRANSPORT_TYPE ?? "stdio"; @@ -21,6 +26,10 @@ function getTransportType(): TransportType { return transportType; } +/** + * Get the HTTP port from the environment variable HTTP_PORT. + * The variable should be a decimal integer between 1 and 65535. If not set, defaults to 3000. + */ function getHttpPort(): number { const rawPort = process.env.HTTP_PORT?.trim(); const invalidHttpPortMessage = `Invalid HTTP_PORT: ${rawPort}. Expected a decimal integer between 1 and 65535.`; @@ -42,6 +51,32 @@ function getHttpPort(): number { return port; } +/** + * Get CORS allowed origins from the environment variable HTTP_CORS_ALLOWED_ORIGINS. + * The variable should be a comma-separated list of origins . + */ +function getCorsAllowedOrigins(): undefined|string[] { + if (process.env.HTTP_CORS_ALLOWED_ORIGINS === undefined) { + logger.warn('Security : HTTP_CORS_ALLOWED_ORIGINS is not set. It is recommended to set this variable to prevent DNS rebinding attacks (e.g., HTTP_CORS_ALLOWED_ORIGINS="http://localhost:3000,https://geollm.beta.ign.fr".'); + return undefined; + } + + const rawOrigins = process.env.HTTP_CORS_ALLOWED_ORIGINS?.trim(); + + const allowedOrigins = rawOrigins + ?.split(",") + .map((origin) => origin.trim()) + .filter(Boolean); + + if (!allowedOrigins || allowedOrigins.length === 0) { + logger.warn('Security : HTTP_CORS_ALLOWED_ORIGINS is empty. It is recommended to set this variable to prevent DNS rebinding attacks (e.g., HTTP_CORS_ALLOWED_ORIGINS="http://localhost:3000,https://geollm.beta.ign.fr".'); + return undefined; + } + + return allowedOrigins; +} + + function buildTransport(transportType: TransportType): TransportConfig { // Handle stdio transport configuration if (transportType === "stdio") { @@ -63,12 +98,16 @@ function buildTransport(transportType: TransportType): TransportConfig { endpoint, cors: { allowOrigin: "*", + allowedOrigins: getCorsAllowedOrigins(), }, host, }, }; } +/** + * Get the version from package.json for the MCP server metadata. + */ function getVersion(): string { const pkgMetadata = JSON.parse( readFileSync(join(__dirname, "../package.json"), "utf-8")