Skip to main content
Loom Core docs

Core Rules

MCP Server Error Handling Guide

This guide defines current error-handling standards for Loom Core MCP servers.

Core Rules

  1. Validate all input early.
  2. Return structured errors via pkg/mcperror.
  3. Return mcp.ErrorResult(err), nil from handlers for user-facing failures.
  4. Wrap external/API failures with service context.
  5. Do not panic in tool handlers.
import (
    "gitlab.flexinfer.ai/libs/mcp-go"

    "github.com/crb2nu/loom/pkg/mcperror"
    "github.com/crb2nu/loom/pkg/validate"
)

Handler Pattern

func (s *server) handleThing(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
    v := validate.NewArgs(args)
    project := v.Required("project")
    page := v.Int("page", 1)
    if err := v.Validate(); err != nil {
        return mcp.ErrorResult(err), nil
    }

    if page < 1 {
        return mcp.ErrorResult(mcperror.InvalidParam("page", "must be >= 1")), nil
    }

    out, err := s.client.Fetch(ctx, project)
    if err != nil {
        return mcp.ErrorResult(mcperror.WrapAPI("MyService", err)), nil
    }

    return mcp.JSONResult(out)
}

Error Helpers

Use pkg/mcperror helpers consistently:

  • Input: RequiredParam, InvalidParam, Validation, ParseError
  • API/service: APIError, WrapAPI, ServiceUnavailable, RateLimited
  • Resources: NotFound
  • Configuration: NotConfigured
  • Generic failures: OperationFailed, ServerError

HTTP/API Mapping

When translating upstream HTTP errors, use mcperror.APIError(service, statusCode, body).

Expected mapping:

  • 401 -> UNAUTHORIZED
  • 403 -> FORBIDDEN
  • 404 -> NOT_FOUND
  • 429 -> RATE_LIMITED
  • 5xx -> SERVER_ERROR

Logging Expectations

  • Log warnings/errors with operation context and identifiers.
  • Do not silently discard errors (_ = err) unless truly ignorable and documented.
  • Avoid logging secrets/token values.

Checklist for New or Updated MCP Servers

  • Uses validate.NewArgs for parsing and validation
  • Returns structured errors via mcp.ErrorResult
  • Wraps external failures with mcperror.WrapAPI/APIError
  • No panics in handler paths
  • Has tests for at least one error path per tool family
  • Updates docs/CHANGELOG for behavior-visible changes

Migration Tracker

Legacy servers use return nil, err or return nil, fmt.Errorf(...) in handler functions. These should be migrated to return mcp.ErrorResult(err), nil.

CI guardrail: scripts/ci/check_error_handling.sh prevents the count from increasing above its configured baseline (ERROR_HANDLING_BASELINE, default defined in the script). Lower the baseline as servers are migrated.

Quick check:

scripts/ci/check_error_handling.sh
Servernil,err CountHandler-level?Status
mcp-terraform24Yes (16 handler, 8 helper)Pending
mcp-linear18Yes (12 handler, 6 helper)Pending
mcp-pagerduty17Yes (13 handler, 4 helper)Pending
mcp-vault17Yes (9 handler, 8 helper)Pending
mcp-k8s16Yes (16 handler)Pending
mcp-notion16Yes (11 handler, 5 helper)Pending
mcp-git13Yes (13 handler)Pending
mcp-sentry12Yes (8 handler, 4 helper)Pending
mcp-aws12MixedPending
mcp-mongodb12MixedPending
mcp-elasticsearch11MixedPending
mcp-prometheus10Yes (8 handler, 2 helper)Pending
mcp-tavily10Yes (8 handler, 2 helper)Pending
mcp-gcp10MixedPending
mcp-grafana8Helper onlyDone
mcp-morph-embeddings8MixedPending
mcp-substack7MixedPending
mcp-godot7MixedPending
mcp-cloudflare6Helper onlyDone
mcp-qdrant6MixedPending
mcp-zep6MixedPending
mcp-confluence6MixedPending
mcp-loki6MixedPending
mcp-browserkit6MixedPending
mcp-sequentialthinking6MixedPending
mcp-alertmanager5MixedPending
mcp-gitlab5Yes (4 handler, 1 helper)Pending
mcp-github-actions4MixedPending
mcp-flux4Helper onlyDone
mcp-argocd4MixedPending
mcp-github3MixedPending
mcp-youtube3MixedPending
mcp-helm2Helper onlyDone
mcp-jira2MixedPending
mcp-minio2MixedPending
mcp-docker1Helper onlyDone
mcp-neo4j1MixedPending
mcp-crypto1MixedPending
mcp-redis0N/ADone
mcp-slack6MixedPending

"Helper only" means all return nil, err instances are in utility functions (HTTP wrappers, CLI runners) called by handlers that already convert via mcp.ErrorResult(err), nil. These are correct and need no migration.

Priority: Migrate "Yes (N handler)" servers first -- those return raw Go errors directly from tool handlers, violating the MCP protocol.