[object Object]

← back to Cli Printing Press

fix(cli): scorer respects runtime MCP surface selection (refs #516 WU-A4) (#519)

358e46a67a8e961d6096f0290d32429fd6cd2960 · 2026-05-02 19:14:46 -0700 · Trevin Chow

Pre-fix: estimateMCPTokens unconditionally read internal/mcp/tools.go.
For CLIs that opt into mcp.orchestration: code AND default
DUB_MCP_SURFACE (or equivalent) to "thin" in main.go, the agent loads
internal/mcp/code_orch.go's 2-tool surface — NOT tools.go's typed
endpoint mirror. The scorer was reporting mcp_token_efficiency 4/10
on Dub despite the runtime catalog being ~1K tokens (2 short tools)
instead of ~30K (53 verbose tools).

Adds canonicalMCPSurfacePath(dir) which:
  - returns code_orch.go when it exists AND main.go's surface-selection
    default is "thin"
  - falls back to tools.go in every other case (no regression for CLIs
    that don't opt into orchestration)

Stub detection extended to accept both RegisterTools and
RegisterCodeOrchestrationTools.

Real-world impact (verified):
  - dub:        91→92  (mcp_token_efficiency 4/10 → 7/10)
  - open-meteo: 82→82  (unchanged — no code_orch.go)
  - craigslist: 85→85  (unchanged — no code_orch.go)

Refs #516 (umbrella). The other 3 sub-WUs in #516 do NOT match the
umbrella's framing and are addressed separately on the issue.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 358e46a67a8e961d6096f0290d32429fd6cd2960
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Sat May 2 19:14:46 2026 -0700

    fix(cli): scorer respects runtime MCP surface selection (refs #516 WU-A4) (#519)
    
    Pre-fix: estimateMCPTokens unconditionally read internal/mcp/tools.go.
    For CLIs that opt into mcp.orchestration: code AND default
    DUB_MCP_SURFACE (or equivalent) to "thin" in main.go, the agent loads
    internal/mcp/code_orch.go's 2-tool surface — NOT tools.go's typed
    endpoint mirror. The scorer was reporting mcp_token_efficiency 4/10
    on Dub despite the runtime catalog being ~1K tokens (2 short tools)
    instead of ~30K (53 verbose tools).
    
    Adds canonicalMCPSurfacePath(dir) which:
      - returns code_orch.go when it exists AND main.go's surface-selection
        default is "thin"
      - falls back to tools.go in every other case (no regression for CLIs
        that don't opt into orchestration)
    
    Stub detection extended to accept both RegisterTools and
    RegisterCodeOrchestrationTools.
    
    Real-world impact (verified):
      - dub:        91→92  (mcp_token_efficiency 4/10 → 7/10)
      - open-meteo: 82→82  (unchanged — no code_orch.go)
      - craigslist: 85→85  (unchanged — no code_orch.go)
    
    Refs #516 (umbrella). The other 3 sub-WUs in #516 do NOT match the
    umbrella's framing and are addressed separately on the issue.
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 internal/pipeline/mcp_size.go      |  67 +++++++++++++++++-
 internal/pipeline/mcp_size_test.go | 135 +++++++++++++++++++++++++++++++++++++
 2 files changed, 200 insertions(+), 2 deletions(-)

diff --git a/internal/pipeline/mcp_size.go b/internal/pipeline/mcp_size.go
index 83f2ed15..14db3806 100644
--- a/internal/pipeline/mcp_size.go
+++ b/internal/pipeline/mcp_size.go
@@ -35,17 +35,80 @@ func mcpToolsPath(dir string) string {
 	return filepath.Join(dir, "internal", "mcp", "tools.go")
 }
 
+// mcpCodeOrchPath is the conventional location of the code-orchestration
+// thin-surface tool registrations (search + execute pair) in a printed CLI
+// that opted into mcp.orchestration: code.
+func mcpCodeOrchPath(dir string) string {
+	return filepath.Join(dir, "internal", "mcp", "code_orch.go")
+}
+
+// runtimeSurfaceDefault matches the default branch of `<API>_MCP_SURFACE`
+// selection logic in a printed CLI's MCP main.go. The API prefix varies
+// per CLI (DUB_, NOTION_, etc.) but the env-var suffix is stable. Captures
+// the literal default value (typically "thin", "full", or "both").
+var runtimeSurfaceDefault = regexp.MustCompile(`os\.Getenv\("[A-Z0-9_]+_MCP_SURFACE"\)[\s\S]{0,200}?surface\s*=\s*"([^"]+)"`)
+
+// canonicalMCPSurfacePath returns the file the scorer should read for token
+// efficiency: the file containing the tool registrations the agent actually
+// loads under the runtime default surface.
+//
+// When the printed CLI opts into mcp.orchestration: code AND its main.go
+// defaults DUB_MCP_SURFACE (or equivalent) to "thin", the agent loads
+// internal/mcp/code_orch.go (the search+execute pair), NOT
+// internal/mcp/tools.go (the typed endpoint mirrors). The scorer should
+// reflect that.
+//
+// Falls back to internal/mcp/tools.go when:
+//   - code_orch.go does not exist (CLI didn't opt into orchestration),
+//   - main.go has no surface-selection logic (older CLIs with no env var),
+//   - default surface is "full" or "both" (tools.go IS the agent surface).
+//
+// This is the architectural fix for retro umbrella issue #516 WU-A4: the
+// scorer must evaluate what the agent sees, not what the static templates
+// emit. Two surfaces co-exist in code_orch CLIs; the runtime default
+// dictates which one drives the catalog tax.
+func canonicalMCPSurfacePath(dir string) string {
+	toolsPath := mcpToolsPath(dir)
+	codeOrchPath := mcpCodeOrchPath(dir)
+
+	if _, err := os.Stat(codeOrchPath); err != nil {
+		return toolsPath
+	}
+	mainPath := mcpMainPath(dir)
+	if mainPath == "" {
+		return toolsPath
+	}
+	mainSrc, err := os.ReadFile(mainPath)
+	if err != nil {
+		return toolsPath
+	}
+	match := runtimeSurfaceDefault.FindStringSubmatch(string(mainSrc))
+	if len(match) < 2 {
+		return toolsPath
+	}
+	if match[1] == "thin" {
+		return codeOrchPath
+	}
+	return toolsPath
+}
+
 // estimateMCPTokens reads the generated MCP tool surface and returns an
 // estimate of its total token weight, plus per-tool breakdown when
 // individual tool definitions can be isolated. Returns a zero-valued
 // estimate (TotalTokens == 0) if no MCP surface exists.
+//
+// Reads canonicalMCPSurfacePath, which respects runtime surface selection
+// in main.go (e.g., DUB_MCP_SURFACE defaulting to "thin" loads
+// code_orch.go's 2-tool surface, not tools.go's 53-tool endpoint mirror).
+// Pre-#516 behavior was unconditional read of tools.go; that mis-scored
+// any CLI whose default surface was the thin pair.
 func estimateMCPTokens(dir string) MCPTokenEstimate {
-	content, err := os.ReadFile(mcpToolsPath(dir))
+	content, err := os.ReadFile(canonicalMCPSurfacePath(dir))
 	if err != nil {
 		return MCPTokenEstimate{}
 	}
 	src := string(content)
-	if !strings.Contains(src, "RegisterTools") {
+	if !strings.Contains(src, "RegisterTools") && !strings.Contains(src, "RegisterCodeOrchestrationTools") {
 		// File exists but is a stub — treat as no MCP surface.
 		return MCPTokenEstimate{}
 	}
diff --git a/internal/pipeline/mcp_size_test.go b/internal/pipeline/mcp_size_test.go
index a62b2e8f..2a3cb72e 100644
--- a/internal/pipeline/mcp_size_test.go
+++ b/internal/pipeline/mcp_size_test.go
@@ -118,3 +118,138 @@ func TestScoreMCPTokenEfficiency_PartialCreditMedium(t *testing.T) {
 	assert.True(t, scored)
 	assert.Equal(t, 7, score)
 }
+
+// TestEstimateMCPTokens_RuntimeSurfaceSelection captures WU-A4 from
+// retro umbrella #516: when the MCP main.go has surface-selection logic
+// defaulting to a thin code-orchestration surface, the agent loads the
+// thin surface (e.g., dub_search + dub_execute), NOT the full
+// endpoint-mirror in tools.go. The token-efficiency score must reflect
+// the actual default surface, not the static tools.go.
+func TestEstimateMCPTokens_RuntimeSurfaceSelection(t *testing.T) {
+	t.Run("thin default selects code_orch.go for token counting", func(t *testing.T) {
+		dir := t.TempDir()
+		mcpDir := filepath.Join(dir, "internal", "mcp")
+		require.NoError(t, os.MkdirAll(mcpDir, 0o755))
+
+		// Write a "tools.go" with 3 verbose endpoint mirrors that would
+		// score badly on token efficiency if read directly.
+		toolsBody := `package mcp
+
+import mcplib "github.com/mark3labs/mcp-go/mcp"
+
+func RegisterTools(s *server.MCPServer) {
+	s.AddTool(mcplib.NewTool("verbose_endpoint_one", mcplib.WithDescription("` + strings.Repeat("x", 400) + `")), nil)
+	s.AddTool(mcplib.NewTool("verbose_endpoint_two", mcplib.WithDescription("` + strings.Repeat("y", 400) + `")), nil)
+	s.AddTool(mcplib.NewTool("verbose_endpoint_three", mcplib.WithDescription("` + strings.Repeat("z", 400) + `")), nil)
+}
+`
+		require.NoError(t, os.WriteFile(filepath.Join(mcpDir, "tools.go"), []byte(toolsBody), 0o644))
+
+		// Write a "code_orch.go" with the thin 2-tool surface — short descriptions.
+		codeOrchBody := `package mcp
+
+import mcplib "github.com/mark3labs/mcp-go/mcp"
+
+func RegisterCodeOrchestrationTools(s *server.MCPServer) {
+	s.AddTool(mcplib.NewTool("dub_search", mcplib.WithDescription("Search the API.")), nil)
+	s.AddTool(mcplib.NewTool("dub_execute", mcplib.WithDescription("Execute one endpoint.")), nil)
+}
+`
+		require.NoError(t, os.WriteFile(filepath.Join(mcpDir, "code_orch.go"), []byte(codeOrchBody), 0o644))
+
+		// Write a "main.go" that defaults DUB_MCP_SURFACE to "thin".
+		cmdDir := filepath.Join(dir, "cmd", "demo-pp-mcp")
+		require.NoError(t, os.MkdirAll(cmdDir, 0o755))
+		mainBody := `package main
+
+import (
+	"os"
+	mcptools "demo-pp-cli/internal/mcp"
+	"github.com/mark3labs/mcp-go/server"
+)
+
+func main() {
+	s := server.NewMCPServer("Demo", "1.0.0")
+	surface := os.Getenv("DUB_MCP_SURFACE")
+	if surface == "" {
+		surface = "thin"
+	}
+	switch surface {
+	case "thin":
+		mcptools.RegisterCodeOrchestrationTools(s)
+	case "full":
+		mcptools.RegisterTools(s)
+	}
+}
+`
+		require.NoError(t, os.WriteFile(filepath.Join(cmdDir, "main.go"), []byte(mainBody), 0o644))
+		require.NoError(t, os.WriteFile(filepath.Join(dir, ".printing-press.json"),
+			[]byte(`{"cli_name":"demo-pp-cli"}`), 0o644))
+
+		est := estimateMCPTokens(dir)
+		// EXPECT: scorer sees the thin surface (2 tools, short descriptions),
+		// NOT the full surface (3 tools with 400-char descriptions each).
+		require.Equal(t, 2, est.ToolCount, "should count code_orch.go's 2 tools, not tools.go's 3")
+		assert.Less(t, est.TotalTokens, 100, "thin surface tools have short descriptions; should be well under 100 total tokens")
+	})
+
+	t.Run("no surface selection falls through to tools.go (no regression)", func(t *testing.T) {
+		// Existing behavior preserved when there's no main.go surface
+		// selection: just read tools.go.
+		dir := writeMCPTools(t, `
+	s.AddTool(mcplib.NewTool("get_thing", mcplib.WithDescription("Retrieve a thing.")), nil)
+`)
+		est := estimateMCPTokens(dir)
+		require.Equal(t, 1, est.ToolCount)
+		assert.Equal(t, "get_thing", est.PerTool[0].Name)
+	})
+
+	t.Run("full default selects tools.go (DUB_MCP_SURFACE=full not thin)", func(t *testing.T) {
+		dir := t.TempDir()
+		mcpDir := filepath.Join(dir, "internal", "mcp")
+		require.NoError(t, os.MkdirAll(mcpDir, 0o755))
+
+		// tools.go has 1 tool (chosen by default since surface=full)
+		toolsBody := `package mcp
+
+import mcplib "github.com/mark3labs/mcp-go/mcp"
+
+func RegisterTools(s *server.MCPServer) {
+	s.AddTool(mcplib.NewTool("typed_tool", mcplib.WithDescription("Typed.")), nil)
+}
+`
+		require.NoError(t, os.WriteFile(filepath.Join(mcpDir, "tools.go"), []byte(toolsBody), 0o644))
+
+		// code_orch.go exists but main.go default is "full"
+		codeOrchBody := `package mcp
+
+func RegisterCodeOrchestrationTools(s *server.MCPServer) {
+	s.AddTool(mcplib.NewTool("thin_search", mcplib.WithDescription("S.")), nil)
+	s.AddTool(mcplib.NewTool("thin_execute", mcplib.WithDescription("E.")), nil)
+}
+`
+		require.NoError(t, os.WriteFile(filepath.Join(mcpDir, "code_orch.go"), []byte(codeOrchBody), 0o644))
+
+		cmdDir := filepath.Join(dir, "cmd", "demo-pp-mcp")
+		require.NoError(t, os.MkdirAll(cmdDir, 0o755))
+		mainBody := `package main
+
+import "os"
+
+func main() {
+	surface := os.Getenv("DUB_MCP_SURFACE")
+	if surface == "" {
+		surface = "full"
+	}
+	_ = surface
+}
+`
+		require.NoError(t, os.WriteFile(filepath.Join(cmdDir, "main.go"), []byte(mainBody), 0o644))
+		require.NoError(t, os.WriteFile(filepath.Join(dir, ".printing-press.json"),
+			[]byte(`{"cli_name":"demo-pp-cli"}`), 0o644))
+
+		est := estimateMCPTokens(dir)
+		require.Equal(t, 1, est.ToolCount, "full default → tools.go")
+		assert.Equal(t, "typed_tool", est.PerTool[0].Name)
+	})
+}

← 83df8ae7 chore(main): release 3.6.1 (#512)  ·  back to Cli Printing Press  ·  fix(cli): MCP template ports — NoCache=true + codeOrch stopw 734e00d1 →