Implementation Plan For MCP Server
Overview
This document outlines the implementation plan for adding Model Context Protocol (MCP) server functionality to protolint. The implementation will focus on local file system usage, utilizing stdio transport for communication between MCP clients and the protolint server.
1. Basic Design
Add MCP Reporter
- Create a new reporter in
linter/report/reporters/mcpReporter.go
- Extend the existing reporter mechanism to include MCP-compatible formatting
Add --mcp Flag
- Add a
--mcp
option to the protolint command - Enable MCP server mode when this flag is present
Implement MCP Server
- Create an
internal/mcp
directory for server-related code - Implement request/response protocol handling
- Implement communication via stdin/stdout
2. Detailed Implementation
2.1 MCP Reporter Implementation
Add a new reporter in mcpReporter.go
that formats failures into MCP-compatible JSON:
package reporters
import (
"encoding/json"
"fmt"
"io"
"github.com/yoheimuta/protolint/linter/report"
)
// MCPReporter prints failures in MCP friendly JSON format.
type MCPReporter struct{}
// Report writes failures to w in MCP friendly format.
func (r MCPReporter) Report(w io.Writer, fs []report.Failure) error {
// Group failures by file
fileFailures := make(map[string][]map[string]interface{})
for _, failure := range fs {
filePath := failure.Pos().Filename
failureInfo := map[string]interface{}{
"rule_id": failure.RuleID(),
"message": failure.Message(),
"line": failure.Pos().Line,
"column": failure.Pos().Column,
"severity": failure.Severity(),
}
fileFailures[filePath] = append(fileFailures[filePath], failureInfo)
}
// Convert to array of results
var fileResults []map[string]interface{}
for filePath, failures := range fileFailures {
fileResults = append(fileResults, map[string]interface{}{
"file_path": filePath,
"failures": failures,
})
}
result := map[string]interface{}{
"results": fileResults,
}
bs, err := json.MarshalIndent(result, "", " ")
if err != nil {
return err
}
_, err = fmt.Fprintln(w, string(bs))
return err
}
Next, add the mcp
reporter to the GetReporter
function in internal/cmd/subcmds/lint/reporterFlag.go
:
// GetReporter returns a reporter from the specified key.
func GetReporter(value string) (report.Reporter, error) {
rs := map[string]report.Reporter{
// Existing reporters
...
"mcp": reporters.MCPReporter{}, // Add MCP reporter
...
}
// ...
}
2.2 MCP Server Implementation Files
protocol.go
package mcp
import (
"encoding/json"
)
// Request represents an MCP request
type Request struct {
Type string `json:"type"`
ID string `json:"id"`
Payload json.RawMessage `json:"payload"`
}
// Response represents an MCP response
type Response struct {
Type string `json:"type"`
ID string `json:"id"`
Payload interface{} `json:"payload"`
}
// ListToolsResponse represents the response for list_tools request
type ListToolsResponse struct {
Tools []ToolInfo `json:"tools"`
}
// ToolInfo represents information about a tool
type ToolInfo struct {
Name string `json:"name"`
Description string `json:"description"`
Schema interface{} `json:"schema,omitempty"`
}
// CallToolPayload represents the payload for call_tool request
type CallToolPayload struct {
Name string `json:"name"`
Arguments json.RawMessage `json:"arguments"`
}
// CallToolResponse represents the response for call_tool request
type CallToolResponse struct {
Result interface{} `json:"result"`
}
server.go
package mcp
import (
"encoding/json"
"fmt"
"io"
"os"
"github.com/yoheimuta/protolint/internal/osutil"
)
// Server represents an MCP server
type Server struct {
tools []Tool
}
// NewServer creates a new MCP server
func NewServer() *Server {
return &Server{
tools: []Tool{
NewLintFilesTool(),
// Other tools can be added in the future
},
}
}
// Run starts the MCP server
func (s *Server) Run(stdout, stderr io.Writer) osutil.ExitCode {
fmt.Fprintf(stderr, "protolint MCP server is running. cwd: %s\n", getCurrentDir())
decoder := json.NewDecoder(os.Stdin)
encoder := json.NewEncoder(stdout)
for {
var request Request
if err := decoder.Decode(&request); err != nil {
if err == io.EOF {
// Normal termination
return osutil.ExitSuccess
}
fmt.Fprintf(stderr, "Error decoding request: %v\n", err)
return osutil.ExitInternalFailure
}
response := s.handleRequest(&request)
if err := encoder.Encode(response); err != nil {
fmt.Fprintf(stderr, "Error encoding response: %v\n", err)
return osutil.ExitInternalFailure
}
}
}
// handleRequest handles a single MCP request
func (s *Server) handleRequest(req *Request) *Response {
switch req.Type {
case "list_tools":
return s.handleListTools(req)
case "call_tool":
return s.handleCallTool(req)
default:
return &Response{
Type: "error",
ID: req.ID,
Payload: map[string]string{
"message": fmt.Sprintf("Unknown request type: %s", req.Type),
},
}
}
}
// handleListTools handles list_tools request
func (s *Server) handleListTools(req *Request) *Response {
toolInfos := make([]ToolInfo, 0, len(s.tools))
for _, tool := range s.tools {
toolInfos = append(toolInfos, tool.GetInfo())
}
return &Response{
Type: "list_tools_response",
ID: req.ID,
Payload: &ListToolsResponse{
Tools: toolInfos,
},
}
}
// handleCallTool handles call_tool request
func (s *Server) handleCallTool(req *Request) *Response {
var payload CallToolPayload
if err := json.Unmarshal(req.Payload, &payload); err != nil {
return &Response{
Type: "error",
ID: req.ID,
Payload: map[string]string{
"message": fmt.Sprintf("Invalid payload: %v", err),
},
}
}
// Find the tool
var tool Tool
for _, t := range s.tools {
if t.GetInfo().Name == payload.Name {
tool = t
break
}
}
if tool == nil {
return &Response{
Type: "error",
ID: req.ID,
Payload: map[string]string{
"message": fmt.Sprintf("Tool not found: %s", payload.Name),
},
}
}
// Execute the tool
result, err := tool.Execute(payload.Arguments)
if err != nil {
return &Response{
Type: "error",
ID: req.ID,
Payload: map[string]string{
"message": fmt.Sprintf("Tool execution failed: %v", err),
},
}
}
return &Response{
Type: "call_tool_response",
ID: req.ID,
Payload: &CallToolResponse{
Result: result,
},
}
}
// getCurrentDir returns the current working directory
func getCurrentDir() string {
dir, err := os.Getwd()
if err != nil {
return "<unknown>"
}
return dir
}
tools.go
package mcp
import (
"bytes"
"encoding/json"
"fmt"
"github.com/yoheimuta/protolint/lib"
)
// Tool defines the interface for MCP tools
type Tool interface {
GetInfo() ToolInfo
Execute(args json.RawMessage) (interface{}, error)
}
// LintFilesTool is a tool for linting Proto files
type LintFilesTool struct{}
// NewLintFilesTool creates a new LintFilesTool
func NewLintFilesTool() *LintFilesTool {
return &LintFilesTool{}
}
// GetInfo returns the tool information
func (t *LintFilesTool) GetInfo() ToolInfo {
return ToolInfo{
Name: "lint-files",
Description: "Lint Protocol Buffer files using protolint",
Schema: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"files": map[string]interface{}{
"type": "array",
"items": map[string]interface{}{
"type": "string",
},
"description": "List of file paths to lint",
},
"config_path": map[string]interface{}{
"type": "string",
"description": "Path to protolint config file",
},
"fix": map[string]interface{}{
"type": "<br/>
**Q&A: Implementation Plan for MCP Server**
=============================================
**Q: What is the purpose of the MCP server?**
------------------------------------------
A: The MCP server is designed to provide a communication interface between MCP clients and the protolint server. It will handle requests and responses in MCP format, allowing for seamless integration with MCP clients.
**Q: What are the key components of the MCP server implementation?**
----------------------------------------------------------------
A: The key components of the MCP server implementation include:
1. **MCP Reporter**: A new reporter that formats failures into MCP-compatible JSON.
2. **--mcp Flag**: A new flag that enables MCP server mode when present.
3. **MCP Server**: A server that handles request/response processing and communication via stdin/stdout.
**Q: How does the MCP reporter work?**
--------------------------------------
A: The MCP reporter is responsible for formatting failures into MCP-compatible JSON. It groups failures by file and converts them into an array of results, which is then encoded into JSON.
**Q: What is the purpose of the --mcp flag?**
------------------------------------------
A: The --mcp flag enables MCP server mode when present. When this flag is detected, the protolint command will start the MCP server, allowing for communication with MCP clients.
**Q: How does the MCP server handle requests and responses?**
---------------------------------------------------------
A: The MCP server uses a request/response protocol to handle incoming requests and send responses back to the client. It uses JSON encoding to serialize and deserialize requests and responses.
**Q: What are the benefits of using the MCP server?**
------------------------------------------------
A: The MCP server provides several benefits, including:
1. **Reuse of Existing Code**: The MCP server leverages existing reporter and lint functionality, reducing the amount of code that needs to be written.
2. **Clear Responsibility Separation**: The MCP server has a clear and well-defined responsibility, making it easier to maintain and extend.
3. **Future Extensibility**: The MCP server is designed to be extensible, allowing for the addition of new tools and features in the future.
**Q: How does the MCP server integrate with MCP clients?**
------------------------------------------------------
A: The MCP server integrates with MCP clients through the use of the MCP protocol. MCP clients can send requests to the MCP server, which will then process the requests and send responses back to the client.
**Q: What are the advantages of using the MCP server with Claude Desktop?**
-------------------------------------------------------------------
A: The MCP server provides several advantages when used with Claude Desktop, including:
1. **Seamless Integration**: The MCP server provides a seamless integration with Claude Desktop, allowing for easy communication and data exchange.
2. **Improved Productivity**: The MCP server enables improved productivity by providing a streamlined and efficient way to work with MCP clients.
3. **Enhanced Collaboration**: The MCP server enables enhanced collaboration by providing a common interface for communication and data exchange between MCP clients and the protolint server.