Skip to content

Conversation

@SamMorrowDrums
Copy link
Collaborator

Summary

This refactor changes how dependencies are passed to tool handlers from a closure-based pattern to a context-based pattern. This eliminates the need to create new closures for every tool on each request, which is critical for the remote server where one server instance is created per request.

Problem

In the closure-based pattern, NewTool stored a function that took deps and returned a handler:

// Old pattern - creates closure on every call to Handler(deps)
func NewTool(..., handler func(deps) handlerFn) {
    return ServerTool{
        Handler: func(deps any) handlerFn {
            return handler(deps.(ToolDependencies))  // Creates new closure each time
        }
    }
}

For the remote server with ~89 tools, this meant 178 allocations per request just for tool handler setup.

Solution

The new pattern injects dependencies into the context once, and handlers extract them:

// New pattern - no closure creation at request time
func NewTool(..., handler func(ctx, deps, req, args) result) {
    return ServerTool{
        Handler: func(ctx any) handlerFn {
            return func(ctx, req) result {
                deps := MustDepsFromContext(ctx)  // Extract from context
                return handler(ctx, deps, req, args)
            }
        }
    }
}

Benchmark Results

Throughput (89 tools, 5s benchmark, 3 runs)

Benchmark main (old) new Speedup
Sequential 9,345 ns/op 4,250 ns/op 2.2x faster
Parallel 2,751 ns/op 779 ns/op 3.5x faster

Memory

Metric main new Reduction
Bytes/op 7,832 B 2,136 B 73% less
Allocs/op 178 89 50% fewer

Scaling (tools from 13 to 1,300)

Tools main new Speedup Memory Saved
13 1,261 ns 811 ns 1.6x 54%
65 6,420 ns 3,771 ns 1.7x 54%
130 14,653 ns 7,797 ns 1.9x 54%
650 68,722 ns 38,872 ns 1.8x 54%
1,300 145,135 ns 67,292 ns 2.2x 54%

Changes

New APIs in pkg/github/dependencies.go

  • ContextWithDeps(ctx, deps) - inject dependencies into context
  • DepsFromContext(ctx) - extract dependencies (returns nil if not present)
  • MustDepsFromContext(ctx) - extract dependencies (panics if not present)

Updated in pkg/inventory/server_tool.go

  • NewServerToolWithContextHandler - new constructor for context-based handlers
  • NewServerToolWithRawContextHandler - new constructor for raw handlers
  • Old constructors marked as deprecated

Converted (89 tool handlers across 15 files)

All tool handlers converted from closure pattern to direct context-based pattern.

Test updates (17 test files)

All tests updated to use ContextWithDeps(context.Background(), deps).

Backwards Compatibility

  • Old NewServerTool and NewServerToolFromHandler are deprecated but still work
  • dynamic_tools.go intentionally uses the old pattern (with nolint comment) since it wraps user-provided handlers

Testing

  • All existing tests pass
  • script/lint passes
  • script/test passes

This refactor addresses performance issues in per-request server scenarios
where creating ~90 handler closures per request was causing latency.

Changes:
- Add ContextWithDeps, DepsFromContext, MustDepsFromContext to dependencies.go
- Add NewServerToolWithContextHandler, NewServerToolWithRawContextHandler to inventory
- Convert all 89 tool handlers from closure pattern to direct context-based deps
- Update all tests to inject deps into context before calling handlers
- Mark old NewServerTool and NewServerToolFromHandler as deprecated

The new pattern:
- Before: func(deps) handler { return func(ctx, req, args) { use deps } }
- After: func(ctx, deps, req, args) { use deps }

Dependencies are now injected into context once (via ContextWithDeps) and
extracted by NewTool internally before passing to handlers. This eliminates
closure creation on the hot path for remote servers.
Copilot AI review requested due to automatic review settings December 18, 2025 10:50
@SamMorrowDrums SamMorrowDrums requested a review from a team as a code owner December 18, 2025 10:50
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors dependency injection from a closure-based pattern to a context-based pattern, improving performance for the remote server by eliminating the need to create closures for each tool on every request. The change reduces allocations from 178 to 89 per request and achieves 2-3.5x throughput improvements.

Key changes:

  • Introduces new context-based constructors (NewServerToolWithContextHandler, NewServerToolWithRawContextHandler) that avoid closure creation at registration time
  • Changes handler signature from func(deps) handler to func(ctx, deps, req, args) result
  • Adds context helper functions ContextWithDeps, DepsFromContext, and MustDepsFromContext in pkg/github/dependencies.go
  • Converts all 89 tool handlers across 15 files to use the new pattern
  • Updates all test files to inject dependencies via context

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated no comments.

Show a summary per file
File Description
pkg/inventory/server_tool.go Adds new context-based constructors; deprecates old closure-based constructors
pkg/github/security_advisories.go Converts 4 tool handlers from closure to context pattern
pkg/github/security_advisories_test.go Updates tests to use ContextWithDeps
pkg/github/secret_scanning.go Converts 2 tool handlers to context pattern
pkg/github/secret_scanning_test.go Updates tests to use ContextWithDeps
pkg/github/search.go Converts 4 search tool handlers to context pattern, including helper function refactoring
pkg/github/search_test.go Updates 5 test cases to use ContextWithDeps
pkg/github/repositories.go Converts 18 repository tool handlers to context pattern
pkg/github/repositories_test.go Updates 17 test cases to use ContextWithDeps
pkg/github/pullrequests.go Converts 10 PR tool handlers to context pattern
pkg/github/pullrequests_test.go Updates 18 test cases to use ContextWithDeps
pkg/github/projects.go Converts 8 project tool handlers to context pattern
pkg/github/projects_test.go Updates 8 test cases to use ContextWithDeps
pkg/github/notifications_test.go Updates 6 test cases to use ContextWithDeps
pkg/github/labels.go Converts 3 label tool handlers to context pattern
pkg/github/labels_test.go Updates 3 test cases to use ContextWithDeps

The refactoring is comprehensive, consistent, and well-executed. All handler conversions follow the same pattern, tests are properly updated, and the deprecated constructors remain for backward compatibility. No issues were identified during the review.

@SamMorrowDrums SamMorrowDrums merged commit 3c453dd into main Dec 18, 2025
23 checks passed
@SamMorrowDrums SamMorrowDrums deleted the context-deps-injection branch December 18, 2025 10:57
Copilot AI added a commit that referenced this pull request Dec 18, 2025
The issue was that after PR #1640 switched from closure-based deps to context-based deps,
the stdio server was missing middleware to inject ToolDependencies into the request context.
This caused tools to panic with "ToolDependencies not found in context" when called.

Added middleware in NewMCPServer() that wraps all requests with github.ContextWithDeps(),
ensuring deps are available to tool handlers via MustDepsFromContext().

Also added tests to verify server creation and toolset resolution logic.

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
SamMorrowDrums added a commit that referenced this pull request Dec 18, 2025
The issue was that after PR #1640 switched from closure-based deps to context-based deps,
the stdio server was missing middleware to inject ToolDependencies into the request context.
This caused tools to panic with "ToolDependencies not found in context" when called.

Added middleware in NewMCPServer() that wraps all requests with github.ContextWithDeps(),
ensuring deps are available to tool handlers via MustDepsFromContext().

Also added tests to verify server creation and toolset resolution logic.

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants