# How the MCP Server Inventory Builder Assembles and Registers Tools

> Learn how the MCP server inventory builder constructs and registers tools by filtering toolsets, validating names, and applying feature flags during the Build phase.

- Repository: [GitHub/github-mcp-server](https://github.com/github/github-mcp-server)
- Tags: internals
- Published: 2026-02-16

---

**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`](https://github.com/github/github-mcp-server/blob/main/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 definitions
- `WithToolsets` – specify which toolset groups to enable
- `WithTools` – force-enable specific tools outside their toolsets
- `WithReadOnly` – filter out write-capable tools
- `WithFeatureChecker` – inject a feature flag evaluator
- `WithFilter` – apply custom runtime filters
- `WithInsidersMode` – control visibility of insider-only features

### Phase 2: Build and Validation

The `Builder.Build()` method (lines 71-79 in [`pkg/inventory/builder.go`](https://github.com/github/github-mcp-server/blob/main/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`](https://github.com/github/github-mcp-server/blob/main/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`](https://github.com/github/github-mcp-server/blob/main/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:

```go
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`](https://github.com/github/github-mcp-server/blob/main/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`](https://github.com/github/github-mcp-server/blob/main/builder.go) lines 99-108](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go#L99-L108); registration at [[`registry.go`](https://github.com/github/github-mcp-server/blob/main/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:

```go
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`](https://github.com/github/github-mcp-server/blob/main/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`](https://github.com/github/github-mcp-server/blob/main/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`](https://github.com/github/github-mcp-server/blob/main/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`, and `WithFilter` that store state without immediate validation.
- **Build** executes in `Builder.Build()` at [`pkg/inventory/builder.go`](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go), performing insiders filtering, toolset expansion, alias resolution, and strict validation that returns `ErrUnknownTools` for unrecognized tool names.
- **Registration** happens through `Inventory.RegisterAll` at [`pkg/inventory/registry.go`](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/registry.go), which iterates over filtered `AvailableTools` and invokes each tool's `RegisterFunc` on 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`](https://github.com/github/github-mcp-server/blob/main/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.