package main import ( "context" "encoding/json" "errors" "net/http" "os" "strings" "fn-registry/functions/infra" ) // mcpHTTPHandler builds the http.Handler that serves the MCP Streamable HTTP // transport for remote Claude clients. Bearer-auth backed by the mcp_tokens // table; tool dispatch reuses executeToolAs() so per-user tools (add_comment, // delete_comment) can infer the actor from the authenticated token. func mcpHTTPHandler(db *DB) http.Handler { auth := func(r *http.Request) (context.Context, error) { header := r.Header.Get("Authorization") token := strings.TrimSpace(strings.TrimPrefix(header, "Bearer ")) if token == "" || token == header { return nil, errors.New("missing bearer token") } userID, err := db.LookupMCPToken(token) if err != nil { return nil, err } if userID == "" { return nil, errors.New("invalid or revoked token") } return context.WithValue(r.Context(), userCtxKey, userID), nil } handler := func(ctx context.Context, name string, input json.RawMessage) (any, bool, error) { body := input if len(body) == 0 { body = json.RawMessage(`{}`) } actor, _ := infra.UserIDFromContext(ctx, userCtxKey) res := executeToolAs(db, name, body, actor) if !res.OK { return res.Error, true, nil } return res.Result, false, nil } return infra.MCPHTTPHandler(infra.MCPHTTPOpts{ Name: "kanban", Version: Version, Tools: mcpToolDefs(), Handler: handler, Auth: auth, Logger: os.Stderr, }) }