Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[WIP] Support completions for GH resources#450

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
Copilot wants to merge4 commits intomain
base:main
Choose a base branch
Loading
fromcopilot/fix-422
Draft
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletione2e/e2e_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -182,7 +182,7 @@ func setupMCPClient(t *testing.T, options ...clientOption) *mcpClient.Client {
require.NoError(t, err, "expected to construct MCP server successfully")

t.Log("Starting In Process MCP client...")
client, err = mcpClient.NewInProcessClient(ghServer)
client, err = mcpClient.NewInProcessClient(ghServer.GetMCPServer())
require.NoError(t, err, "expected to create in-process client successfully")
}

Expand Down
2 changes: 1 addition & 1 deletiongo.mod
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@ go 1.23.7
require (
github.com/google/go-github/v69 v69.2.0
github.com/josephburnett/jd v1.9.2
github.com/mark3labs/mcp-go v0.30.0
github.com/mark3labs/mcp-go v0.30.1
github.com/migueleliasweb/go-github-mock v1.3.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
Expand Down
2 changes: 2 additions & 0 deletionsgo.sum
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mark3labs/mcp-go v0.30.0 h1:Taz7fiefkxY/l8jz1nA90V+WdM2eoMtlvwfWforVYbo=
github.com/mark3labs/mcp-go v0.30.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
github.com/mark3labs/mcp-go v0.30.1 h1:3R1BPvNT/rC1iPpLx+EMXFy+gvux/Mz/Nio3c6XEU9E=
github.com/mark3labs/mcp-go v0.30.1/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
github.com/migueleliasweb/go-github-mock v1.3.0 h1:2sVP9JEMB2ubQw1IKto3/fzF51oFC6eVWOOFDgQoq88=
github.com/migueleliasweb/go-github-mock v1.3.0/go.mod h1:ipQhV8fTcj/G6m7BKzin08GaJ/3B5/SonRAkgrk0zCY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
Expand Down
18 changes: 9 additions & 9 deletionsinternal/ghmcp/server.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,7 +47,7 @@ type MCPServerConfig struct {
Translator translations.TranslationHelperFunc
}

func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
func NewMCPServer(cfg MCPServerConfig) (*github.GitHubMCPServer, error) {
apiHost, err := parseAPIHost(cfg.Host)
if err != nil {
return nil, fmt.Errorf("failed to parse API host: %w", err)
Expand DownExpand Up@@ -91,8 +91,6 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
OnBeforeInitialize: []server.OnBeforeInitializeFunc{beforeInit},
}

ghServer := github.NewServer(cfg.Version, server.WithHooks(hooks))

enabledToolsets := cfg.EnabledToolsets
if cfg.DynamicToolsets {
// filter "all" from the enabled toolsets
Expand All@@ -112,6 +110,8 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
return gqlClient, nil // closing over client
}

ghServer := github.NewGitHubServer(cfg.Version, getClient, server.WithHooks(hooks))

// Create default toolsets
toolsets, err := github.InitToolsets(
enabledToolsets,
Expand All@@ -125,15 +125,15 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
}

context := github.InitContextToolset(getClient, cfg.Translator)
github.RegisterResources(ghServer, getClient, cfg.Translator)
github.RegisterResources(ghServer.MCPServer, getClient, cfg.Translator)

// Register the tools with the server
toolsets.RegisterTools(ghServer)
context.RegisterTools(ghServer)
toolsets.RegisterTools(ghServer.MCPServer)
context.RegisterTools(ghServer.MCPServer)

if cfg.DynamicToolsets {
dynamic := github.InitDynamicToolset(ghServer, toolsets, cfg.Translator)
dynamic.RegisterTools(ghServer)
dynamic := github.InitDynamicToolset(ghServer.MCPServer, toolsets, cfg.Translator)
dynamic.RegisterTools(ghServer.MCPServer)
}

return ghServer, nil
Expand DownExpand Up@@ -192,7 +192,7 @@ func RunStdioServer(cfg StdioServerConfig) error {
return fmt.Errorf("failed to create MCP server: %w", err)
}

stdioServer :=server.NewStdioServer(ghServer)
stdioServer :=github.NewCompletionAwareStdioServer(ghServer.GetMCPServer(), ghServer.GetCompletionHandler())

logrusLogger := logrus.New()
if cfg.LogFilePath != "" {
Expand Down
142 changes: 142 additions & 0 deletionspkg/github/completion_integration_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
package github

import (
"context"
"testing"

"github.com/google/go-github/v69/github"
"github.com/mark3labs/mcp-go/mcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGitHubMCPServerCompletionIntegration(t *testing.T) {
// Mock client function
getClient := func(_ context.Context) (*github.Client, error) {
// Return a nil client - this will cause API calls to fail gracefully
// which is fine for testing the completion request handling flow
return nil, nil
}

// Create a GitHub MCP server with completion support
ghServer := NewGitHubServer("test", getClient)
require.NotNil(t, ghServer)

// Create an in-process client with our custom GitHubMCPServer transport
mcpClient, err := NewInProcessClientWithGitHubServer(ghServer)
require.NoError(t, err)

// Initialize the client
ctx := context.Background()
request := mcp.InitializeRequest{}
request.Params.ProtocolVersion = "2025-03-26"
request.Params.ClientInfo = mcp.Implementation{
Name: "test-client",
Version: "1.0.0",
}

result, err := mcpClient.Initialize(ctx, request)
require.NoError(t, err)
assert.Equal(t, "github-mcp-server", result.ServerInfo.Name)

// Test completion request - this should work even with a nil GitHub client
// because non-repo URIs return empty completions without calling GitHub APIs
completionRequest := mcp.CompleteRequest{
Params: struct {
Ref any `json:"ref"`
Argument struct {
Name string `json:"name"`
Value string `json:"value"`
} `json:"argument"`
}{
Ref: map[string]interface{}{
"type": "ref/resource",
"uri": "file:///some/non-repo/path",
},
Argument: struct {
Name string `json:"name"`
Value string `json:"value"`
}{
Name: "param",
Value: "test",
},
},
}

completionResult, err := mcpClient.Complete(ctx, completionRequest)
require.NoError(t, err)
require.NotNil(t, completionResult)

// Should return empty completion for non-repo URIs
assert.Equal(t, []string{}, completionResult.Completion.Values)
assert.Equal(t, 0, completionResult.Completion.Total)

// Test repo URI completion with unsupported argument
repoCompletionRequest := mcp.CompleteRequest{
Params: struct {
Ref any `json:"ref"`
Argument struct {
Name string `json:"name"`
Value string `json:"value"`
} `json:"argument"`
}{
Ref: map[string]interface{}{
"type": "ref/resource",
"uri": "repo://{owner}/{repo}/contents{/path*}",
},
Argument: struct {
Name string `json:"name"`
Value string `json:"value"`
}{
Name: "unsupported",
Value: "test",
},
},
}

repoCompletionResult, err := mcpClient.Complete(ctx, repoCompletionRequest)
require.NoError(t, err)
require.NotNil(t, repoCompletionResult)

// Should return empty completion for unsupported arguments
assert.Equal(t, []string{}, repoCompletionResult.Completion.Values)
assert.Equal(t, 0, repoCompletionResult.Completion.Total)

// Clean up
err = mcpClient.Close()
assert.NoError(t, err)
}

func TestGitHubMCPServerCompletionCapabilities(t *testing.T) {
// Mock client function
getClient := func(_ context.Context) (*github.Client, error) {
return nil, nil
}

// Create a GitHub MCP server with completion support
ghServer := NewGitHubServer("test", getClient)
require.NotNil(t, ghServer)

// Create an in-process client with our custom GitHubMCPServer transport
mcpClient, err := NewInProcessClientWithGitHubServer(ghServer)
require.NoError(t, err)

// Initialize the client
ctx := context.Background()
request := mcp.InitializeRequest{}
request.Params.ProtocolVersion = "2025-03-26"
request.Params.ClientInfo = mcp.Implementation{
Name: "test-client",
Version: "1.0.0",
}

result, err := mcpClient.Initialize(ctx, request)
require.NoError(t, err)

// Check basic server info
assert.Equal(t, "github-mcp-server", result.ServerInfo.Name)

// Clean up
err = mcpClient.Close()
assert.NoError(t, err)
}
141 changes: 141 additions & 0 deletionspkg/github/completion_stdio_server.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
package github

import (
"context"
"encoding/json"
"io"
"log"

"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)

// CompletionAwareStdioServer wraps the MCP stdio server to add completion support
type CompletionAwareStdioServer struct {
baseServer *server.MCPServer
completionHandler CompletionHandlerFunc
errLogger *log.Logger
}

// NewCompletionAwareStdioServer creates a new stdio server with completion support
func NewCompletionAwareStdioServer(mcpServer *server.MCPServer, completionHandler CompletionHandlerFunc) *CompletionAwareStdioServer {
return &CompletionAwareStdioServer{
baseServer: mcpServer,
completionHandler: completionHandler,
errLogger: log.New(io.Discard, "", 0), // Default to discarding errors
}
}

// SetErrorLogger sets the error logger for the server
func (s *CompletionAwareStdioServer) SetErrorLogger(logger *log.Logger) {
s.errLogger = logger
}

// Listen starts the completion-aware stdio server
func (s *CompletionAwareStdioServer) Listen(ctx context.Context, stdin io.Reader, stdout io.Writer) error {
// Use the simplified approach: create a custom stdio server that mimics the real one
// but intercepts completion requests

// We'll use the real stdio server from the mcp-go library and intercept the raw messages
realStdioServer := server.NewStdioServer(s.baseServer)
realStdioServer.SetErrorLogger(s.errLogger)

// Create pipes to intercept messages
stdinPipe := &completionInterceptReader{
original: stdin,
completionHandler: s.completionHandler,
baseServer: s.baseServer,
stdout: stdout,
ctx: ctx,
errLogger: s.errLogger,
}

return realStdioServer.Listen(ctx, stdinPipe, stdout)
}

// completionInterceptReader intercepts stdin to handle completion requests
type completionInterceptReader struct {
original io.Reader
completionHandler CompletionHandlerFunc
baseServer *server.MCPServer
stdout io.Writer
ctx context.Context
errLogger *log.Logger
buffer []byte
bufferPos int
}

func (r *completionInterceptReader) Read(p []byte) (n int, err error) {
// If we have buffered data, return that first
if r.bufferPos < len(r.buffer) {
n = copy(p, r.buffer[r.bufferPos:])
r.bufferPos += n
if r.bufferPos >= len(r.buffer) {
r.buffer = nil
r.bufferPos = 0
}
return n, nil
}

// Read from original source
n, err = r.original.Read(p)
if err != nil {
return n, err
}

// Check if this contains a completion request
data := p[:n]
if r.isCompletionRequest(data) {
// Handle completion request directly
response := r.handleCompletionRequest(data)
if response != nil {
// Write response to stdout
encoder := json.NewEncoder(r.stdout)
if encErr := encoder.Encode(response); encErr != nil {
r.errLogger.Printf("Error writing completion response: %v", encErr)
}
}
// Return EOF to the real server so it doesn't process this message
return 0, io.EOF
}

return n, err
}

// isCompletionRequest checks if the data contains a completion request
func (r *completionInterceptReader) isCompletionRequest(data []byte) bool {
var baseMessage struct {
Method string `json:"method"`
}

if err := json.Unmarshal(data, &baseMessage); err != nil {
return false
}

return baseMessage.Method == "completion/complete"
}

// handleCompletionRequest processes completion requests
func (r *completionInterceptReader) handleCompletionRequest(data []byte) mcp.JSONRPCMessage {
var baseMessage struct {
JSONRPC string `json:"jsonrpc"`
ID any `json:"id"`
Method string `json:"method"`
}

if err := json.Unmarshal(data, &baseMessage); err != nil {
return createErrorResponse(baseMessage.ID, mcp.PARSE_ERROR, "Failed to parse completion request")
}

var request mcp.CompleteRequest
if err := json.Unmarshal(data, &request); err != nil {
return createErrorResponse(baseMessage.ID, mcp.INVALID_REQUEST, "Failed to parse completion request")
}

result, err := r.completionHandler(r.ctx, request)
if err != nil {
return createErrorResponse(baseMessage.ID, mcp.INTERNAL_ERROR, err.Error())
}

return createResponse(baseMessage.ID, *result)
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp