How the MCP Server Inventory Builder Assembles and Registers Tools
The MCP server inventory builder constructs a filtered Inventory by processing toolsets, validating tool names, and applying feature flags during the Build phase, then registers available tools via RegisterAll with the MCP server.
The github/github-mcp-server repository implements a sophisticated inventory management system for its Model Context Protocol (MCP) implementation. Understanding how the MCP server inventory builder assembles and registers tools is essential for developers extending the server or customizing tool availability for specific deployment scenarios.
Three-Phase Assembly Process
The inventory construction follows a distinct three-phase pattern: configuration, build-time validation, and runtime registration.
Phase 1: Construction and Configuration
The process begins with NewBuilder in pkg/inventory/builder.go (lines 54-60), which initializes a Builder struct to store configuration state. During this phase, the builder accumulates settings through chainable methods that merely store data without performing validation:
SetTools,SetResources,SetPrompts– supply the raw server definitionsWithToolsets– specify which toolset groups to enableWithTools– force-enable specific tools outside their toolsetsWithReadOnly– filter out write-capable toolsWithFeatureChecker– inject a feature flag evaluatorWithFilter– apply custom runtime filtersWithInsidersMode– control visibility of insider-only features
Phase 2: Build and Validation
The Builder.Build() method (lines 71-79 in pkg/inventory/builder.go) performs all heavy lifting in a single pass, transforming configuration into an immutable Inventory. This phase executes several critical operations:
Insiders mode processing – stripInsidersFeatures (lines 58-66) removes tools marked with InsidersOnly: true and strips UI metadata when insiders mode is disabled.
Toolset expansion – processToolsets (lines 39-48) resolves special identifiers like "default" and "all", validates requested toolsets against available definitions, and records unrecognized IDs for error reporting.
Tool validation – The cleanTools function builds a canonical set of valid tool names, resolves deprecated aliases through the deprecatedAliases map, and validates force-enabled tools passed via WithTools. If unknown tool names are supplied, Build returns ErrUnknownTools (defined at lines 26-30).
Feature flag wiring – The builder stores the optional FeatureFlagChecker within the inventory for later evaluation during registration.
Instruction generation – Optionally generates human-readable instruction blocks describing available capabilities.
Phase 3: Registration with MCP Server
Once built, the Inventory exposes registration methods in pkg/inventory/registry.go. The primary entry point is RegisterAll (lines 109-115), which delegates to RegisterTools, RegisterResourceTemplates, and RegisterPrompts.
RegisterTools (lines 71-76) iterates over the filtered AvailableTools slice and invokes each tool's RegisterFunc on the MCP server instance. Similarly, resources and prompts register their respective handlers. When UI icons are missing from individual tool definitions, the registration process automatically applies icons from toolset metadata.
Key Implementation Details
Toolset Processing and Expansion
The processToolsets function handles the logic for toolset selection defined in pkg/inventory/builder.go (lines 99-108). When "default" is specified, the builder includes only toolsets marked as default. When "all" is specified, every available toolset is included. Unrecognized toolset IDs are collected and reported without halting the build process, allowing for graceful degradation.
Feature Flag and Insiders Mode Handling
Feature flag evaluation occurs at build time through WithFeatureChecker and stripInsidersFeatures (lines 58-66). Tools with InsidersOnly: true are completely removed from the inventory when insiders mode is disabled, ensuring that beta or internal features remain inaccessible to standard deployments.
Tool Validation and Alias Resolution
The validation logic in cleanTools (referenced at lines 114-122 for WithTools handling) maintains a deprecatedAliases map that translates legacy tool names to their current canonical equivalents. This ensures backward compatibility while enforcing strict validation—any tool name passed to WithTools that cannot be resolved to a known tool results in ErrUnknownTools.
Practical Code Examples
Example 1: Read-Only Repository Toolset
This example demonstrates creating a minimal inventory that exposes only the "repos" toolset in read-only mode:
import (
"context"
"github.com/github/github-mcp-server/pkg/inventory"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// Assume tools, resources, and prompts are generated elsewhere
func newReadOnlyInventory(
tools []inventory.ServerTool,
resources []inventory.ServerResourceTemplate,
prompts []inventory.ServerPrompt,
) (*inventory.Inventory, error) {
return inventory.NewBuilder().
SetTools(tools).
SetResources(resources).
SetPrompts(prompts).
WithToolsets([]string{"repos"}). // Only the "repos" toolset
WithReadOnly(true). // Drop write-capable tools
Build()
}
// In the request handler:
func handleMCP(w http.ResponseWriter, r *http.Request) {
inv, _ := newReadOnlyInventory(allTools, allResources, allPrompts)
srv := mcp.NewServer()
inv.RegisterAll(r.Context(), srv, nil) // nil = no extra deps
// Serve the MCP JSON payload using srv
}
Key implementation references: NewBuilder at [builder.go lines 54-60](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go#L54-L60); toolset filtering at [builder.go lines 99-108](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go#L99-L108); registration at [registry.go lines 109-115](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/registry.go#L109-L115).
Example 2: Dynamic Filtering and Force-Enabled Tools
This example shows how to force-enable specific tools outside their toolsets and apply custom runtime filters:
func dynamicFilter(ctx context.Context, t *inventory.ServerTool) (bool, error) {
// Exclude tools that the current user cannot access
// This example checks a custom context value
if ctx.Value("user") == "guest" && t.Tool.Write {
return false, nil
}
return true, nil
}
func buildDynamicInventory() (*inventory.Inventory, error) {
return inventory.NewBuilder().
SetTools(allTools).
SetResources(allResources).
SetPrompts(allPrompts).
// No toolset list → defaults are used
WithTools([]string{"search"}). // Force-enable "search" even if its toolset is disabled
WithFilter(dynamicFilter). // Apply runtime filter
Build()
}
Key implementation references: force-enabled tools at [builder.go lines 114-122](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go#L114-L122); custom filters at [builder.go lines 135-141](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go#L135-L141); validation errors at [builder.go lines 26-30](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go#L26-L30).
Summary
- The MCP server inventory builder follows a three-phase pattern: configuration storage, build-time validation and filtering, and runtime registration.
- Construction occurs through chainable methods like
WithToolsets,WithReadOnly, andWithFilterthat store state without immediate validation. - Build executes in
Builder.Build()atpkg/inventory/builder.go, performing insiders filtering, toolset expansion, alias resolution, and strict validation that returnsErrUnknownToolsfor unrecognized tool names. - Registration happens through
Inventory.RegisterAllatpkg/inventory/registry.go, which iterates over filteredAvailableToolsand invokes each tool'sRegisterFuncon the MCP server instance. - The system supports dynamic filtering, feature flag checking, and deprecated alias resolution to maintain backward compatibility while enforcing strict access controls.
Frequently Asked Questions
How does the inventory builder handle unknown tool names passed to WithTools?
The builder validates all tool names during the Build() phase using the cleanTools function. If a tool name passed to WithTools cannot be found in the canonical tool list or resolved through the deprecatedAliases map, Build() returns ErrUnknownTools immediately, preventing server startup with invalid configuration.
What is the difference between WithToolsets and WithTools in the builder configuration?
WithToolsets accepts toolset identifiers like "repos", "issues", "default", or "all" to enable entire groups of related tools, while WithTools accepts individual tool names to force-enable specific tools regardless of their toolset membership. This allows fine-grained control, such as enabling a single search tool while excluding its parent toolset.
How does the inventory builder filter tools in read-only mode?
When WithReadOnly(true) is configured, the builder marks the inventory for read-only operation. During registration in pkg/inventory/registry.go, the system filters out tools where Tool.Write is true, ensuring only read-capable tools are exposed to the MCP server. This filtering occurs after toolset selection but before the final AvailableTools list is generated.
Can custom filters access request context during tool registration?
Yes, filters passed to WithFilter receive the request context.Context and the *ServerTool pointer, allowing dynamic evaluation based on request-specific values such as authentication headers, user roles, or feature flags. The filter returns a boolean indicating whether the tool should be included, enabling runtime access control decisions during the registration phase.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →