* feat(log): log all errors at handling point with context This change ensures all errors are logged at the point where they are handled, including contextual information such as operations and logical stack traces. Key changes: - Added `StackTrace` and `FormatStackTrace` to `pkg/log/errors.go`. - Enhanced `Logger.log` in `pkg/log/log.go` to automatically extract and log `op` and `stack` keys when an error is passed in keyvals. - Updated CLI logging and output helpers to support structured logging. - Updated CLI fatal error handlers to log errors before exiting. - Audited and updated error logging in MCP service (tool handlers and TCP transport), CLI background services (signal and health), and Agentic task handlers. * feat(log): log all errors at handling point with context This change ensures all errors are logged at the point where they are handled, including contextual information such as operations and logical stack traces. Key changes: - Added `StackTrace` and `FormatStackTrace` to `pkg/log/errors.go`. - Enhanced `Logger.log` in `pkg/log/log.go` to automatically extract and log `op` and `stack` keys when an error is passed in keyvals. - Updated CLI logging and output helpers to support structured logging. - Updated CLI fatal error handlers to log errors before exiting. - Audited and updated error logging in MCP service (tool handlers and TCP transport), CLI background services (signal and health), and Agentic task handlers. - Fixed formatting in `pkg/mcp/mcp.go` and `pkg/io/local/client.go`. - Removed unused `fmt` import in `pkg/cli/runtime.go`. * feat(log): log all errors at handling point with context This change ensures all errors are logged at the point where they are handled, including contextual information such as operations and logical stack traces. Key changes: - Added `StackTrace` and `FormatStackTrace` to `pkg/log/errors.go`. - Enhanced `Logger.log` in `pkg/log/log.go` to automatically extract and log `op` and `stack` keys when an error is passed in keyvals. - Updated CLI logging and output helpers to support structured logging. - Updated CLI fatal error handlers to log errors before exiting. - Audited and updated error logging in MCP service (tool handlers and TCP transport), CLI background services (signal and health), and Agentic task handlers. - Fixed formatting in `pkg/mcp/mcp.go` and `pkg/io/local/client.go`. - Removed unused `fmt` import in `pkg/cli/runtime.go`. - Fixed CI failure in `auto-merge` workflow by providing explicit repository context to the GitHub CLI. * feat(log): address PR feedback and improve error context extraction Addressed feedback from PR review: - Improved `Fatalf` and other fatal functions in `pkg/cli/errors.go` to use structured logging for the formatted message. - Added direct unit tests for `StackTrace` and `FormatStackTrace` in `pkg/log/errors_test.go`, covering edge cases like plain errors, nil errors, and mixed error chains. - Optimized the automatic context extraction loop in `pkg/log/log.go` by capturing the original length of keyvals. - Fixed a bug in `StackTrace` where operations were duplicated when the error chain included non-`*log.Err` errors. - Fixed formatting and unused imports from previous commits. * fix: address code review comments - Simplify Fatalf logging by removing redundant format parameter (the formatted message is already logged as "msg") - Tests for StackTrace/FormatStackTrace edge cases already exist - Loop optimization in pkg/log/log.go already implemented Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <developers@lethean.io> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
140 lines
3.3 KiB
Go
140 lines
3.3 KiB
Go
package mcp
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"io"
|
|
"net"
|
|
|
|
"github.com/host-uk/core/pkg/log"
|
|
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
// maxMCPMessageSize is the maximum size for MCP JSON-RPC messages (10 MB).
|
|
const maxMCPMessageSize = 10 * 1024 * 1024
|
|
|
|
// TCPTransport manages a TCP listener for MCP.
|
|
type TCPTransport struct {
|
|
addr string
|
|
listener net.Listener
|
|
}
|
|
|
|
// NewTCPTransport creates a new TCP transport listener.
|
|
// It listens on the provided address (e.g. "localhost:9100").
|
|
func NewTCPTransport(addr string) (*TCPTransport, error) {
|
|
listener, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &TCPTransport{addr: addr, listener: listener}, nil
|
|
}
|
|
|
|
// ServeTCP starts a TCP server for the MCP service.
|
|
// It accepts connections and spawns a new MCP server session for each connection.
|
|
func (s *Service) ServeTCP(ctx context.Context, addr string) error {
|
|
t, err := NewTCPTransport(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = t.listener.Close() }()
|
|
|
|
// Close listener when context is cancelled to unblock Accept
|
|
go func() {
|
|
<-ctx.Done()
|
|
_ = t.listener.Close()
|
|
}()
|
|
|
|
if addr == "" {
|
|
addr = t.listener.Addr().String()
|
|
}
|
|
log.Info("MCP TCP server listening", "addr", addr)
|
|
|
|
for {
|
|
conn, err := t.listener.Accept()
|
|
if err != nil {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
default:
|
|
log.Error("mcp: accept error", "err", err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
go s.handleConnection(ctx, conn)
|
|
}
|
|
}
|
|
|
|
func (s *Service) handleConnection(ctx context.Context, conn net.Conn) {
|
|
// Note: We don't defer conn.Close() here because it's closed by the Server/Transport
|
|
|
|
// Create new server instance for this connection
|
|
impl := &mcp.Implementation{
|
|
Name: "core-cli",
|
|
Version: "0.1.0",
|
|
}
|
|
server := mcp.NewServer(impl, nil)
|
|
s.registerTools(server)
|
|
|
|
// Create transport for this connection
|
|
transport := &connTransport{conn: conn}
|
|
|
|
// Run server (blocks until connection closed)
|
|
// Server.Run calls Connect, then Read loop.
|
|
if err := server.Run(ctx, transport); err != nil {
|
|
log.Error("mcp: connection error", "err", err, "remote", conn.RemoteAddr())
|
|
}
|
|
}
|
|
|
|
// connTransport adapts net.Conn to mcp.Transport
|
|
type connTransport struct {
|
|
conn net.Conn
|
|
}
|
|
|
|
func (t *connTransport) Connect(ctx context.Context) (mcp.Connection, error) {
|
|
scanner := bufio.NewScanner(t.conn)
|
|
scanner.Buffer(make([]byte, 64*1024), maxMCPMessageSize)
|
|
return &connConnection{
|
|
conn: t.conn,
|
|
scanner: scanner,
|
|
}, nil
|
|
}
|
|
|
|
// connConnection implements mcp.Connection
|
|
type connConnection struct {
|
|
conn net.Conn
|
|
scanner *bufio.Scanner
|
|
}
|
|
|
|
func (c *connConnection) Read(ctx context.Context) (jsonrpc.Message, error) {
|
|
// Blocks until line is read
|
|
if !c.scanner.Scan() {
|
|
if err := c.scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
// EOF - connection closed cleanly
|
|
return nil, io.EOF
|
|
}
|
|
line := c.scanner.Bytes()
|
|
return jsonrpc.DecodeMessage(line)
|
|
}
|
|
|
|
func (c *connConnection) Write(ctx context.Context, msg jsonrpc.Message) error {
|
|
data, err := jsonrpc.EncodeMessage(msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Append newline for line-delimited JSON
|
|
data = append(data, '\n')
|
|
_, err = c.conn.Write(data)
|
|
return err
|
|
}
|
|
|
|
func (c *connConnection) Close() error {
|
|
return c.conn.Close()
|
|
}
|
|
|
|
func (c *connConnection) SessionID() string {
|
|
return "tcp-session" // Unique ID might be better, but optional
|
|
}
|