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:
- Filtering:
Inventory.ForMCPRequestnarrows the inventory to the single requested tool usingfilterToolsByName - Registration:
RegisterToolsadds the tool's handler to the MCP server vias.AddTool - 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/listandresources/templates/list: Return available resource templatesresources/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
ServerToolinpkg/inventory/server_tool.go, with execution flowing throughtools/callrequests filtered byForMCPRequest - Resources provide read-only data access through
ServerResourceTemplateinpkg/inventory/resources.go, using URI patterns that the MCP SDK resolves duringresources/readoperations - Prompts deliver guided workflows via
ServerPromptinpkg/inventory/prompts.go, returning structured conversation steps throughprompts/gethandlers - Request scoping ensures security and performance by filtering the inventory to only relevant capabilities for each MCP method via
Inventory.ForMCPRequestinpkg/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →