Support du transport SSE avec résolution de config par connexion (pour MetaMCP et plateformes similaires) #25

Closed
opened 2026-05-20 09:28:10 +00:00 by thibaud-lclr · 0 comments
Owner

Contexte

Aujourd'hui, tous les serveurs MCP basés sur mcp-framework utilisent exclusivement le transport stdio : le binaire est lancé en tant que processus fils par le client MCP, qui pipe stdin/stdout. Ce modèle fonctionne bien pour un usage local, mais il est incompatible avec les plateformes MCP hébergées comme MetaMCP, qui se connectent à des serveurs MCP distants via SSE (Server-Sent Events) sur HTTP.

Le besoin concret : pouvoir déployer graylog-mcp (et tout autre serveur basé sur le framework) en mode SSE self-hosted, accessible depuis MetaMCP, sans aucun setup local ni Bitwarden.

Limitation actuelle

  • Le framework ne supporte que le transport stdio via fwbootstrap.
  • La config (credentials inclus) est résolue une seule fois au démarrage depuis les env vars, le fichier de config, ou le secret store (Bitwarden).
  • En mode SSE, ce modèle ne tient pas : le serveur est partagé, chaque connexion entrante appartient à un utilisateur différent avec sa propre config.

Design proposé

Transport SSE stateless

Le serveur SSE ne stocke aucun état, aucun credential. La config est portée par la requête HTTP de chaque connexion entrante :

Paramètre Source HTTP
base_url Query param : ?base_url=https://graylog.company.com
stream_id Query param : ?stream_id=xxx
api_token Header : Authorization: Bearer <token>

Ce design est validé par l'UI MetaMCP qui expose nativement un champ Bearer Token et un champ Custom Headers lors de l'ajout d'un serveur SSE. Les paramètres non-secrets (base_url, stream_id) passent en query string pour rester visibles dans les logs et l'UI ; le credential sensible (api_token) passe exclusivement en header.

Chaque connexion SSE instancie son propre handler MCP avec la config extraite de la requête. Bitwarden et le fichier de config sont complètement bypassed en mode SSE.

Nouvelle commande serve (ou flag --transport sse)

graylog-mcp serve --port 8080

Ou via env :

MCP_TRANSPORT=sse MCP_PORT=8080 graylog-mcp mcp

Changements nécessaires dans fwbootstrap

  1. Nouveau hook Serve dans fwbootstrap.Hooks (à côté du hook MCP existant) :
Hooks: fwbootstrap.Hooks{
    MCP: func(ctx context.Context, inv fwbootstrap.Invocation) error {
        // transport stdio — comportement actuel inchangé
    },
    Serve: func(ctx context.Context, inv fwbootstrap.Invocation) error {
        // transport SSE — nouveau
    },
}
  1. Interface de résolution per-request exposée par le framework :
type RequestConfigResolver[C any] func(r *http.Request) (C, error)

Le framework fournit un helper standard qui lit les query params et le header Authorization: Bearer, mappés sur les champs déclarés dans mcp.toml. Les serveurs peuvent surcharger ce resolver si besoin.

  1. Helper SSE dans fwbootstrap :
fwbootstrap.ServeSSE(ctx, fwbootstrap.SSEOptions{
    Port:           8080,
    ConfigResolver: myResolver,
    HandlerFactory: func(cfg MyConfig) (mcp.Handler, error) { ... },
})

Ce qui ne change pas

  • Le mode stdio existant reste 100% intact — aucune régression pour les utilisateurs actuels.
  • Bitwarden, le fichier de config, les profils : inchangés pour le mode stdio.
  • L'interface fwbootstrap.Options reste rétrocompatible.

Infra de déploiement

Le serveur SSE étant complètement stateless (pas de DB, pas de sessions), il se déploie trivialement sur Fly.io, Railway, Render, ou un VPS avec Docker. Une image Docker officielle par serveur MCP serait un plus.

Priorité

Ce changement bénéficierait à tous les serveurs MCP basés sur le framework (pas seulement graylog-mcp). L'implémentation dans le framework plutôt que dans chaque serveur individuellement évite la duplication.

## Contexte Aujourd'hui, tous les serveurs MCP basés sur `mcp-framework` utilisent exclusivement le transport **stdio** : le binaire est lancé en tant que processus fils par le client MCP, qui pipe stdin/stdout. Ce modèle fonctionne bien pour un usage local, mais il est incompatible avec les plateformes MCP hébergées comme **MetaMCP**, qui se connectent à des serveurs MCP distants via **SSE (Server-Sent Events)** sur HTTP. Le besoin concret : pouvoir déployer `graylog-mcp` (et tout autre serveur basé sur le framework) en mode SSE self-hosted, accessible depuis MetaMCP, sans aucun setup local ni Bitwarden. ## Limitation actuelle - Le framework ne supporte que le transport stdio via `fwbootstrap`. - La config (credentials inclus) est résolue **une seule fois au démarrage** depuis les env vars, le fichier de config, ou le secret store (Bitwarden). - En mode SSE, ce modèle ne tient pas : le serveur est partagé, chaque connexion entrante appartient à un utilisateur différent avec sa propre config. ## Design proposé ### Transport SSE stateless Le serveur SSE ne stocke aucun état, aucun credential. La config est portée par la **requête HTTP** de chaque connexion entrante : | Paramètre | Source HTTP | |-----------|-------------| | `base_url` | Query param : `?base_url=https://graylog.company.com` | | `stream_id` | Query param : `?stream_id=xxx` | | `api_token` | Header : `Authorization: Bearer <token>` | Ce design est validé par l'UI MetaMCP qui expose nativement un champ **Bearer Token** et un champ **Custom Headers** lors de l'ajout d'un serveur SSE. Les paramètres non-secrets (`base_url`, `stream_id`) passent en query string pour rester visibles dans les logs et l'UI ; le credential sensible (`api_token`) passe exclusivement en header. Chaque connexion SSE instancie son propre handler MCP avec la config extraite de la requête. Bitwarden et le fichier de config sont complètement bypassed en mode SSE. ### Nouvelle commande `serve` (ou flag `--transport sse`) ``` graylog-mcp serve --port 8080 ``` Ou via env : ``` MCP_TRANSPORT=sse MCP_PORT=8080 graylog-mcp mcp ``` ### Changements nécessaires dans `fwbootstrap` 1. **Nouveau hook `Serve`** dans `fwbootstrap.Hooks` (à côté du hook `MCP` existant) : ```go Hooks: fwbootstrap.Hooks{ MCP: func(ctx context.Context, inv fwbootstrap.Invocation) error { // transport stdio — comportement actuel inchangé }, Serve: func(ctx context.Context, inv fwbootstrap.Invocation) error { // transport SSE — nouveau }, } ``` 2. **Interface de résolution per-request** exposée par le framework : ```go type RequestConfigResolver[C any] func(r *http.Request) (C, error) ``` Le framework fournit un helper standard qui lit les query params et le header `Authorization: Bearer`, mappés sur les champs déclarés dans `mcp.toml`. Les serveurs peuvent surcharger ce resolver si besoin. 3. **Helper SSE dans `fwbootstrap`** : ```go fwbootstrap.ServeSSE(ctx, fwbootstrap.SSEOptions{ Port: 8080, ConfigResolver: myResolver, HandlerFactory: func(cfg MyConfig) (mcp.Handler, error) { ... }, }) ``` ## Ce qui ne change pas - Le mode stdio existant reste **100% intact** — aucune régression pour les utilisateurs actuels. - Bitwarden, le fichier de config, les profils : inchangés pour le mode stdio. - L'interface `fwbootstrap.Options` reste rétrocompatible. ## Infra de déploiement Le serveur SSE étant complètement stateless (pas de DB, pas de sessions), il se déploie trivialement sur Fly.io, Railway, Render, ou un VPS avec Docker. Une image Docker officielle par serveur MCP serait un plus. ## Priorité Ce changement bénéficierait à tous les serveurs MCP basés sur le framework (pas seulement `graylog-mcp`). L'implémentation dans le framework plutôt que dans chaque serveur individuellement évite la duplication.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
AI/mcp-framework#25
No description provided.