How the MCP Protocol Handles Tool Execution, Resources, and Prompts in GitHub MCP Server

The MCP protocol handles tool execution, resources, and prompts through three distinct capability groups—Tools for callable actions, Resources for structured data queries, and Prompts for guided workflows—each implemented via specific MCP methods and filtered through request-scoped inventory views.

The GitHub MCP server implements the Model Context Protocol (MCP) specification by exposing these three capability groups through a centralized inventory system. This architecture enables AI clients to discover and invoke GitHub operations, read repository metadata, and execute guided workflows while maintaining clean separation between protocol handling and business logic.

Understanding MCP Protocol Capabilities

The server organizes capabilities into three distinct groups, each mapped to specific MCP methods:

Capability MCP Method Purpose Implementation File
Tools tools/list & tools/call Callable actions like creating issues or pull requests pkg/inventory/server_tool.go
Resources resources/list, resources/read, resources/templates/list Structured data queries for repository metadata pkg/inventory/resources.go
Prompts prompts/list, prompts/get Guided conversation flows and workflows pkg/inventory/prompts.go

The Inventory struct defined in pkg/inventory/registry.go holds the complete collection of these items at server startup. When processing requests, the server uses Inventory.ForMCPRequest to create filtered views containing only the capabilities relevant to the current MCP method.

How MCP Protocol Handles Tool Execution

Tool execution represents the primary mechanism for modifying GitHub state or retrieving computed data. The protocol handles this through a lazy initialization pattern that defers handler creation until registration time.

Tool Definition and Lazy Handler Generation

Each tool is encapsulated in a ServerTool struct defined in pkg/inventory/server_tool.go:

type ServerTool struct {
    Tool          mcp.Tool
    Toolset       ToolsetMetadata
    HandlerFunc   HandlerFunc   // deps → mcp.ToolHandler
    // … feature-flag, scope, insider, read-only fields …
}

The HandlerFunc generates the actual handler using injected dependencies (such as GitHub API clients). This approach avoids creating heavy closures for every request, instead constructing handlers only during the registration phase in pkg/github/server.go.

Tool Execution Flow

When a client invokes tools/call, the server processes the request through this pipeline:

  1. Filtering: Inventory.ForMCPRequest narrows the inventory to the single requested tool using filterToolsByName
  2. Registration: RegisterTools adds the tool's handler to the MCP server via s.AddTool
  3. Invocation: The MCP server routes the call to the generated handler, which unmarshals JSON arguments, executes business logic, and returns a mcp.CallToolResult
// Example tool handler implementation
func createIssueHandler(deps any) mcp.ToolHandler {
    client := deps.(ToolDependencies).GetClient
    return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
        var args struct{ Owner, Repo, Title, Body string }
        if err := json.Unmarshal(req.Params.Arguments, &args); err != nil {
            return nil, err
        }
        // Execute GitHub API call via client
        result, err := client.CreateIssue(ctx, args.Owner, args.Repo, args.Title, args.Body)
        if err != nil {
            return nil, err
        }
        return &mcp.CallToolResult{
            Result: json.RawMessage(fmt.Sprintf(`{"url":"%s"}`, result.URL)),
        }, nil
    }
}

How MCP Protocol Handles Resources

Resources provide read-only access to structured data through URI-addressable templates. Unlike tools, resources follow a passive model where the client reads data rather than invoking actions.

Resource Template Structure

Resources are defined using ServerResourceTemplate in pkg/inventory/resources.go:

type ServerResourceTemplate struct {
    Template          mcp.ResourceTemplate
    HandlerFunc       ResourceHandlerFunc // deps → mcp.ResourceHandler
    Toolset           ToolsetMetadata
    FeatureFlagEnable string
    FeatureFlagDisable string
}

The ResourceTemplate defines URI patterns with parameterized segments (e.g., repo://{owner}/{repo}), allowing clients to request specific resources by substituting values into the template.

Resource Resolution and Read Operations

The MCP protocol handles resource access through these methods:

  • resources/list and resources/templates/list: Return available resource templates
  • resources/read: Accepts a concrete URI and returns the resource content

When processing resources/read, the MCP SDK (not the server code directly) matches the requested URI against registered templates and invokes the associated ResourceHandlerFunc. The handler receives the parsed URI and returns structured data:

// Example resource handler
func repoHandler(deps any) mcp.ResourceHandler {
    client := deps.(ToolDependencies).GetClient
    return func(ctx context.Context, uri string) (any, error) {
        // Parse owner/repo from URI pattern repo://{owner}/{repo}
        parts := strings.Split(strings.TrimPrefix(uri, "repo://"), "/")
        if len(parts) != 2 {
            return nil, fmt.Errorf("invalid uri format")
        }
        owner, repo := parts[0], parts[1]
        
        // Fetch repository metadata
        metadata, err := client.GetRepository(ctx, owner, repo)
        if err != nil {
            return nil, err
        }
        
        return metadata, nil
    }
}

How MCP Protocol Handles Prompts

Prompts represent guided conversation flows that help users complete complex multi-step tasks. They differ from tools by returning structured conversation guidance rather than executing actions directly.

Prompt Definition and Workflow Generation

Prompts are encapsulated in ServerPrompt defined in pkg/inventory/prompts.go:

type ServerPrompt struct {
    Prompt            mcp.Prompt
    Handler           mcp.PromptHandler
    Toolset           ToolsetMetadata
    FeatureFlagEnable string
    FeatureFlagDisable string
}

The mcp.Prompt defines metadata such as name and description, while the PromptHandler generates the actual prompt content when requested via prompts/get.

When a client requests a prompt, the server returns a structured workflow that typically includes suggested tool calls or conversation steps:

// Example prompt handler from pkg/github/workflow_prompts.go
func issueFixPromptHandler(ctx context.Context, req *mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
    return &mcp.GetPromptResult{
        Description: "Workflow to create an issue and fix it with a PR",
        Steps: []mcp.PromptStep{
            {
                Tool: "create_issue",
                Description: "Create a new issue describing the bug",
            },
            {
                Tool: "create_branch",
                Description: "Create a branch to work on the fix",
            },
            {
                Tool: "create_pull_request",
                Description: "Open a PR that closes the issue",
            },
        },
    }, nil
}

Request-Scoped Filtering with ForMCPRequest

The Inventory.ForMCPRequest method in pkg/inventory/registry.go serves as the central routing mechanism that determines which capabilities are exposed for each MCP method. This method creates shallow copies of the inventory containing only relevant items:

// Simplified excerpt from ForMCPRequest (registry.go)
switch method {
case MCPMethodToolsCall:
    result.resourceTemplates, result.prompts = nil, nil
    if itemName != "" {
        result.tools = r.filterToolsByName(itemName)
    }
case MCPMethodPromptsGet:
    result.tools, result.resourceTemplates = nil, nil
    if itemName != "" {
        result.prompts = r.filterPromptsByName(itemName)
    }
case MCPMethodResourcesRead:
    // Keep only resource templates
    result.tools, result.prompts = nil, nil
    // Filter by URI pattern if needed
}

This filtering mechanism ensures that:

  • Security: Only the requested capability is exposed during a call
  • Performance: Unnecessary handlers are not registered for the current request scope
  • Flexibility: Feature flags and toolset filters apply consistently across all capability types

Summary

The MCP protocol handles tool execution, resources, and prompts through a unified inventory architecture in the GitHub MCP server:

  • Tools use lazy handler generation via ServerTool in pkg/inventory/server_tool.go, with execution flowing through tools/call requests filtered by ForMCPRequest
  • Resources provide read-only data access through ServerResourceTemplate in pkg/inventory/resources.go, using URI patterns that the MCP SDK resolves during resources/read operations
  • Prompts deliver guided workflows via ServerPrompt in pkg/inventory/prompts.go, returning structured conversation steps through prompts/get handlers
  • Request scoping ensures security and performance by filtering the inventory to only relevant capabilities for each MCP method via Inventory.ForMCPRequest in pkg/inventory/registry.go

Frequently Asked Questions

What is the difference between MCP tools and resources?

Tools are callable actions that can modify state or perform computations, accessed via tools/call and implemented using ServerTool with lazy handler generation. Resources are read-only data sources accessed via resources/read using URI templates, implemented via ServerResourceTemplate in pkg/inventory/resources.go. While tools execute business logic through handlers, resources return structured data based on URI pattern matching handled by the MCP SDK.

How does the GitHub MCP server filter capabilities for specific requests?

The server uses the ForMCPRequest method in pkg/inventory/registry.go to create request-scoped inventory views. This method switches on the MCP method name (such as MCPMethodToolsCall or MCPMethodResourcesRead) and returns a shallow copy of the inventory containing only the relevant capabilities. For single-item requests like tools/call, it further filters using filterToolsByName to isolate the specific tool being invoked.

Can MCP prompts execute tools directly?

No, MCP prompts do not execute tools directly. Instead, prompts defined in pkg/inventory/prompts.go return structured workflow guidance via mcp.PromptHandler functions. When a client calls prompts/get, the handler returns a mcp.GetPromptResult containing steps that suggest which tools to call and in what order. The actual tool execution happens separately through tools/call requests initiated by the client based on the prompt's guidance.

What is lazy handler generation in MCP tool execution?

Lazy handler generation is a performance optimization pattern used in pkg/inventory/server_tool.go where ServerTool stores a HandlerFunc rather than a concrete handler. This function accepts dependencies and returns an mcp.ToolHandler only when the tool is registered with the MCP server. This approach avoids creating heavy closures for every request and ensures handlers are instantiated with the correct dependency injection context during the registration phase in pkg/github/server.go.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →