parent
316e8a7385
commit
126c799723
3 changed files with 150 additions and 13 deletions
BIN
core-test
Executable file
BIN
core-test
Executable file
Binary file not shown.
|
|
@ -82,61 +82,61 @@ func New(opts ...Option) (*Service, error) {
|
|||
}
|
||||
}
|
||||
|
||||
s.registerTools()
|
||||
s.registerTools(s.server)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// registerTools adds file operation tools to the MCP server.
|
||||
func (s *Service) registerTools() {
|
||||
func (s *Service) registerTools(server *mcp.Server) {
|
||||
// File operations
|
||||
mcp.AddTool(s.server, &mcp.Tool{
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "file_read",
|
||||
Description: "Read the contents of a file",
|
||||
}, s.readFile)
|
||||
|
||||
mcp.AddTool(s.server, &mcp.Tool{
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "file_write",
|
||||
Description: "Write content to a file",
|
||||
}, s.writeFile)
|
||||
|
||||
mcp.AddTool(s.server, &mcp.Tool{
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "file_delete",
|
||||
Description: "Delete a file or empty directory",
|
||||
}, s.deleteFile)
|
||||
|
||||
mcp.AddTool(s.server, &mcp.Tool{
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "file_rename",
|
||||
Description: "Rename or move a file",
|
||||
}, s.renameFile)
|
||||
|
||||
mcp.AddTool(s.server, &mcp.Tool{
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "file_exists",
|
||||
Description: "Check if a file or directory exists",
|
||||
}, s.fileExists)
|
||||
|
||||
mcp.AddTool(s.server, &mcp.Tool{
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "file_edit",
|
||||
Description: "Edit a file by replacing old_string with new_string. Use replace_all=true to replace all occurrences.",
|
||||
}, s.editDiff)
|
||||
|
||||
// Directory operations
|
||||
mcp.AddTool(s.server, &mcp.Tool{
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "dir_list",
|
||||
Description: "List contents of a directory",
|
||||
}, s.listDirectory)
|
||||
|
||||
mcp.AddTool(s.server, &mcp.Tool{
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "dir_create",
|
||||
Description: "Create a new directory",
|
||||
}, s.createDirectory)
|
||||
|
||||
// Language detection
|
||||
mcp.AddTool(s.server, &mcp.Tool{
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "lang_detect",
|
||||
Description: "Detect the programming language of a file",
|
||||
}, s.detectLanguage)
|
||||
|
||||
mcp.AddTool(s.server, &mcp.Tool{
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "lang_list",
|
||||
Description: "Get list of supported programming languages",
|
||||
}, s.getSupportedLanguages)
|
||||
|
|
@ -566,8 +566,14 @@ func detectLanguageFromPath(path string) string {
|
|||
}
|
||||
}
|
||||
|
||||
// Run starts the MCP server on stdio.
|
||||
// Run starts the MCP server.
|
||||
// If MCP_ADDR is set, it starts a TCP server.
|
||||
// Otherwise, it starts a Stdio server.
|
||||
func (s *Service) Run(ctx context.Context) error {
|
||||
addr := os.Getenv("MCP_ADDR")
|
||||
if addr != "" {
|
||||
return s.ServeTCP(ctx, addr)
|
||||
}
|
||||
return s.server.Run(ctx, &mcp.StdioTransport{})
|
||||
}
|
||||
|
||||
|
|
|
|||
131
pkg/mcp/transport_tcp.go
Normal file
131
pkg/mcp/transport_tcp.go
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
package mcp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// TCPTransport manages a TCP listener for MCP.
|
||||
type TCPTransport struct {
|
||||
addr string
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
// NewTCPTransport creates a new TCP transport listener.
|
||||
// It listens on the provided address (e.g. "localhost:9100").
|
||||
func NewTCPTransport(addr string) (*TCPTransport, error) {
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TCPTransport{addr: addr, listener: listener}, nil
|
||||
}
|
||||
|
||||
// ServeTCP starts a TCP server for the MCP service.
|
||||
// It accepts connections and spawns a new MCP server session for each connection.
|
||||
func (s *Service) ServeTCP(ctx context.Context, addr string) error {
|
||||
t, err := NewTCPTransport(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer t.listener.Close()
|
||||
|
||||
if addr == "" {
|
||||
addr = t.listener.Addr().String()
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "MCP TCP server listening on %s\n", addr)
|
||||
|
||||
for {
|
||||
conn, err := t.listener.Accept()
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Accept error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
go s.handleConnection(ctx, conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) handleConnection(ctx context.Context, conn net.Conn) {
|
||||
// Note: We don't defer conn.Close() here because it's closed by the Server/Transport
|
||||
|
||||
// Create new server instance for this connection
|
||||
impl := &mcp.Implementation{
|
||||
Name: "core-cli",
|
||||
Version: "0.1.0",
|
||||
}
|
||||
server := mcp.NewServer(impl, nil)
|
||||
s.registerTools(server)
|
||||
|
||||
// Create transport for this connection
|
||||
transport := &connTransport{conn: conn}
|
||||
|
||||
// Run server (blocks until connection closed)
|
||||
// Server.Run calls Connect, then Read loop.
|
||||
if err := server.Run(ctx, transport); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Connection error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// connTransport adapts net.Conn to mcp.Transport
|
||||
type connTransport struct {
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (t *connTransport) Connect(ctx context.Context) (mcp.Connection, error) {
|
||||
return &connConnection{
|
||||
conn: t.conn,
|
||||
scanner: bufio.NewScanner(t.conn),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// connConnection implements mcp.Connection
|
||||
type connConnection struct {
|
||||
conn net.Conn
|
||||
scanner *bufio.Scanner
|
||||
}
|
||||
|
||||
func (c *connConnection) Read(ctx context.Context) (jsonrpc.Message, error) {
|
||||
// Blocks until line is read
|
||||
if !c.scanner.Scan() {
|
||||
if err := c.scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// EOF
|
||||
// Return error to signal closure, as per Scanner contract?
|
||||
// SDK usually expects error on close.
|
||||
return nil, fmt.Errorf("EOF")
|
||||
}
|
||||
line := c.scanner.Bytes()
|
||||
return jsonrpc.DecodeMessage(line)
|
||||
}
|
||||
|
||||
func (c *connConnection) Write(ctx context.Context, msg jsonrpc.Message) error {
|
||||
data, err := jsonrpc.EncodeMessage(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Append newline for line-delimited JSON
|
||||
data = append(data, '\n')
|
||||
_, err = c.conn.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *connConnection) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *connConnection) SessionID() string {
|
||||
return "tcp-session" // Unique ID might be better, but optional
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue