feat(mcp): streamable HTTP transport with session, init, and dispatch

Implements the Streamable HTTP transport: POST routing handles initialize
(issues session ID), tools/list, tools/call, and unknown methods; GET SSE
emits a keepalive comment then blocks on context cancellation. A minimal
registry stub is introduced so the server compiles and tools/list returns
an empty array until Phase 6+ registers real tools.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mathias Bergqvist
2026-05-04 20:49:54 +02:00
parent 50a3b27825
commit 36765b8360
4 changed files with 296 additions and 1 deletions

View File

@@ -6,6 +6,8 @@ import (
"os"
"gitea.d-ma.be/mathias/gitea-mcp/internal/config"
"gitea.d-ma.be/mathias/gitea-mcp/internal/mcp"
"gitea.d-ma.be/mathias/gitea-mcp/internal/registry"
)
func main() {
@@ -17,14 +19,24 @@ func main() {
os.Exit(1)
}
reg := registry.New()
// Tool registration happens in Phase 6+; for now, registry is empty.
mcpSrv := mcp.NewServer(mcp.ServerOptions{
Registry: reg,
OriginAllowlist: cfg.OriginAllowlist,
Sessions: mcp.NewSessionStore(),
})
mux := http.NewServeMux()
mux.Handle("/mcp", mcpSrv)
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
})
addr := ":" + cfg.Port
logger.Info("gitea-mcp starting", "addr", addr)
logger.Info("gitea-mcp starting", "addr", addr, "version", "0.1.0")
if err := http.ListenAndServe(addr, mux); err != nil {
logger.Error("server stopped", "err", err)
os.Exit(1)