a76ec74338
C++ ImGui kanban for steering LLM agents. Six panels (Board, Calendar, Dashboard, Agent runs, Worktrees, DoD inspector) wired to registry functions http_request, kpi_card, sparkline, agent_runs_timeline, dod_evidence_panel. Backend Go on :8403 (independent operations.db from kanban_web).
61 lines
1.9 KiB
Go
61 lines
1.9 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/subtle"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
|
|
"fn-registry/functions/infra"
|
|
)
|
|
|
|
const internalTokenHeader = "X-Internal-Token"
|
|
|
|
// generateInternalToken returns a 32-byte hex token used by the kanban-mcp
|
|
// subprocess to call back into /api/tool/{name}. Generated fresh per process.
|
|
func generateInternalToken() string {
|
|
b := make([]byte, 32)
|
|
if _, err := rand.Read(b); err != nil {
|
|
panic("rand.Read: " + err.Error())
|
|
}
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
// handleInternalTool exposes executeTool via HTTP for the MCP subprocess.
|
|
// Auth: shared internal token in X-Internal-Token header. Constant-time compare.
|
|
func handleInternalTool(db *DB, expectedToken string, logger *ChatLogger) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
got := r.Header.Get(internalTokenHeader)
|
|
if subtle.ConstantTimeCompare([]byte(got), []byte(expectedToken)) != 1 {
|
|
infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusUnauthorized, Code: "unauthorized", Message: "invalid internal token"})
|
|
return
|
|
}
|
|
name := r.PathValue("name")
|
|
if name == "" {
|
|
infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusBadRequest, Code: "bad_request", Message: "tool name required"})
|
|
return
|
|
}
|
|
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxBodyBytes))
|
|
if err != nil {
|
|
infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusBadRequest, Code: "bad_request", Message: err.Error()})
|
|
return
|
|
}
|
|
if len(body) == 0 {
|
|
body = []byte("{}")
|
|
}
|
|
input := json.RawMessage(body)
|
|
if err := validateToolName(name); err != nil {
|
|
infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusNotFound, Code: "unknown_tool", Message: err.Error()})
|
|
return
|
|
}
|
|
res := executeTool(db, name, input)
|
|
if logger != nil {
|
|
logger.Log(name, input, res)
|
|
}
|
|
// Always 200 — MCP-side maps res.OK to MCP isError.
|
|
infra.HTTPJSONResponse(w, http.StatusOK, res)
|
|
}
|
|
}
|