Backend: Compact verbose comments in config and MCP handler

This commit is contained in:
Michael Mayer 2026-05-15 17:50:08 +00:00
parent 2e93a02195
commit c802b05e09
2 changed files with 17 additions and 55 deletions

View file

@ -67,14 +67,8 @@ func ServeMCP(router *gin.RouterGroup) {
Version: conf.Version(),
}, conf.Edition())
// Streamable HTTP handler. Warn-level logging keeps the default log
// quiet under normal operation while still surfacing SDK warnings.
// McpSessionTimeout bounds how long idle sessions linger; active
// clients renew the timer on every request, so interactive IDE use
// is unaffected while abandoned sessions free up promptly.
// CrossOriginProtection must be set explicitly: go-sdk v1.6.0 dropped
// the implicit default that previously rejected cross-origin requests
// when the field was nil (modelcontextprotocol/go-sdk#906).
// Streamable HTTP handler with warn-level logging and an explicit
// CrossOriginProtection (go-sdk no longer enables it implicitly).
handler := sdkmcp.NewStreamableHTTPHandler(
func(r *http.Request) *sdkmcp.Server { return mcpServer },
&sdkmcp.StreamableHTTPOptions{
@ -84,16 +78,10 @@ func ServeMCP(router *gin.RouterGroup) {
},
)
// mcpHandler authenticates each request, caps the JSON-RPC payload
// size before delegating to the SDK handler, and translates an
// oversized-body condition into a standard 413 response. In public
// mode Session() returns the default public session (treated as
// admin by the ACL), so the read-only tools registered today are
// reachable anonymously — intentional to support demo deployments
// such as demo.photoprism.app. Any future tool that touches
// per-user state, the database, or mutates anything MUST NOT be
// registered on this shared server without an additional per-tool
// gate; see internal/mcp/README.md for the recommended patterns.
// mcpHandler authenticates each request, caps the JSON-RPC payload size,
// and surfaces 413 on overflow. Public mode reaches read-only tools as
// the default public session; any tool that touches per-user state must
// add an explicit gate before being registered on this shared server.
mcpHandler := func(c *gin.Context) {
s := Auth(c, acl.ResourceMCP, acl.ActionView)
@ -112,13 +100,8 @@ func ServeMCP(router *gin.RouterGroup) {
return
}
// Cap the request body size before the upstream SDK reads it
// via io.ReadAll. LimitRequestBodyBytes swaps c.Request.Body
// for an http.MaxBytesReader, matching the shared pattern used
// by every other JSON API handler; mcpLimitReader then tracks
// whether the cap was exceeded so the response-writer wrapper
// can rewrite the SDK's 400 "failed to read body" into the
// standard 413 we use for oversized JSON bodies elsewhere.
// Cap the body before the SDK reads it; the wrapper rewrites the
// SDK's "failed to read body" 400 into a standard 413 on overflow.
LimitRequestBodyBytes(c, MaxMCPRequestBytes)
tripped := &atomic.Bool{}

View file

@ -37,27 +37,12 @@ func (c *Config) IndexWorkersReason() string {
return reason
}
// indexWorkers derives the indexing worker count from the configured
// option, the available logical CPUs (runtime.NumCPU(), cgroup-aware),
// and the database driver. It returns both the count and a short reason
// tag so callers can surface the rationale without re-deriving it.
//
// Auto-detection rules:
//
// low-memory below RecommendedMem we always run a single worker
// sqlite-cap SQLite serializes writes, so cap to 4 on 8+ CPU hosts
// or whenever the operator pinned more than 4
// configured honor the operator override (clamped to runtime.NumCPU())
// auto half of runtime.NumCPU() to leave headroom for OS, DB,
// HTTP serving, and background workers
// single-cpu exactly one CPU is visible — fall back to 1 worker
//
// The implementation deliberately does not consult cpuid.CPU.PhysicalCores
// because that value reads CPUID leaf 0xB sub-leaf 1 (per package only,
// not system-wide), can return 0 when the topology is masked by a
// hypervisor, and is unreliable on dual-socket Xeons and hybrid Intel
// CPUs. runtime.NumCPU() is the cgroup- and affinity-aware figure that
// reflects what this process can actually schedule onto.
// indexWorkers derives the indexing worker count from the configured option,
// runtime.NumCPU(), and the database driver. Returns the count and a reason
// tag (low-memory, sqlite-cap, configured, configured-clamped, auto, single-cpu)
// so callers can surface the rationale without re-deriving it. Uses
// runtime.NumCPU() (cgroup- and affinity-aware) rather than CPUID-based
// physical-core probes, which are unreliable under virtualization.
func (c *Config) indexWorkers() (n int, reason string) {
// Cap to one worker on systems below the recommended memory threshold.
if TotalMem < RecommendedMem {
@ -88,10 +73,7 @@ func (c *Config) indexWorkers() (n int, reason string) {
return configured, "configured"
}
// Default to half the visible CPUs to leave headroom for the OS, the
// database, HTTP serving, and other background workers. With HT this
// approximates the physical core count without depending on a
// fragile per-package CPUID read.
// Half the visible CPUs leaves headroom for OS, DB, HTTP, and other workers.
if half := cpus / 2; half >= 1 {
return half, "auto"
}
@ -99,11 +81,8 @@ func (c *Config) indexWorkers() (n int, reason string) {
return 1, "single-cpu"
}
// parseIndexWorkers normalizes the configured index-workers option to an
// integer. Empty strings, the IndexWorkersAuto sentinel, and unparsable
// values map to 0 so IndexWorkers() falls through to the derived count;
// numeric strings (positive or negative) parse with the same semantics
// as the previous int field.
// parseIndexWorkers normalizes the index-workers option to an int; empty,
// "auto", and unparseable values return 0 to fall through to auto-derivation.
func parseIndexWorkers(value string) int {
value = strings.TrimSpace(value)