316c2c4275
check-and-test / check-and-test (pull_request) Has been cancelled
Closes #13. Today GITEA_DEBUG=true only flips the zap level to debug, dumps a contextless `Text Result:` blob per tool call, and enables the gitea SDK's own debug stream (which doesn't merge with our log file). That is not enough to diagnose a misbehaving tool call in production. This change introduces: - A `pkg/middleware.ToolLogging` ToolHandlerMiddleware that logs each tool invocation on entry and exit with structured fields: tool name, redacted args, request_id, token source, duration_ms, and status. The request_id is stashed in the context so downstream layers can correlate. - A `loggingRoundTripper` in `pkg/gitea` that wraps the shared HTTP transport and emits one structured line per upstream Gitea API call (method, token-stripped URL, status, duration_ms, bytes). This replaces — and is far more useful than — `gitea.SetDebugMode()`. - An effective-config dump at startup (`logEffectiveConfig`) when debug+config scope is on, so "did my env var get picked up?" becomes answerable from the log file. - Token source tracking. CLI flag / GITEA_ACCESS_TOKEN / GITEA_ACCESS_TOKEN_FILE / per-request Authorization header are now distinguished in logs as flag/env/env-file/header. - A baseline redactor in `pkg/debug` that masks args / query params whose keys look like secrets (token, password, secret, api_key, authorization, …). This is the placeholder for issue #5's shared redactor; once that lands, callers here should defer to it. - `GITEA_DEBUG_SCOPES` env var to scope debug output to any subset of `tools,http,config`. Default (unset) is "everything". Test coverage was added for the redactor, scope flag, request-id helpers, preview truncation (multibyte-safe), and the middleware's context wiring + error propagation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
61 lines
2.0 KiB
Go
61 lines
2.0 KiB
Go
package to
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"gitea.com/gitea/gitea-mcp/pkg/debug"
|
|
"gitea.com/gitea/gitea-mcp/pkg/flag"
|
|
"gitea.com/gitea/gitea-mcp/pkg/log"
|
|
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func TextResult(v any) (*mcp.CallToolResult, error) {
|
|
resultBytes, err := json.Marshal(v)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal result err: %v", err)
|
|
}
|
|
if flag.DebugScopeEnabled("tools") {
|
|
// We can't take a context here without breaking every caller, so we
|
|
// log without the request_id correlation. The middleware's exit log
|
|
// records the request_id + duration; this entry adds a (truncated)
|
|
// payload preview for callers that want to see what came back.
|
|
log.Debug("tool call: result",
|
|
zap.Int("bytes", len(resultBytes)),
|
|
zap.String("preview", debug.TruncatePreview(string(resultBytes))),
|
|
)
|
|
}
|
|
return mcp.NewToolResultText(string(resultBytes)), nil
|
|
}
|
|
|
|
// TextResultCtx is the context-aware variant of TextResult. New call sites
|
|
// should prefer it so log lines carry the request_id and tool name. The
|
|
// non-context TextResult above is kept for backwards compatibility with
|
|
// the many existing tool handlers.
|
|
func TextResultCtx(ctx context.Context, v any) (*mcp.CallToolResult, error) {
|
|
resultBytes, err := json.Marshal(v)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal result err: %v", err)
|
|
}
|
|
if flag.DebugScopeEnabled("tools") {
|
|
log.Debug("tool call: result",
|
|
zap.String("tool", debug.ToolNameFromContext(ctx)),
|
|
zap.String("request_id", debug.RequestIDFromContext(ctx)),
|
|
zap.Int("bytes", len(resultBytes)),
|
|
zap.String("preview", debug.TruncatePreview(string(resultBytes))),
|
|
)
|
|
}
|
|
return mcp.NewToolResultText(string(resultBytes)), nil
|
|
}
|
|
|
|
// ErrorResult returns the error to the MCP layer. We no longer log here —
|
|
// the ToolLogging middleware emits a structured error entry that includes
|
|
// the tool name and request_id, which is far more useful than the bare
|
|
// `log.Errorf("%s", err)` this used to do.
|
|
func ErrorResult(err error) (*mcp.CallToolResult, error) {
|
|
return nil, err
|
|
}
|