diff --git a/internal/cmd/proxy.go b/internal/cmd/proxy.go
index 354074a86..b24522c43 100644
--- a/internal/cmd/proxy.go
+++ b/internal/cmd/proxy.go
@@ -189,7 +189,7 @@ func runProxy(cmd *cobra.Command, args []string) error {
 	// Resolve GitHub API URL: flag → env vars → default
 	apiURL := proxyAPIURL
 	if apiURL == "" {
-		apiURL = proxy.DeriveGitHubAPIURL()
+		apiURL = envutil.DeriveGitHubAPIURL("")
 	}
 	if apiURL == "" {
 		apiURL = proxy.DefaultGitHubAPIBase
diff --git a/internal/config/expand.go b/internal/config/expand.go
new file mode 100644
index 000000000..1f15e50f8
--- /dev/null
+++ b/internal/config/expand.go
@@ -0,0 +1,133 @@
+package config
+
+import (
+	"fmt"
+	"os"
+	"regexp"
+
+	"github.com/github/gh-aw-mcpg/internal/config/rules"
+)
+
+// Variable expression pattern: ${VARIABLE_NAME}
+var varExprPattern = regexp.MustCompile(`\$\{([A-Za-z_][A-Za-z0-9_]*)\}`)
+
+// expandVariablesCore is the shared implementation for variable expansion.
+// It works with byte slices and handles the core expansion logic, tracking undefined variables.
+// This eliminates code duplication between expandVariables and ExpandRawJSONVariables.
+// It returns the expanded bytes, a slice of undefined variable names, and an error
+// (currently always nil).
+func expandVariablesCore(data []byte, contextDesc string) ([]byte, []string, error) {
+	logValidation.Printf("Expanding variables: context=%s", contextDesc)
+	var undefinedVars []string
+
+	result := varExprPattern.ReplaceAllFunc(data, func(match []byte) []byte {
+		// Extract variable name (remove ${ and })
+		varName := string(match[2 : len(match)-1])
+
+		if envValue, exists := os.LookupEnv(varName); exists {
+			logValidation.Printf("Expanded variable: %s (found in environment)", varName)
+			return []byte(envValue)
+		}
+
+		// Track undefined variable
+		undefinedVars = append(undefinedVars, varName)
+		logValidation.Printf("Undefined variable: %s", varName)
+		return match // Keep original if undefined
+	})
+
+	logValidation.Printf("Variable expansion completed: context=%s, undefined_count=%d", contextDesc, len(undefinedVars))
+	return result, undefinedVars, nil
+}
+
+// expandVariables expands variable expressions in a string.
+// value is the source string and jsonPath identifies the config location for errors.
+// It returns the expanded string and an error if any variable is undefined.
+func expandVariables(value, jsonPath string) (string, error) {
+	result, undefinedVars, _ := expandVariablesCore([]byte(value), fmt.Sprintf("jsonPath=%s", jsonPath))
+
+	if len(undefinedVars) > 0 {
+		logValidation.Printf("Variable expansion failed: undefined variables=%v", undefinedVars)
+		return "", rules.UndefinedVariable(undefinedVars[0], jsonPath)
+	}
+
+	return string(result), nil
+}
+
+// ExpandRawJSONVariables expands all ${VAR} expressions in JSON data before schema validation.
+// This ensures the schema validates the expanded values, not the variable syntax.
+// It collects undefined variables and reports the first undefined variable as an error.
+func ExpandRawJSONVariables(data []byte) ([]byte, error) {
+	result, undefinedVars, _ := expandVariablesCore(data, "raw JSON data")
+
+	if len(undefinedVars) > 0 {
+		logValidation.Printf("Variable expansion failed: undefined variables=%v", undefinedVars)
+		return nil, rules.UndefinedVariable(undefinedVars[0], "configuration")
+	}
+
+	return result, nil
+}
+
+// expandEnvVariables expands all variable expressions in an env map.
+// env is the map to expand and serverName is used for config-path error context.
+// It returns a new map with expanded values or an error if any variable is undefined.
+func expandEnvVariables(env map[string]string, serverName string) (map[string]string, error) {
+	logValidation.Printf("Expanding env variables for server: %s, count=%d", serverName, len(env))
+	result := make(map[string]string, len(env))
+
+	for key, value := range env {
+		jsonPath := fmt.Sprintf("mcpServers.%s.env.%s", serverName, key)
+
+		expanded, err := expandVariables(value, jsonPath)
+		if err != nil {
+			return nil, err
+		}
+
+		result[key] = expanded
+	}
+
+	logValidation.Printf("Env variable expansion completed for server: %s", serverName)
+	return result, nil
+}
+
+// expandTracingVariables expands ${VAR} expressions in TracingConfig fields.
+// This is called for TOML-loaded configs before validation, mirroring the
+// stdin JSON path where ExpandRawJSONVariables handles expansion.
+func expandTracingVariables(cfg *TracingConfig) error {
+	if cfg == nil {
+		return nil
+	}
+
+	if cfg.Endpoint != "" {
+		expanded, err := expandVariables(cfg.Endpoint, "gateway.opentelemetry.endpoint")
+		if err != nil {
+			return err
+		}
+		cfg.Endpoint = expanded
+	}
+
+	if cfg.TraceID != "" {
+		expanded, err := expandVariables(cfg.TraceID, "gateway.opentelemetry.traceId")
+		if err != nil {
+			return err
+		}
+		cfg.TraceID = expanded
+	}
+
+	if cfg.SpanID != "" {
+		expanded, err := expandVariables(cfg.SpanID, "gateway.opentelemetry.spanId")
+		if err != nil {
+			return err
+		}
+		cfg.SpanID = expanded
+	}
+
+	if cfg.Headers != "" {
+		expanded, err := expandVariables(cfg.Headers, "gateway.opentelemetry.headers")
+		if err != nil {
+			return err
+		}
+		cfg.Headers = expanded
+	}
+
+	return nil
+}
diff --git a/internal/config/validation.go b/internal/config/validation.go
index e3c7b5510..039313e45 100644
--- a/internal/config/validation.go
+++ b/internal/config/validation.go
@@ -16,9 +16,6 @@ import (
 // ValidationError is an alias for rules.ValidationError for backward compatibility
 type ValidationError = rules.ValidationError
 
-// Variable expression pattern: ${VARIABLE_NAME}
-var varExprPattern = regexp.MustCompile(`\$\{([A-Za-z_][A-Za-z0-9_]*)\}`)
-
 // W3C trace context patterns (spec §4.1.3.6)
 var (
 	traceIDPattern = regexp.MustCompile(`^[0-9a-f]{32}$`)
@@ -45,122 +42,6 @@ func logValidateServerFailed(name, reason string) {
 	logValidation.Printf("Validation failed: %s, name=%s", reason, name)
 }
 
-// expandVariablesCore is the shared implementation for variable expansion.
-// It works with byte slices and handles the core expansion logic, tracking undefined variables.
-// This eliminates code duplication between expandVariables and ExpandRawJSONVariables.
-func expandVariablesCore(data []byte, contextDesc string) ([]byte, []string, error) {
-	logValidation.Printf("Expanding variables: context=%s", contextDesc)
-	var undefinedVars []string
-
-	result := varExprPattern.ReplaceAllFunc(data, func(match []byte) []byte {
-		// Extract variable name (remove ${ and })
-		varName := string(match[2 : len(match)-1])
-
-		if envValue, exists := os.LookupEnv(varName); exists {
-			logValidation.Printf("Expanded variable: %s (found in environment)", varName)
-			return []byte(envValue)
-		}
-
-		// Track undefined variable
-		undefinedVars = append(undefinedVars, varName)
-		logValidation.Printf("Undefined variable: %s", varName)
-		return match // Keep original if undefined
-	})
-
-	logValidation.Printf("Variable expansion completed: context=%s, undefined_count=%d", contextDesc, len(undefinedVars))
-	return result, undefinedVars, nil
-}
-
-// expandVariables expands variable expressions in a string
-// Returns the expanded string and error if any variable is undefined
-func expandVariables(value, jsonPath string) (string, error) {
-	result, undefinedVars, _ := expandVariablesCore([]byte(value), fmt.Sprintf("jsonPath=%s", jsonPath))
-
-	if len(undefinedVars) > 0 {
-		logValidation.Printf("Variable expansion failed: undefined variables=%v", undefinedVars)
-		return "", rules.UndefinedVariable(undefinedVars[0], jsonPath)
-	}
-
-	return string(result), nil
-}
-
-// ExpandRawJSONVariables expands all ${VAR} expressions in JSON data before schema validation.
-// This ensures the schema validates the expanded values, not the variable syntax.
-// It collects all undefined variables and reports them in a single error.
-func ExpandRawJSONVariables(data []byte) ([]byte, error) {
-	result, undefinedVars, _ := expandVariablesCore(data, "raw JSON data")
-
-	if len(undefinedVars) > 0 {
-		logValidation.Printf("Variable expansion failed: undefined variables=%v", undefinedVars)
-		return nil, rules.UndefinedVariable(undefinedVars[0], "configuration")
-	}
-
-	return result, nil
-}
-
-// expandEnvVariables expands all variable expressions in an env map
-func expandEnvVariables(env map[string]string, serverName string) (map[string]string, error) {
-	logValidation.Printf("Expanding env variables for server: %s, count=%d", serverName, len(env))
-	result := make(map[string]string, len(env))
-
-	for key, value := range env {
-		jsonPath := fmt.Sprintf("mcpServers.%s.env.%s", serverName, key)
-
-		expanded, err := expandVariables(value, jsonPath)
-		if err != nil {
-			return nil, err
-		}
-
-		result[key] = expanded
-	}
-
-	logValidation.Printf("Env variable expansion completed for server: %s", serverName)
-	return result, nil
-}
-
-// expandTracingVariables expands ${VAR} expressions in TracingConfig fields.
-// This is called for TOML-loaded configs before validation, mirroring the
-// stdin JSON path where ExpandRawJSONVariables handles expansion.
-func expandTracingVariables(cfg *TracingConfig) error {
-	if cfg == nil {
-		return nil
-	}
-
-	if cfg.Endpoint != "" {
-		expanded, err := expandVariables(cfg.Endpoint, "gateway.opentelemetry.endpoint")
-		if err != nil {
-			return err
-		}
-		cfg.Endpoint = expanded
-	}
-
-	if cfg.TraceID != "" {
-		expanded, err := expandVariables(cfg.TraceID, "gateway.opentelemetry.traceId")
-		if err != nil {
-			return err
-		}
-		cfg.TraceID = expanded
-	}
-
-	if cfg.SpanID != "" {
-		expanded, err := expandVariables(cfg.SpanID, "gateway.opentelemetry.spanId")
-		if err != nil {
-			return err
-		}
-		cfg.SpanID = expanded
-	}
-
-	if cfg.Headers != "" {
-		expanded, err := expandVariables(cfg.Headers, "gateway.opentelemetry.headers")
-		if err != nil {
-			return err
-		}
-		cfg.Headers = expanded
-	}
-
-	return nil
-}
-
 // validateMounts validates mount specifications using centralized rules
 func validateMounts(mounts []string, jsonPath string) error {
 	for i, mount := range mounts {
diff --git a/internal/envutil/github.go b/internal/envutil/github.go
index 892f3da3f..1551b66d8 100644
--- a/internal/envutil/github.go
+++ b/internal/envutil/github.go
@@ -1,6 +1,8 @@
 package envutil
 
 import (
+	"fmt"
+	"net/url"
 	"os"
 	"strings"
 
@@ -9,6 +11,9 @@ import (
 
 var logGitHub = logger.New("envutil:github")
 
+// DefaultGitHubAPIBaseURL is the default GitHub API base URL.
+const DefaultGitHubAPIBaseURL = "https://api.github.com"
+
 // LookupGitHubToken searches environment variables for a GitHub token using
 // a canonical priority order. It returns the first non-empty (trimmed) value
 // found, or an empty string if none is set.
@@ -49,3 +54,49 @@ func LookupGitHubAPIURL(defaultURL string) string {
 	logGitHub.Printf("GitHub API URL using default: %s", url)
 	return url
 }
+
+// DeriveGitHubAPIURL resolves the GitHub API URL from environment variables:
+//  1. GITHUB_API_URL
+//  2. GITHUB_SERVER_URL (derived)
+//  3. defaultURL
+func DeriveGitHubAPIURL(defaultURL string) string {
+	if apiURL := LookupGitHubAPIURL(""); apiURL != "" {
+		return apiURL
+	}
+	if serverURL := strings.TrimSpace(os.Getenv("GITHUB_SERVER_URL")); serverURL != "" {
+		derived := deriveAPIFromServerURL(serverURL)
+		if derived != "" {
+			logGitHub.Printf("GitHub API URL derived from GITHUB_SERVER_URL=%s: %s", serverURL, derived)
+			return derived
+		}
+	}
+	return strings.TrimRight(strings.TrimSpace(defaultURL), "/")
+}
+
+// deriveAPIFromServerURL converts a GITHUB_SERVER_URL to the corresponding API endpoint.
+// GHEC tenants (*.ghe.com): https://tenant.ghe.com → https://copilot-api.tenant.ghe.com
+// GitHub.com: https://github.com → https://api.github.com
+// GHES (all others): https://github.example.com → https://github.example.com/api/v3
+func deriveAPIFromServerURL(serverURL string) string {
+	parsed, err := url.Parse(strings.TrimRight(serverURL, "/"))
+	if err != nil || parsed.Host == "" {
+		return ""
+	}
+	if parsed.Scheme != "http" && parsed.Scheme != "https" {
+		return ""
+	}
+
+	hostname := strings.ToLower(parsed.Hostname())
+
+	switch {
+	case hostname == "github.com" || hostname == "www.github.com":
+		return DefaultGitHubAPIBaseURL
+	case strings.HasSuffix(hostname, ".ghe.com"):
+		if port := parsed.Port(); port != "" {
+			return fmt.Sprintf("%s://copilot-api.%s:%s", parsed.Scheme, hostname, port)
+		}
+		return fmt.Sprintf("%s://copilot-api.%s", parsed.Scheme, hostname)
+	default:
+		return fmt.Sprintf("%s://%s/api/v3", parsed.Scheme, parsed.Host)
+	}
+}
diff --git a/internal/envutil/github_test.go b/internal/envutil/github_test.go
index aba1e813a..5a9146381 100644
--- a/internal/envutil/github_test.go
+++ b/internal/envutil/github_test.go
@@ -68,7 +68,7 @@ func TestLookupGitHubToken(t *testing.T) {
 }
 
 func TestLookupGitHubAPIURL(t *testing.T) {
-	const defaultURL = "https://api.github.com"
+	const defaultURL = DefaultGitHubAPIBaseURL
 
 	t.Run("returns default when env not set", func(t *testing.T) {
 		t.Setenv("GITHUB_API_URL", "")
@@ -95,3 +95,117 @@ func TestLookupGitHubAPIURL(t *testing.T) {
 		assert.Equal(t, "https://github.example.com/api/v3", LookupGitHubAPIURL(defaultURL))
 	})
 }
+
+func TestDeriveAPIFromServerURL(t *testing.T) {
+	tests := []struct {
+		name      string
+		serverURL string
+		expected  string
+	}{
+		{
+			name:      "github.com returns default",
+			serverURL: "https://github.com",
+			expected:  DefaultGitHubAPIBaseURL,
+		},
+		{
+			name:      "www.github.com returns default",
+			serverURL: "https://www.github.com",
+			expected:  DefaultGitHubAPIBaseURL,
+		},
+		{
+			name:      "GHEC tenant derives copilot-api subdomain",
+			serverURL: "https://mycompany.ghe.com",
+			expected:  "https://copilot-api.mycompany.ghe.com",
+		},
+		{
+			name:      "GHES uses /api/v3 path",
+			serverURL: "https://github.mycompany.com",
+			expected:  "https://github.mycompany.com/api/v3",
+		},
+		{
+			name:      "GHEC tenant with port",
+			serverURL: "https://mycompany.ghe.com:8443",
+			expected:  "https://copilot-api.mycompany.ghe.com:8443",
+		},
+		{
+			name:      "GHES with port",
+			serverURL: "https://github.example.com:8443",
+			expected:  "https://github.example.com:8443/api/v3",
+		},
+		{
+			name:      "invalid URL",
+			serverURL: "not-a-url",
+			expected:  "",
+		},
+		{
+			name:      "missing scheme is rejected",
+			serverURL: "//github.example.com",
+			expected:  "",
+		},
+		{
+			name:      "unsupported scheme is rejected",
+			serverURL: "ftp://github.example.com",
+			expected:  "",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert.Equal(t, tt.expected, deriveAPIFromServerURL(tt.serverURL))
+		})
+	}
+}
+
+func TestDeriveGitHubAPIURL(t *testing.T) {
+	tests := []struct {
+		name       string
+		envAPIURL  string
+		envSrvURL  string
+		defaultURL string
+		expected   string
+	}{
+		{
+			name:       "default when no env vars",
+			envAPIURL:  "",
+			envSrvURL:  "",
+			defaultURL: DefaultGitHubAPIBaseURL,
+			expected:   DefaultGitHubAPIBaseURL,
+		},
+		{
+			name:       "empty default when no env vars",
+			envAPIURL:  "",
+			envSrvURL:  "",
+			defaultURL: "",
+			expected:   "",
+		},
+		{
+			name:       "GITHUB_API_URL takes priority",
+			envAPIURL:  "https://api.custom.ghe.com",
+			envSrvURL:  "https://other.ghe.com",
+			defaultURL: DefaultGitHubAPIBaseURL,
+			expected:   "https://api.custom.ghe.com",
+		},
+		{
+			name:       "derive from GITHUB_SERVER_URL",
+			envAPIURL:  "",
+			envSrvURL:  "https://mycompany.ghe.com",
+			defaultURL: DefaultGitHubAPIBaseURL,
+			expected:   "https://copilot-api.mycompany.ghe.com",
+		},
+		{
+			name:       "invalid GITHUB_SERVER_URL falls back to default",
+			envAPIURL:  "",
+			envSrvURL:  "not-a-valid-url",
+			defaultURL: DefaultGitHubAPIBaseURL,
+			expected:   DefaultGitHubAPIBaseURL,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Setenv("GITHUB_API_URL", tt.envAPIURL)
+			t.Setenv("GITHUB_SERVER_URL", tt.envSrvURL)
+			assert.Equal(t, tt.expected, DeriveGitHubAPIURL(tt.defaultURL))
+		})
+	}
+}
diff --git a/internal/mcp/tool_result.go b/internal/mcp/tool_result.go
index 9c612aae6..7eb5c622a 100644
--- a/internal/mcp/tool_result.go
+++ b/internal/mcp/tool_result.go
@@ -142,3 +142,17 @@ func ParseToolArguments(req *sdk.CallToolRequest) (map[string]interface{}, error
 	logToolResult.Printf("Parsed %d arguments", len(toolArgs))
 	return toolArgs, nil
 }
+
+// NewErrorCallToolResult creates a standard error CallToolResult with the error
+// message included as text content.
+func NewErrorCallToolResult(err error) (*sdk.CallToolResult, interface{}, error) {
+	if err == nil {
+		err = fmt.Errorf("unknown error")
+	}
+	return &sdk.CallToolResult{
+		IsError: true,
+		Content: []sdk.Content{
+			&sdk.TextContent{Text: err.Error()},
+		},
+	}, nil, err
+}
diff --git a/internal/proxy/derive_api_url_test.go b/internal/proxy/derive_api_url_test.go
deleted file mode 100644
index 00c5308f6..000000000
--- a/internal/proxy/derive_api_url_test.go
+++ /dev/null
@@ -1,194 +0,0 @@
-package proxy
-
-import (
-	"os"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-// TestDeriveAPIFromServerURL_ExtendedCoverage covers branches in deriveAPIFromServerURL
-// that are not exercised by the existing TestDeriveAPIFromServerURL test:
-//
-//   - www.github.com hostname (alternate form of github.com handled in same switch case)
-//   - Upper-case hostname is normalised before matching
-//   - Non-HTTPS (http) GHEC tenants preserve the http scheme
-//   - Non-HTTPS GHES servers preserve the http scheme
-//   - github.com URL that includes a path component (path is ignored)
-//   - URL with empty host (parse returns empty Host)
-//   - ghe.com itself is NOT treated as a GHEC tenant (HasSuffix requires a dot prefix)
-//   - deeply-nested GHEC subdomain
-func TestDeriveAPIFromServerURL_ExtendedCoverage(t *testing.T) {
-	tests := []struct {
-		name      string
-		serverURL string
-		expected  string
-	}{
-		// --- github.com / www.github.com ---
-		{
-			// www.github.com is explicitly handled by the same switch case as github.com.
-			// The existing test only covers "github.com" without the www prefix.
-			name:      "www.github.com returns default API base",
-			serverURL: "https://www.github.com",
-			expected:  DefaultGitHubAPIBase,
-		},
-		{
-			// www.github.com with trailing slash
-			name:      "www.github.com with trailing slash",
-			serverURL: "https://www.github.com/",
-			expected:  DefaultGitHubAPIBase,
-		},
-		{
-			// Hostname comparisons use strings.ToLower, so upper-case is normalised.
-			name:      "GITHUB.COM upper-case normalised to DefaultGitHubAPIBase",
-			serverURL: "https://GITHUB.COM",
-			expected:  DefaultGitHubAPIBase,
-		},
-		{
-			// A path in the URL should not affect the derived API URL because
-			// parsed.Hostname() returns only the host portion.
-			name:      "github.com with path component",
-			serverURL: "https://github.com/org/repo",
-			expected:  DefaultGitHubAPIBase,
-		},
-
-		// --- GHEC (*.ghe.com) ---
-		{
-			// When the scheme is http (not https), the scheme must be preserved.
-			name:      "http GHEC tenant preserves http scheme",
-			serverURL: "http://mycompany.ghe.com",
-			expected:  "http://copilot-api.mycompany.ghe.com",
-		},
-		{
-			// Deeply-nested subdomain: every part before .ghe.com is the tenant label.
-			name:      "deeply nested GHEC subdomain",
-			serverURL: "https://deep.nested.tenant.ghe.com",
-			expected:  "https://copilot-api.deep.nested.tenant.ghe.com",
-		},
-		{
-			// Upper-case .GHE.COM hostname is normalised before the HasSuffix check.
-			name:      "upper-case GHE.COM tenant normalised",
-			serverURL: "https://MYCOMPANY.GHE.COM",
-			expected:  "https://copilot-api.mycompany.ghe.com",
-		},
-		{
-			// ghe.com itself: HasSuffix("ghe.com", ".ghe.com") is false, so it falls
-			// through to the GHES case and receives the /api/v3 suffix.
-			name:      "bare ghe.com is treated as GHES not GHEC",
-			serverURL: "https://ghe.com",
-			expected:  "https://ghe.com/api/v3",
-		},
-
-		// --- GHES (all other hosts) ---
-		{
-			// Non-HTTPS GHES server must preserve the http scheme.
-			name:      "http GHES server preserves http scheme",
-			serverURL: "http://github.mycompany.com",
-			expected:  "http://github.mycompany.com/api/v3",
-		},
-		{
-			// URL with a path: the path is NOT included in parsed.Host so it is
-			// correctly omitted from the derived GHES URL.
-			name:      "GHES URL with path component ignores path",
-			serverURL: "https://github.example.com/enterprise",
-			expected:  "https://github.example.com/api/v3",
-		},
-
-		// --- Edge cases: parse errors and empty host ---
-		{
-			// A URL with a scheme but no host at all (parsed.Host == "").
-			// The function should return "" to signal an unusable URL.
-			name:      "URL with empty host returns empty string",
-			serverURL: "http://",
-			expected:  "",
-		},
-		{
-			// A file:// URL has no network host, so parsed.Host is "".
-			name:      "file URL with no host returns empty string",
-			serverURL: "file:///some/path",
-			expected:  "",
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			result := deriveAPIFromServerURL(tt.serverURL)
-			assert.Equal(t, tt.expected, result, "deriveAPIFromServerURL(%q)", tt.serverURL)
-		})
-	}
-}
-
-// TestDeriveGitHubAPIURL_ExtendedCoverage covers branches in DeriveGitHubAPIURL that
-// the existing TestDeriveGitHubAPIURL does not exercise:
-//
-//   - GITHUB_SERVER_URL set to an invalid URL (deriveAPIFromServerURL returns "") → function returns ""
-//   - GITHUB_SERVER_URL set to a URL with empty host → function returns ""
-//   - GITHUB_API_URL set to empty string explicitly → falls through to GITHUB_SERVER_URL
-//   - Neither var set (already tested) just to confirm baseline
-func TestDeriveGitHubAPIURL_ExtendedCoverage(t *testing.T) {
-	tests := []struct {
-		name     string
-		envVars  map[string]string
-		expected string
-	}{
-		{
-			// When GITHUB_SERVER_URL is set to an invalid URL that cannot be parsed
-			// into a usable host, deriveAPIFromServerURL returns "" and
-			// DeriveGitHubAPIURL must propagate that by returning "" as well.
-			name:     "GITHUB_SERVER_URL invalid URL returns empty",
-			envVars:  map[string]string{"GITHUB_SERVER_URL": "not-a-valid-url"},
-			expected: "",
-		},
-		{
-			// A URL that parses without error but has an empty host also causes
-			// deriveAPIFromServerURL to return "", which should propagate.
-			name:     "GITHUB_SERVER_URL with empty host returns empty",
-			envVars:  map[string]string{"GITHUB_SERVER_URL": "http://"},
-			expected: "",
-		},
-		{
-			// GITHUB_API_URL set to an explicitly empty string is treated the same
-			// as not setting it at all (os.Getenv returns ""), so the function falls
-			// through to check GITHUB_SERVER_URL.
-			name:     "empty GITHUB_API_URL falls through to GITHUB_SERVER_URL",
-			envVars:  map[string]string{"GITHUB_API_URL": "", "GITHUB_SERVER_URL": "https://mycompany.ghe.com"},
-			expected: "https://copilot-api.mycompany.ghe.com",
-		},
-		{
-			// When GITHUB_SERVER_URL is www.github.com (the untested alias),
-			// DeriveGitHubAPIURL should derive DefaultGitHubAPIBase.
-			name:     "GITHUB_SERVER_URL www.github.com returns DefaultGitHubAPIBase",
-			envVars:  map[string]string{"GITHUB_SERVER_URL": "https://www.github.com"},
-			expected: DefaultGitHubAPIBase,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			// Save and restore the two env vars around each sub-test.
-			savedAPI, hadAPI := os.LookupEnv("GITHUB_API_URL")
-			savedServer, hadServer := os.LookupEnv("GITHUB_SERVER_URL")
-			t.Cleanup(func() {
-				if hadAPI {
-					_ = os.Setenv("GITHUB_API_URL", savedAPI)
-				} else {
-					_ = os.Unsetenv("GITHUB_API_URL")
-				}
-				if hadServer {
-					_ = os.Setenv("GITHUB_SERVER_URL", savedServer)
-				} else {
-					_ = os.Unsetenv("GITHUB_SERVER_URL")
-				}
-			})
-			_ = os.Unsetenv("GITHUB_API_URL")
-			_ = os.Unsetenv("GITHUB_SERVER_URL")
-
-			for k, v := range tt.envVars {
-				_ = os.Setenv(k, v)
-			}
-
-			result := DeriveGitHubAPIURL()
-			assert.Equal(t, tt.expected, result)
-		})
-	}
-}
diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go
index bdd643c31..3b8cb41b9 100644
--- a/internal/proxy/proxy.go
+++ b/internal/proxy/proxy.go
@@ -13,13 +13,11 @@ import (
 	"log"
 	"net/http"
 	"net/url"
-	"os"
 	"strings"
 	"time"
 
 	"github.com/github/gh-aw-mcpg/internal/config"
 	"github.com/github/gh-aw-mcpg/internal/difc"
-	"github.com/github/gh-aw-mcpg/internal/envutil"
 	"github.com/github/gh-aw-mcpg/internal/guard"
 	"github.com/github/gh-aw-mcpg/internal/logger"
 	"github.com/github/gh-aw-mcpg/internal/tracing"
@@ -35,58 +33,6 @@ const (
 	ghHostPathPrefix = "/api/v3"
 )
 
-// DeriveGitHubAPIURL resolves the upstream GitHub API URL from environment
-// variables. Priority order:
-//  1. GITHUB_API_URL — explicit API endpoint (e.g. https://copilot-api.mycompany.ghe.com)
-//  2. GITHUB_SERVER_URL — auto-derive API endpoint from server URL:
-//     - https://mycompany.ghe.com  → https://copilot-api.mycompany.ghe.com
-//     - https://github.mycompany.com → https://github.mycompany.com/api/v3
-//     - https://github.com → https://api.github.com
-//  3. Returns empty string if no env vars are set (caller uses DefaultGitHubAPIBase)
-func DeriveGitHubAPIURL() string {
-	if apiURL := envutil.LookupGitHubAPIURL(""); apiURL != "" {
-		logProxy.Printf("GitHub API URL from GITHUB_API_URL: %s", apiURL)
-		return apiURL
-	}
-	if serverURL := os.Getenv("GITHUB_SERVER_URL"); serverURL != "" {
-		derived := deriveAPIFromServerURL(serverURL)
-		if derived != "" {
-			logProxy.Printf("GitHub API URL derived from GITHUB_SERVER_URL=%s: %s", serverURL, derived)
-			return derived
-		}
-	}
-	return ""
-}
-
-// deriveAPIFromServerURL converts a GITHUB_SERVER_URL to the corresponding API endpoint.
-// GHEC tenants (*.ghe.com): https://tenant.ghe.com → https://copilot-api.tenant.ghe.com
-// GitHub.com: https://github.com → https://api.github.com
-// GHES (all others): https://github.example.com → https://github.example.com/api/v3
-func deriveAPIFromServerURL(serverURL string) string {
-	parsed, err := url.Parse(strings.TrimRight(serverURL, "/"))
-	if err != nil || parsed.Host == "" {
-		return ""
-	}
-
-	// Use Hostname() (not Host) so that an optional port does not interfere
-	// with the suffix / equality checks below.
-	hostname := strings.ToLower(parsed.Hostname())
-
-	switch {
-	case hostname == "github.com" || hostname == "www.github.com":
-		return DefaultGitHubAPIBase
-	case strings.HasSuffix(hostname, ".ghe.com"):
-		// GHEC tenant: copilot-api.<subdomain>.ghe.com (re-add port when present)
-		if port := parsed.Port(); port != "" {
-			return fmt.Sprintf("%s://copilot-api.%s:%s", parsed.Scheme, hostname, port)
-		}
-		return fmt.Sprintf("%s://copilot-api.%s", parsed.Scheme, hostname)
-	default:
-		// GHES: <host>/api/v3 (parsed.Host retains the port, if any)
-		return fmt.Sprintf("%s://%s/api/v3", parsed.Scheme, parsed.Host)
-	}
-}
-
 // Server is a filtering HTTP forward proxy for the GitHub REST/GraphQL API.
 // It loads the same WASM guard used by the MCP gateway and runs the 6-phase
 // DIFC pipeline on every proxied response.
diff --git a/internal/proxy/proxy_test.go b/internal/proxy/proxy_test.go
index 56d2b8a38..1c69dc0cd 100644
--- a/internal/proxy/proxy_test.go
+++ b/internal/proxy/proxy_test.go
@@ -4,7 +4,6 @@ import (
 	"io"
 	"net/http"
 	"net/http/httptest"
-	"os"
 	"testing"
 
 	"github.com/github/gh-aw-mcpg/internal/difc"
@@ -845,127 +844,3 @@ func TestUnwrapSingleObject(t *testing.T) {
 		})
 	}
 }
-
-func TestDeriveAPIFromServerURL(t *testing.T) {
-	tests := []struct {
-		name      string
-		serverURL string
-		expected  string
-	}{
-		{
-			name:      "github.com returns default",
-			serverURL: "https://github.com",
-			expected:  DefaultGitHubAPIBase,
-		},
-		{
-			name:      "github.com with trailing slash",
-			serverURL: "https://github.com/",
-			expected:  DefaultGitHubAPIBase,
-		},
-		{
-			name:      "GHEC tenant derives copilot-api subdomain",
-			serverURL: "https://mycompany.ghe.com",
-			expected:  "https://copilot-api.mycompany.ghe.com",
-		},
-		{
-			name:      "GHEC tenant with trailing slash",
-			serverURL: "https://mycompany.ghe.com/",
-			expected:  "https://copilot-api.mycompany.ghe.com",
-		},
-		{
-			name:      "GHES uses /api/v3 path",
-			serverURL: "https://github.mycompany.com",
-			expected:  "https://github.mycompany.com/api/v3",
-		},
-		{
-			name:      "GHEC tenant with port",
-			serverURL: "https://mycompany.ghe.com:8443",
-			expected:  "https://copilot-api.mycompany.ghe.com:8443",
-		},
-		{
-			name:      "GHES with port",
-			serverURL: "https://github.example.com:8443",
-			expected:  "https://github.example.com:8443/api/v3",
-		},
-		{
-			name:      "empty string",
-			serverURL: "",
-			expected:  "",
-		},
-		{
-			name:      "invalid URL",
-			serverURL: "not-a-url",
-			expected:  "",
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			result := deriveAPIFromServerURL(tt.serverURL)
-			assert.Equal(t, tt.expected, result)
-		})
-	}
-}
-
-func TestDeriveGitHubAPIURL(t *testing.T) {
-	tests := []struct {
-		name     string
-		envVars  map[string]string
-		expected string
-	}{
-		{
-			name:     "no env vars returns empty",
-			envVars:  map[string]string{},
-			expected: "",
-		},
-		{
-			name:     "GITHUB_API_URL takes priority",
-			envVars:  map[string]string{"GITHUB_API_URL": "https://api.custom.ghe.com", "GITHUB_SERVER_URL": "https://other.ghe.com"},
-			expected: "https://api.custom.ghe.com",
-		},
-		{
-			name:     "GITHUB_SERVER_URL auto-derives GHEC",
-			envVars:  map[string]string{"GITHUB_SERVER_URL": "https://mycompany.ghe.com"},
-			expected: "https://copilot-api.mycompany.ghe.com",
-		},
-		{
-			name:     "GITHUB_SERVER_URL auto-derives GHES",
-			envVars:  map[string]string{"GITHUB_SERVER_URL": "https://github.example.com"},
-			expected: "https://github.example.com/api/v3",
-		},
-		{
-			name:     "GITHUB_SERVER_URL=https://github.com falls back to api.github.com",
-			envVars:  map[string]string{"GITHUB_SERVER_URL": "https://github.com"},
-			expected: DefaultGitHubAPIBase,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			// Save and clear relevant env vars
-			savedAPI, hadAPI := os.LookupEnv("GITHUB_API_URL")
-			savedServer, hadServer := os.LookupEnv("GITHUB_SERVER_URL")
-			os.Unsetenv("GITHUB_API_URL")
-			os.Unsetenv("GITHUB_SERVER_URL")
-			defer func() {
-				if hadAPI {
-					_ = os.Setenv("GITHUB_API_URL", savedAPI)
-				} else {
-					_ = os.Unsetenv("GITHUB_API_URL")
-				}
-				if hadServer {
-					_ = os.Setenv("GITHUB_SERVER_URL", savedServer)
-				} else {
-					_ = os.Unsetenv("GITHUB_SERVER_URL")
-				}
-			}()
-
-			for k, v := range tt.envVars {
-				os.Setenv(k, v)
-			}
-
-			result := DeriveGitHubAPIURL()
-			assert.Equal(t, tt.expected, result)
-		})
-	}
-}
diff --git a/internal/server/call_tool_result_test.go b/internal/server/call_tool_result_test.go
index 15f5cdd9c..edbef6965 100644
--- a/internal/server/call_tool_result_test.go
+++ b/internal/server/call_tool_result_test.go
@@ -171,14 +171,23 @@ func TestNewErrorCallToolResult(t *testing.T) {
 			err:         fmt.Errorf("formatted error: %s", "test"),
 			expectError: true,
 		},
+		{
+			name:        "nil error fallback",
+			err:         nil,
+			expectError: true,
+		},
 	}
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			result, data, err := newErrorCallToolResult(tt.err)
+			result, data, err := mcp.NewErrorCallToolResult(tt.err)
 
 			// Verify the error is returned
-			assert.Equal(t, tt.err, err, "Error should be returned as-is")
+			if tt.err == nil {
+				assert.Error(t, err, "nil error input should return a fallback error")
+			} else {
+				assert.Equal(t, tt.err, err, "Error should be returned as-is")
+			}
 
 			// Verify data is nil
 			assert.Nil(t, data, "Data should be nil for error results")
@@ -191,7 +200,7 @@ func TestNewErrorCallToolResult(t *testing.T) {
 			require.Len(t, result.Content, 1, "Should have one content item with error message")
 			textContent, ok := result.Content[0].(*sdk.TextContent)
 			require.True(t, ok, "Content should be TextContent")
-			assert.Equal(t, tt.err.Error(), textContent.Text, "Error message should be in content")
+			assert.Equal(t, err.Error(), textContent.Text, "Error message should be in content")
 
 			t.Logf("✓ Error CallToolResult properly created with IsError=%v, message=%q", result.IsError, textContent.Text)
 		})
diff --git a/internal/server/collaborator_permission_test.go b/internal/server/collaborator_permission_test.go
index cb1b8210f..2c8b364ab 100644
--- a/internal/server/collaborator_permission_test.go
+++ b/internal/server/collaborator_permission_test.go
@@ -294,20 +294,29 @@ func TestLookupGitHubToken(t *testing.T) {
 	}
 }
 
-func TestLookupGitHubAPIBaseURL(t *testing.T) {
+func TestDeriveGitHubAPIBaseURL(t *testing.T) {
 	t.Run("default", func(t *testing.T) {
 		t.Setenv("GITHUB_API_URL", "")
-		assert.Equal(t, "https://api.github.com", lookupGitHubAPIBaseURL())
+		t.Setenv("GITHUB_SERVER_URL", "")
+		assert.Equal(t, "https://api.github.com", envutil.DeriveGitHubAPIURL("https://api.github.com"))
 	})
 
 	t.Run("custom URL", func(t *testing.T) {
 		t.Setenv("GITHUB_API_URL", "https://github.example.com/api/v3")
-		assert.Equal(t, "https://github.example.com/api/v3", lookupGitHubAPIBaseURL())
+		t.Setenv("GITHUB_SERVER_URL", "")
+		assert.Equal(t, "https://github.example.com/api/v3", envutil.DeriveGitHubAPIURL("https://api.github.com"))
 	})
 
 	t.Run("strips trailing slash", func(t *testing.T) {
 		t.Setenv("GITHUB_API_URL", "https://github.example.com/api/v3/")
-		assert.Equal(t, "https://github.example.com/api/v3", lookupGitHubAPIBaseURL())
+		t.Setenv("GITHUB_SERVER_URL", "")
+		assert.Equal(t, "https://github.example.com/api/v3", envutil.DeriveGitHubAPIURL("https://api.github.com"))
+	})
+
+	t.Run("derives from GITHUB_SERVER_URL", func(t *testing.T) {
+		t.Setenv("GITHUB_API_URL", "")
+		t.Setenv("GITHUB_SERVER_URL", "https://github.example.com")
+		assert.Equal(t, "https://github.example.com/api/v3", envutil.DeriveGitHubAPIURL("https://api.github.com"))
 	})
 }
 
diff --git a/internal/server/tool_registry.go b/internal/server/tool_registry.go
index 471293143..5b9acef33 100644
--- a/internal/server/tool_registry.go
+++ b/internal/server/tool_registry.go
@@ -264,7 +264,7 @@ func (us *UnifiedServer) registerToolsFromBackend(serverID string) error {
 			toolArgs, err := mcp.ParseToolArguments(req)
 			if err != nil {
 				logger.LogError("client", "Failed to unmarshal tool arguments, tool=%s, error=%v", toolNameCopy, err)
-				return newErrorCallToolResult(err)
+				return mcp.NewErrorCallToolResult(err)
 			}
 
 			// Log the MCP tool call request
@@ -276,7 +276,7 @@ func (us *UnifiedServer) registerToolsFromBackend(serverID string) error {
 			// Check session is initialized
 			if err := us.requireSession(ctx); err != nil {
 				logger.LogError("client", "MCP tool call failed: session not initialized, session=%s, tool=%s", sessionID, toolNameCopy)
-				return newErrorCallToolResult(err)
+				return mcp.NewErrorCallToolResult(err)
 			}
 
 			result, data, err := us.callBackendTool(ctx, serverIDCopy, toolNameCopy, toolArgs)
@@ -359,7 +359,7 @@ func (us *UnifiedServer) registerSysTools() error {
 		toolArgs, err := mcp.ParseToolArguments(req)
 		if err != nil {
 			logger.LogError("client", "Failed to unmarshal sys_init arguments, error=%v", err)
-			return newErrorCallToolResult(err)
+			return mcp.NewErrorCallToolResult(err)
 		}
 
 		// Extract token from args
@@ -372,7 +372,7 @@ func (us *UnifiedServer) registerSysTools() error {
 		sessionID := us.getSessionID(ctx)
 		if sessionID == "" {
 			logger.LogError("client", "MCP session initialization failed: no session ID provided")
-			return newErrorCallToolResult(fmt.Errorf("no session ID provided"))
+			return mcp.NewErrorCallToolResult(fmt.Errorf("no session ID provided"))
 		}
 
 		logger.LogInfo("client", "MCP session initialization started, session=%s, has_token=%v", sessionID, token != "")
@@ -395,7 +395,7 @@ func (us *UnifiedServer) registerSysTools() error {
 		result, err := us.callSysServer("sys_init")
 		if err != nil {
 			logger.LogError("client", "MCP session initialization: sys_init call failed, session=%s, error=%v", sessionID, err)
-			return newErrorCallToolResult(err)
+			return mcp.NewErrorCallToolResult(err)
 		}
 
 		resultJSON, _ := json.Marshal(result)
@@ -428,13 +428,13 @@ func (us *UnifiedServer) registerSysTools() error {
 		// Check session is initialized
 		if err := us.requireSession(ctx); err != nil {
 			logger.LogError("client", "MCP sys_list_servers failed: session not initialized, session=%s", sessionID)
-			return newErrorCallToolResult(err)
+			return mcp.NewErrorCallToolResult(err)
 		}
 
 		result, err := us.callSysServer("sys_list_servers")
 		if err != nil {
 			logger.LogError("client", "MCP sys_list_servers error, session=%s, error=%v", sessionID, err)
-			return newErrorCallToolResult(err)
+			return mcp.NewErrorCallToolResult(err)
 		}
 
 		resultJSON, _ := json.Marshal(result)
diff --git a/internal/server/unified.go b/internal/server/unified.go
index 8687793bf..401f66101 100644
--- a/internal/server/unified.go
+++ b/internal/server/unified.go
@@ -6,8 +6,6 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"os"
-	"strings"
 	"sync"
 	"time"
 
@@ -306,7 +304,7 @@ func (g *guardBackendCaller) callCollaboratorPermission(ctx context.Context, arg
 		return nil, fmt.Errorf("get_collaborator_permission: no GitHub token available")
 	}
 
-	apiURL := lookupGitHubAPIBaseURL()
+	apiURL := envutil.DeriveGitHubAPIURL(envutil.DefaultGitHubAPIBaseURL)
 	path := fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", owner, repo, username)
 	url := apiURL + path
 
@@ -359,29 +357,6 @@ func (g *guardBackendCaller) callCollaboratorPermission(ctx context.Context, arg
 	return mcpResp, nil
 }
 
-// lookupGitHubAPIBaseURL returns the GitHub API base URL from environment
-// or defaults to https://api.github.com.
-func lookupGitHubAPIBaseURL() string {
-	url := envutil.LookupGitHubAPIURL("https://api.github.com")
-	if v := strings.TrimSpace(os.Getenv("GITHUB_API_URL")); v != "" {
-		logUnified.Printf("Using GITHUB_API_URL: %s", url)
-	} else {
-		logUnified.Printf("Using default GitHub API URL: %s", url)
-	}
-	return url
-}
-
-// newErrorCallToolResult creates a standard error CallToolResult with the error message
-// included as text content, so MCP clients can understand what went wrong.
-func newErrorCallToolResult(err error) (*sdk.CallToolResult, interface{}, error) {
-	return &sdk.CallToolResult{
-		IsError: true,
-		Content: []sdk.Content{
-			&sdk.TextContent{Text: err.Error()},
-		},
-	}, nil, err
-}
-
 // buildCircuitBreakers creates per-backend circuit breakers from the configuration.
 func buildCircuitBreakers(cfg *config.Config) map[string]*circuitBreaker {
 	cbs := make(map[string]*circuitBreaker)
@@ -508,7 +483,7 @@ func (us *UnifiedServer) callBackendTool(ctx context.Context, serverID, toolName
 		deniedErr := fmt.Errorf("tool %q is not in the allowed-tools list for this server", toolName)
 		toolSpan.RecordError(deniedErr)
 		toolSpan.SetStatus(codes.Error, "tool not allowed")
-		return newErrorCallToolResult(deniedErr)
+		return mcp.NewErrorCallToolResult(deniedErr)
 	}
 
 	// Create backend caller for the guard
@@ -522,7 +497,7 @@ func (us *UnifiedServer) callBackendTool(ctx context.Context, serverID, toolName
 	enforcementMode, err := us.ensureGuardInitialized(ctx, sessionID, serverID, g, backendCaller)
 	if err != nil {
 		httpStatusCode = 500
-		return newErrorCallToolResult(fmt.Errorf("guard session initialization failed: %w", err))
+		return mcp.NewErrorCallToolResult(fmt.Errorf("guard session initialization failed: %w", err))
 	}
 
 	requestEvaluator := difc.NewEvaluatorWithMode(enforcementMode)
@@ -549,7 +524,7 @@ func (us *UnifiedServer) callBackendTool(ctx context.Context, serverID, toolName
 	if err != nil {
 		logger.LogWarn("difc", "Guard labeling failed: %v", err)
 		httpStatusCode = 500
-		return newErrorCallToolResult(fmt.Errorf("guard labeling failed: %w", err))
+		return mcp.NewErrorCallToolResult(fmt.Errorf("guard labeling failed: %w", err))
 	}
 
 	logUnified.Printf("[DIFC] Resource: %s | Operation: %s | Secrecy: %v | Integrity: %v",
@@ -575,7 +550,7 @@ func (us *UnifiedServer) callBackendTool(ctx context.Context, serverID, toolName
 			toolSpan.RecordError(detailedErr)
 			toolSpan.SetStatus(codes.Error, "access denied: "+result.Reason)
 			httpStatusCode = 403
-			return newErrorCallToolResult(detailedErr)
+			return mcp.NewErrorCallToolResult(detailedErr)
 		}
 	} else {
 		logUnified.Printf("[DIFC] Access ALLOWED for agent %s to %s", agentID, resource.Description)
@@ -597,7 +572,7 @@ func (us *UnifiedServer) callBackendTool(ctx context.Context, serverID, toolName
 		execSpan.RecordError(err)
 		execSpan.SetStatus(codes.Error, "circuit breaker open")
 		httpStatusCode = 429
-		return newErrorCallToolResult(err)
+		return mcp.NewErrorCallToolResult(err)
 	}
 
 	backendResult, err := executeBackendToolCall(execCtx, us.launcher, serverID, sessionID, toolName, args)
@@ -608,7 +583,7 @@ func (us *UnifiedServer) callBackendTool(ctx context.Context, serverID, toolName
 		execSpan.RecordError(err)
 		execSpan.SetStatus(codes.Error, err.Error())
 		httpStatusCode = 500
-		return newErrorCallToolResult(err)
+		return mcp.NewErrorCallToolResult(err)
 	}
 
 	// Inspect the tool result for rate-limit indicators from the GitHub MCP server.
@@ -640,7 +615,7 @@ func (us *UnifiedServer) callBackendTool(ctx context.Context, serverID, toolName
 		if err != nil {
 			logger.LogWarn("difc", "Response labeling failed: %v", err)
 			httpStatusCode = 500
-			return newErrorCallToolResult(fmt.Errorf("response labeling failed: %w", err))
+			return mcp.NewErrorCallToolResult(fmt.Errorf("response labeling failed: %w", err))
 		}
 	} else {
 		logUnified.Printf("[DIFC] Skipping LabelResponse() for %s operation in %s mode", operation, enforcementMode)
@@ -665,7 +640,7 @@ func (us *UnifiedServer) callBackendTool(ctx context.Context, serverID, toolName
 				blockErr := fmt.Errorf("DIFC policy violation: %d of %d items in response are not accessible to agent %s",
 					filtered.GetFilteredCount(), filtered.TotalCount, agentID)
 				httpStatusCode = 403
-				return newErrorCallToolResult(blockErr)
+				return mcp.NewErrorCallToolResult(blockErr)
 			}
 
 			if filtered.GetFilteredCount() > 0 {
@@ -678,14 +653,14 @@ func (us *UnifiedServer) callBackendTool(ctx context.Context, serverID, toolName
 			finalResult, err = filtered.ToResult()
 			if err != nil {
 				httpStatusCode = 500
-				return newErrorCallToolResult(fmt.Errorf("failed to convert filtered data: %w", err))
+				return mcp.NewErrorCallToolResult(fmt.Errorf("failed to convert filtered data: %w", err))
 			}
 		} else {
 			// Simple labeled data - already passed coarse-grained check
 			finalResult, err = labeledData.ToResult()
 			if err != nil {
 				httpStatusCode = 500
-				return newErrorCallToolResult(fmt.Errorf("failed to convert labeled data: %w", err))
+				return mcp.NewErrorCallToolResult(fmt.Errorf("failed to convert labeled data: %w", err))
 			}
 		}
 
@@ -714,7 +689,7 @@ func (us *UnifiedServer) callBackendTool(ctx context.Context, serverID, toolName
 	callResult, err := mcp.ConvertToCallToolResult(finalResult)
 	if err != nil {
 		httpStatusCode = 500
-		return newErrorCallToolResult(fmt.Errorf("failed to convert result: %w", err))
+		return mcp.NewErrorCallToolResult(fmt.Errorf("failed to convert result: %w", err))
 	}
 
 	// If items were filtered by DIFC policy in filter/propagate mode, append a notice so
